<commit_before>package appdb

import (
	"database/sql"
	"encoding/json"
	"sort"
	"time"

	"golang.org/x/net/context"

	"chain/database/pg"
	"chain/errors"
	"chain/fedchain-sandbox/wire"
	"chain/metrics"
	"chain/strings"
)

type activityItem struct {
	walletID  string
	txid      string
	data      []byte
	bucketIDs []string
}

// CreateActivityItems writes new activity items given a tx.
// Must be called inside a db transaction, but after the utxos are
// updated for this tx.
func CreateActivityItems(ctx context.Context, tx *wire.MsgTx, txTime time.Time) error {
	defer metrics.RecordElapsed(time.Now())
	_ = pg.FromContext(ctx).(pg.Tx) // panics if not in a db transaction
	wids, addrIsChange, err := getWalletsAndChangeInTx(ctx, tx)
	if err != nil {
		return errors.Wrap(err, "getting wallets in tx")
	}

	for _, w := range wids {
		item, err := generateActivityItem(ctx, tx, w, addrIsChange, txTime)
		if err != nil {
			msg := "generating item for wallet " + w
			return errors.Wrap(err, msg)
		}
		err = writeActivityItem(ctx, item)
		if err != nil {
			msg := "generating item for wallet " + w
			return errors.Wrap(err, msg)
		}
	}

	return nil
}

// getWalletsAndChangeInTx takes a transaction and returns two things:
// a unique list of all the wallet ids associated with that transaction, and
// a map, address ids as keys, and values specifying if they're change addresses or not.
func getWalletsAndChangeInTx(ctx context.Context, tx *wire.MsgTx) ([]string, map[string]bool, error) {
	const q = `SELECT wallet_id, address_id FROM utxos WHERE txid=$1`

	var txids []string
	for _, i := range tx.TxIn {
		txids = append(txids, i.PreviousOutPoint.Hash.String())
	}
	txids = append(txids, tx.TxSha().String())

	var addrs []string
	var wallets []string
	for _, tid := range txids {
		rows, err := pg.FromContext(ctx).Query(q, tid)
		if err != nil {
			return nil, nil, errors.Wrap(err, "query")
		}
		defer rows.Close()

		for rows.Next() {
			var w, addr string
			err = rows.Scan(&w, &addr)
			wallets = append(wallets, w)
			addrs = append(addrs, addr)
		}
		if rows.Err() != nil {
			return nil, nil, errors.Wrap(rows.Err(), "rows")
		}
		rows.Close()
	}

	sort.Strings(wallets)
	wallets = strings.Uniq(wallets)

	const changeQ = `SELECT is_change FROM addresses WHERE id=$1`

	addrIsChange := make(map[string]bool)
	for _, a := range addrs {
		var isChange bool
		err := pg.FromContext(ctx).QueryRow(changeQ, a).Scan(&isChange)
		if err != nil {
			return nil, nil, errors.Wrap(err, "change row scan")
		}

		if isChange {
			addrIsChange[a] = true
		}
	}

	return wallets, addrIsChange, nil
}

func generateActivityItem(ctx context.Context, tx *wire.MsgTx, walletID string, addrIsChange map[string]bool, txTime time.Time) (*activityItem, error) {
	var (
		insByBucket  = make(map[string]map[string]int64)
		insByAddr    = make(map[string]map[string]int64)
		outsByBucket = make(map[string]map[string]int64)
		outsByAddr   = make(map[string]map[string]int64)
		buckets      []string
	)

	var (
		wid   string
		bid   string
		addr  string
		asset string
		amt   int64
	)

	const inputQ = `
		SELECT wallet_id, bucket_id, address_id, asset_id, amount
		FROM utxos
		WHERE txid=$1 AND index=$2
	`

	// Pool all inputs by bucket or address.
	for _, in := range tx.TxIn {
		prev := in.PreviousOutPoint
		rows, err := pg.FromContext(ctx).Query(inputQ, prev.Hash.String(), prev.Index)
		if err != nil {
			return nil, errors.Wrap(err, "querying inputs")
		}
		defer rows.Close()

		for rows.Next() {
			err = rows.Scan(&wid, &bid, &addr, &asset, &amt)
			if err != nil {
				return nil, errors.Wrap(err, "row scan")
			}

			// We only want to track which bucket this came from
			// if this input is part of this wallet. Otherwise,
			// we only track the address.
			if wid == walletID {
				if insByBucket[bid] == nil {
					insByBucket[bid] = make(map[string]int64)
					buckets = append(buckets, bid)
				}

				insByBucket[bid][asset] += amt
			} else {
				if insByAddr[addr] == nil {
					insByAddr[addr] = make(map[string]int64)
				}

				insByAddr[addr][asset] += amt
			}
		}

		if rows.Err() != nil {
			return nil, errors.Wrap(rows.Err(), "input tx rows")
		}

		rows.Close()
	}

	const outputQ = `
		SELECT wallet_id, bucket_id, address_id, asset_id, amount
		FROM utxos
		WHERE txid=$1
	`

	// Pool all outputs by bucket or address.
	hash := tx.TxSha().String()
	rows, err := pg.FromContext(ctx).Query(outputQ, hash)
	if err != nil {
		return nil, errors.Wrap(err, "query output utxos")
	}
	defer rows.Close()

	// Iterate through all outputs:
	for rows.Next() {
		err = rows.Scan(&wid, &bid, &addr, &asset, &amt)
		if err != nil {
			return nil, errors.Wrap(err, "row scan outputs")
		}

		// We only want to track which bucket
		// this came from if this output is part of this wallet.
		// Otherwise, we only track the address.
		if wid == walletID {

			// If this output is part of this wallet, it might
			// be a change address.
			// We only show the net effect on a bucket,
			// so if this address is a change address, we
			// do two things:
			//
			// 1) Don't add it to the list of outputs.
			// 2) Subtract it from inputs.
			if addrIsChange[addr] {
				if insByBucket[bid] != nil {
					insByBucket[bid][asset] -= amt
				} else {
					// If this addr is a change addr,
					// there should be an input that could
					// create said change.
					return nil, errors.New("change addr without corresponding input")
				}
			} else {
				if outsByBucket[bid] == nil {
					outsByBucket[bid] = make(map[string]int64)
					buckets = append(buckets, bid)
				}

				outsByBucket[bid][asset] += amt
			}
		} else {
			if outsByAddr[addr] == nil {
				outsByAddr[addr] = make(map[string]int64)
			}

			outsByAddr[addr][asset] += amt
		}
	}

	sort.Strings(buckets)
	buckets = strings.Uniq(buckets)

	// Create Activity Item data blob. The Activity Item
	// blobs are json objects that look something like this:
	//
	// {
	//   "txid": txid,
	//   "inputs": [
	//     {
	//       "asset_id": aid,
	//       "amount": 10,
	//       "bucket_id": bid
	//     },
	//     {
	//       "asset_id": aid2,
	//       "amount": 10,
	//       "address": addr1
	//     }
	//   ],
	//   "outputs": [ same format as inputs ]
	// }
	//
	// These data blobs are stored in a json field on the activity table,
	// along with the wallet_id and tx_id.

	var (
		inputsBlob  []map[string]interface{}
		outputsBlob []map[string]interface{}
	)

	// TODO(tess): Make sure that insByAddr, insByBucket, outsByAddr, and
	// outsByBucket are traversed in order. htnote solves this problem
	// like this: https://github.com/chain-engineering/htnote/blob/master/payload.go#L183
	for addr, addrMap := range insByAddr {
		for asset, amt := range addrMap {
			i := make(map[string]interface{})
			i["asset_id"] = asset
			i["amount"] = amt
			i["address"] = addr

			inputsBlob = append(inputsBlob, i)
		}
	}
	for bckt, bcktMap := range insByBucket {
		for asset, amt := range bcktMap {
			i := make(map[string]interface{})
			i["asset_id"] = asset
			i["amount"] = amt
			i["bucket_id"] = bckt

			inputsBlob = append(inputsBlob, i)
		}
	}
	for addr, addrMap := range outsByAddr {
		for asset, amt := range addrMap {
			o := make(map[string]interface{})
			o["asset_id"] = asset
			o["amount"] = amt
			o["address"] = addr

			outputsBlob = append(outputsBlob, o)
		}
	}
	for bckt, bcktMap := range outsByBucket {
		for asset, amt := range bcktMap {
			o := make(map[string]interface{})
			o["asset_id"] = asset
			o["amount"] = amt
			o["bucket_id"] = bckt

			outputsBlob = append(outputsBlob, o)
		}
	}

	data := make(map[string]interface{})
	data["inputs"] = inputsBlob
	data["outputs"] = outputsBlob
	data["txid"] = hash
	data["transaction_time"] = txTime.UTC()

	blob, err := json.Marshal(data)
	if err != nil {
		return nil, errors.Wrap(err, "json marshalling data")
	}

	item := &activityItem{
		walletID:  walletID,
		txid:      hash,
		data:      blob,
		bucketIDs: buckets,
	}

	return item, nil
}

func writeActivityItem(ctx context.Context, item *activityItem) error {
	// Now insert the data blob, along with the other tx information.
	const insertQ = `
		INSERT INTO activity (wallet_id, data, txid)
		VALUES ($1, $2, $3)
		RETURNING id
	`

	var actID string
	err := pg.FromContext(ctx).QueryRow(insertQ, item.walletID, item.data, item.txid).Scan(&actID)
	if err != nil {
		return errors.Wrap(err, "inserting activity item")
	}

	const insertBucketQ = `
		INSERT INTO activity_buckets (activity_id, bucket_id) VALUES ($1, $2)
	`

	for _, bid := range item.bucketIDs {
		_, err := pg.FromContext(ctx).Exec(insertBucketQ, actID, bid)
		if err != nil {
			return errors.Wrap(err, "inserting activity bucket item")
		}
	}

	return nil
}

func WalletActivity(ctx context.Context, walletID string, prev string, limit int) ([]*json.RawMessage, string, error) {
	q := `
		SELECT id, data FROM activity
		WHERE wallet_id=$1 AND (($2 = '') OR (id < $2))
		ORDER BY id DESC LIMIT $3
	`

	rows, err := pg.FromContext(ctx).Query(q, walletID, prev, limit)
	if err != nil {
		return nil, "", errors.Wrap(err, "query")
	}
	defer rows.Close()

	return activityItemsFromRows(rows)
}

func BucketActivity(ctx context.Context, bucketID string, prev string, limit int) ([]*json.RawMessage, string, error) {
	q := `
		SELECT a.id, a.data
		FROM activity AS a
		LEFT JOIN activity_buckets AS ab
		ON a.id=ab.activity_id
		WHERE ab.bucket_id=$1 AND (($2 = '') OR (a.id < $2))
		ORDER BY a.id DESC LIMIT $3
	`

	rows, err := pg.FromContext(ctx).Query(q, bucketID, prev, limit)
	if err != nil {
		return nil, "", errors.Wrap(err, "query")
	}
	defer rows.Close()

	return activityItemsFromRows(rows)
}

func activityItemsFromRows(rows *sql.Rows) (items []*json.RawMessage, last string, err error) {
	for rows.Next() {
		var a []byte
		err := rows.Scan(&last, &a)
		if err != nil {
			err = errors.Wrap(err, "row scan")
			return nil, "", err
		}

		items = append(items, (*json.RawMessage)(&a))
	}

	if rows.Err() != nil {
		err = errors.Wrap(rows.Err(), "rows")
		return nil, "", err
	}

	return items, last, nil
}

func WalletTxActivity(ctx context.Context, walletID, txID string) (*json.RawMessage, error) {
	q := `
		SELECT data FROM activity
		WHERE wallet_id=$1 AND txid=$2
	`

	var a []byte
	err := pg.FromContext(ctx).QueryRow(q, walletID, txID).Scan(&a)
	if err == sql.ErrNoRows {
		return nil, errors.WithDetailf(pg.ErrUserInputNotFound, "transaction id: %v", txID)
	}
	return (*json.RawMessage)(&a), err
}
<commit_msg>add timers to the functions in CreateActivityItem<commit_after>package appdb

import (
	"database/sql"
	"encoding/json"
	"sort"
	"time"

	"golang.org/x/net/context"

	"chain/database/pg"
	"chain/errors"
	"chain/fedchain-sandbox/wire"
	"chain/metrics"
	"chain/strings"
)

type activityItem struct {
	walletID  string
	txid      string
	data      []byte
	bucketIDs []string
}

// CreateActivityItems writes new activity items given a tx.
// Must be called inside a db transaction, but after the utxos are
// updated for this tx.
func CreateActivityItems(ctx context.Context, tx *wire.MsgTx, txTime time.Time) error {
	defer metrics.RecordElapsed(time.Now())
	_ = pg.FromContext(ctx).(pg.Tx) // panics if not in a db transaction
	wids, addrIsChange, err := getWalletsAndChangeInTx(ctx, tx)
	if err != nil {
		return errors.Wrap(err, "getting wallets in tx")
	}

	for _, w := range wids {
		item, err := generateActivityItem(ctx, tx, w, addrIsChange, txTime)
		if err != nil {
			msg := "generating item for wallet " + w
			return errors.Wrap(err, msg)
		}
		err = writeActivityItem(ctx, item)
		if err != nil {
			msg := "generating item for wallet " + w
			return errors.Wrap(err, msg)
		}
	}

	return nil
}

// getWalletsAndChangeInTx takes a transaction and returns two things:
// a unique list of all the wallet ids associated with that transaction, and
// a map, address ids as keys, and values specifying if they're change addresses or not.
func getWalletsAndChangeInTx(ctx context.Context, tx *wire.MsgTx) ([]string, map[string]bool, error) {
	defer metrics.RecordElapsed(time.Now())
	const q = `SELECT wallet_id, address_id FROM utxos WHERE txid=$1`

	var txids []string
	for _, i := range tx.TxIn {
		txids = append(txids, i.PreviousOutPoint.Hash.String())
	}
	txids = append(txids, tx.TxSha().String())

	var addrs []string
	var wallets []string
	for _, tid := range txids {
		rows, err := pg.FromContext(ctx).Query(q, tid)
		if err != nil {
			return nil, nil, errors.Wrap(err, "query")
		}
		defer rows.Close()

		for rows.Next() {
			var w, addr string
			err = rows.Scan(&w, &addr)
			wallets = append(wallets, w)
			addrs = append(addrs, addr)
		}
		if rows.Err() != nil {
			return nil, nil, errors.Wrap(rows.Err(), "rows")
		}
		rows.Close()
	}

	sort.Strings(wallets)
	wallets = strings.Uniq(wallets)

	const changeQ = `SELECT is_change FROM addresses WHERE id=$1`

	addrIsChange := make(map[string]bool)
	for _, a := range addrs {
		var isChange bool
		err := pg.FromContext(ctx).QueryRow(changeQ, a).Scan(&isChange)
		if err != nil {
			return nil, nil, errors.Wrap(err, "change row scan")
		}

		if isChange {
			addrIsChange[a] = true
		}
	}

	return wallets, addrIsChange, nil
}

func generateActivityItem(ctx context.Context, tx *wire.MsgTx, walletID string, addrIsChange map[string]bool, txTime time.Time) (*activityItem, error) {
	defer metrics.RecordElapsed(time.Now())
	var (
		insByBucket  = make(map[string]map[string]int64)
		insByAddr    = make(map[string]map[string]int64)
		outsByBucket = make(map[string]map[string]int64)
		outsByAddr   = make(map[string]map[string]int64)
		buckets      []string
	)

	var (
		wid   string
		bid   string
		addr  string
		asset string
		amt   int64
	)

	const inputQ = `
		SELECT wallet_id, bucket_id, address_id, asset_id, amount
		FROM utxos
		WHERE txid=$1 AND index=$2
	`

	// Pool all inputs by bucket or address.
	for _, in := range tx.TxIn {
		prev := in.PreviousOutPoint
		rows, err := pg.FromContext(ctx).Query(inputQ, prev.Hash.String(), prev.Index)
		if err != nil {
			return nil, errors.Wrap(err, "querying inputs")
		}
		defer rows.Close()

		for rows.Next() {
			err = rows.Scan(&wid, &bid, &addr, &asset, &amt)
			if err != nil {
				return nil, errors.Wrap(err, "row scan")
			}

			// We only want to track which bucket this came from
			// if this input is part of this wallet. Otherwise,
			// we only track the address.
			if wid == walletID {
				if insByBucket[bid] == nil {
					insByBucket[bid] = make(map[string]int64)
					buckets = append(buckets, bid)
				}

				insByBucket[bid][asset] += amt
			} else {
				if insByAddr[addr] == nil {
					insByAddr[addr] = make(map[string]int64)
				}

				insByAddr[addr][asset] += amt
			}
		}

		if rows.Err() != nil {
			return nil, errors.Wrap(rows.Err(), "input tx rows")
		}

		rows.Close()
	}

	const outputQ = `
		SELECT wallet_id, bucket_id, address_id, asset_id, amount
		FROM utxos
		WHERE txid=$1
	`

	// Pool all outputs by bucket or address.
	hash := tx.TxSha().String()
	rows, err := pg.FromContext(ctx).Query(outputQ, hash)
	if err != nil {
		return nil, errors.Wrap(err, "query output utxos")
	}
	defer rows.Close()

	// Iterate through all outputs:
	for rows.Next() {
		err = rows.Scan(&wid, &bid, &addr, &asset, &amt)
		if err != nil {
			return nil, errors.Wrap(err, "row scan outputs")
		}

		// We only want to track which bucket
		// this came from if this output is part of this wallet.
		// Otherwise, we only track the address.
		if wid == walletID {

			// If this output is part of this wallet, it might
			// be a change address.
			// We only show the net effect on a bucket,
			// so if this address is a change address, we
			// do two things:
			//
			// 1) Don't add it to the list of outputs.
			// 2) Subtract it from inputs.
			if addrIsChange[addr] {
				if insByBucket[bid] != nil {
					insByBucket[bid][asset] -= amt
				} else {
					// If this addr is a change addr,
					// there should be an input that could
					// create said change.
					return nil, errors.New("change addr without corresponding input")
				}
			} else {
				if outsByBucket[bid] == nil {
					outsByBucket[bid] = make(map[string]int64)
					buckets = append(buckets, bid)
				}

				outsByBucket[bid][asset] += amt
			}
		} else {
			if outsByAddr[addr] == nil {
				outsByAddr[addr] = make(map[string]int64)
			}

			outsByAddr[addr][asset] += amt
		}
	}

	sort.Strings(buckets)
	buckets = strings.Uniq(buckets)

	// Create Activity Item data blob. The Activity Item
	// blobs are json objects that look something like this:
	//
	// {
	//   "txid": txid,
	//   "inputs": [
	//     {
	//       "asset_id": aid,
	//       "amount": 10,
	//       "bucket_id": bid
	//     },
	//     {
	//       "asset_id": aid2,
	//       "amount": 10,
	//       "address": addr1
	//     }
	//   ],
	//   "outputs": [ same format as inputs ]
	// }
	//
	// These data blobs are stored in a json field on the activity table,
	// along with the wallet_id and tx_id.

	var (
		inputsBlob  []map[string]interface{}
		outputsBlob []map[string]interface{}
	)

	// TODO(tess): Make sure that insByAddr, insByBucket, outsByAddr, and
	// outsByBucket are traversed in order. htnote solves this problem
	// like this: https://github.com/chain-engineering/htnote/blob/master/payload.go#L183
	for addr, addrMap := range insByAddr {
		for asset, amt := range addrMap {
			i := make(map[string]interface{})
			i["asset_id"] = asset
			i["amount"] = amt
			i["address"] = addr

			inputsBlob = append(inputsBlob, i)
		}
	}
	for bckt, bcktMap := range insByBucket {
		for asset, amt := range bcktMap {
			i := make(map[string]interface{})
			i["asset_id"] = asset
			i["amount"] = amt
			i["bucket_id"] = bckt

			inputsBlob = append(inputsBlob, i)
		}
	}
	for addr, addrMap := range outsByAddr {
		for asset, amt := range addrMap {
			o := make(map[string]interface{})
			o["asset_id"] = asset
			o["amount"] = amt
			o["address"] = addr

			outputsBlob = append(outputsBlob, o)
		}
	}
	for bckt, bcktMap := range outsByBucket {
		for asset, amt := range bcktMap {
			o := make(map[string]interface{})
			o["asset_id"] = asset
			o["amount"] = amt
			o["bucket_id"] = bckt

			outputsBlob = append(outputsBlob, o)
		}
	}

	data := make(map[string]interface{})
	data["inputs"] = inputsBlob
	data["outputs"] = outputsBlob
	data["txid"] = hash
	data["transaction_time"] = txTime.UTC()

	blob, err := json.Marshal(data)
	if err != nil {
		return nil, errors.Wrap(err, "json marshalling data")
	}

	item := &activityItem{
		walletID:  walletID,
		txid:      hash,
		data:      blob,
		bucketIDs: buckets,
	}

	return item, nil
}

func writeActivityItem(ctx context.Context, item *activityItem) error {
	defer metrics.RecordElapsed(time.Now())
	// Now insert the data blob, along with the other tx information.
	const insertQ = `
		INSERT INTO activity (wallet_id, data, txid)
		VALUES ($1, $2, $3)
		RETURNING id
	`

	var actID string
	err := pg.FromContext(ctx).QueryRow(insertQ, item.walletID, item.data, item.txid).Scan(&actID)
	if err != nil {
		return errors.Wrap(err, "inserting activity item")
	}

	const insertBucketQ = `
		INSERT INTO activity_buckets (activity_id, bucket_id) VALUES ($1, $2)
	`

	for _, bid := range item.bucketIDs {
		_, err := pg.FromContext(ctx).Exec(insertBucketQ, actID, bid)
		if err != nil {
			return errors.Wrap(err, "inserting activity bucket item")
		}
	}

	return nil
}

func WalletActivity(ctx context.Context, walletID string, prev string, limit int) ([]*json.RawMessage, string, error) {
	q := `
		SELECT id, data FROM activity
		WHERE wallet_id=$1 AND (($2 = '') OR (id < $2))
		ORDER BY id DESC LIMIT $3
	`

	rows, err := pg.FromContext(ctx).Query(q, walletID, prev, limit)
	if err != nil {
		return nil, "", errors.Wrap(err, "query")
	}
	defer rows.Close()

	return activityItemsFromRows(rows)
}

func BucketActivity(ctx context.Context, bucketID string, prev string, limit int) ([]*json.RawMessage, string, error) {
	q := `
		SELECT a.id, a.data
		FROM activity AS a
		LEFT JOIN activity_buckets AS ab
		ON a.id=ab.activity_id
		WHERE ab.bucket_id=$1 AND (($2 = '') OR (a.id < $2))
		ORDER BY a.id DESC LIMIT $3
	`

	rows, err := pg.FromContext(ctx).Query(q, bucketID, prev, limit)
	if err != nil {
		return nil, "", errors.Wrap(err, "query")
	}
	defer rows.Close()

	return activityItemsFromRows(rows)
}

func activityItemsFromRows(rows *sql.Rows) (items []*json.RawMessage, last string, err error) {
	for rows.Next() {
		var a []byte
		err := rows.Scan(&last, &a)
		if err != nil {
			err = errors.Wrap(err, "row scan")
			return nil, "", err
		}

		items = append(items, (*json.RawMessage)(&a))
	}

	if rows.Err() != nil {
		err = errors.Wrap(rows.Err(), "rows")
		return nil, "", err
	}

	return items, last, nil
}

func WalletTxActivity(ctx context.Context, walletID, txID string) (*json.RawMessage, error) {
	q := `
		SELECT data FROM activity
		WHERE wallet_id=$1 AND txid=$2
	`

	var a []byte
	err := pg.FromContext(ctx).QueryRow(q, walletID, txID).Scan(&a)
	if err == sql.ErrNoRows {
		return nil, errors.WithDetailf(pg.ErrUserInputNotFound, "transaction id: %v", txID)
	}
	return (*json.RawMessage)(&a), err
}
<|endoftext|>


<commit_before>package api

import (
	"gopkg.in/fatih/set.v0"

	"github.com/bytom/bytom/blockchain/query"
	chainjson "github.com/bytom/bytom/encoding/json"
	"github.com/bytom/bytom/protocol/bc"
	"github.com/bytom/bytom/protocol/bc/types"
)

// return best block hash
func (a *API) getBestBlockHash() Response {
	blockHash := map[string]string{"block_hash": a.chain.BestBlockHash().String()}
	return NewSuccessResponse(blockHash)
}

// return current block count
func (a *API) getBlockCount() Response {
	blockHeight := map[string]uint64{"block_count": a.chain.BestBlockHeight()}
	return NewSuccessResponse(blockHeight)
}

// BlockTx is the tx struct for getBlock func
type BlockTx struct {
	ID        bc.Hash                  `json:"id"`
	Version   uint64                   `json:"version"`
	Size      uint64                   `json:"size"`
	TimeRange uint64                   `json:"time_range"`
	Inputs    []*query.AnnotatedInput  `json:"inputs"`
	Outputs   []*query.AnnotatedOutput `json:"outputs"`
	MuxID     bc.Hash                  `json:"mux_id"`
}

// BlockReq is used to handle getBlock req
type BlockReq struct {
	BlockHeight uint64             `json:"block_height"`
	BlockHash   chainjson.HexBytes `json:"block_hash"`
}

// GetBlockResp is the resp for getBlock api
type GetBlockResp struct {
	Hash                   *bc.Hash   `json:"hash"`
	Size                   uint64     `json:"size"`
	Version                uint64     `json:"version"`
	Height                 uint64     `json:"height"`
	Validator              string     `json:"validator"`
	PreviousBlockHash      *bc.Hash   `json:"previous_block_hash"`
	Timestamp              uint64     `json:"timestamp"`
	TransactionsMerkleRoot *bc.Hash   `json:"transaction_merkle_root"`
	Transactions           []*BlockTx `json:"transactions"`
}

// return block by hash/height
func (a *API) getBlock(ins BlockReq) Response {
	block, err := a.getBlockHelper(ins)
	if err != nil {
		return NewErrorResponse(err)
	}

	blockHash := block.Hash()
	rawBlock, err := block.MarshalText()
	if err != nil {
		return NewErrorResponse(err)
	}

	var validatorPubKey string
	if block.Height > 0 {
		validator, err := a.chain.GetValidator(&block.PreviousBlockHash, block.Timestamp)
		if err != nil {
			return NewErrorResponse(err)
		}

		validatorPubKey = validator.PubKey
	}

	resp := &GetBlockResp{
		Hash:                   &blockHash,
		Size:                   uint64(len(rawBlock)),
		Version:                block.Version,
		Height:                 block.Height,
		Validator:              validatorPubKey,
		PreviousBlockHash:      &block.PreviousBlockHash,
		Timestamp:              block.Timestamp,
		TransactionsMerkleRoot: &block.TransactionsMerkleRoot,
		Transactions:           []*BlockTx{},
	}

	for _, orig := range block.Transactions {
		tx := &BlockTx{
			ID:        orig.ID,
			Version:   orig.Version,
			Size:      orig.SerializedSize,
			TimeRange: orig.TimeRange,
			Inputs:    []*query.AnnotatedInput{},
			Outputs:   []*query.AnnotatedOutput{},
		}

		resOutID := orig.ResultIds[0]
		resOut, ok := orig.Entries[*resOutID].(*bc.OriginalOutput)
		if ok {
			tx.MuxID = *resOut.Source.Ref
		} else {
			resRetire, _ := orig.Entries[*resOutID].(*bc.Retirement)
			tx.MuxID = *resRetire.Source.Ref
		}

		for i := range orig.Inputs {
			tx.Inputs = append(tx.Inputs, a.wallet.BuildAnnotatedInput(orig, uint32(i)))
		}
		for i := range orig.Outputs {
			tx.Outputs = append(tx.Outputs, a.wallet.BuildAnnotatedOutput(orig, i))
		}
		resp.Transactions = append(resp.Transactions, tx)
	}
	return NewSuccessResponse(resp)
}

// GetRawBlockResp is resp struct for getRawBlock API
type GetRawBlockResp struct {
	RawBlock  *types.Block `json:"raw_block"`
	Validator string       `json:"validator"`
}

func (a *API) getRawBlock(ins BlockReq) Response {
	block, err := a.getBlockHelper(ins)
	if err != nil {
		return NewErrorResponse(err)
	}

	var validatorPubKey string
	if block.Height > 0 {
		validator, err := a.chain.GetValidator(&block.PreviousBlockHash, block.Timestamp)
		if err != nil {
			return NewErrorResponse(err)
		}

		validatorPubKey = validator.PubKey
	}

	resp := GetRawBlockResp{
		RawBlock:  block,
		Validator: validatorPubKey,
	}
	return NewSuccessResponse(resp)
}

// GetBlockHeaderResp is resp struct for getBlockHeader API
type GetBlockHeaderResp struct {
	BlockHeader *types.BlockHeader `json:"block_header"`
}

func (a *API) getBlockHeader(ins BlockReq) Response {
	block, err := a.getBlockHelper(ins)
	if err != nil {
		return NewErrorResponse(err)
	}

	resp := &GetBlockHeaderResp{
		BlockHeader: &block.BlockHeader,
	}
	return NewSuccessResponse(resp)
}

func (a *API) getBlockHelper(ins BlockReq) (*types.Block, error) {
	if len(ins.BlockHash) == 32 {
		hash := hexBytesToHash(ins.BlockHash)
		return a.chain.GetBlockByHash(&hash)
	} else {
		return a.chain.GetBlockByHeight(ins.BlockHeight)
	}
}

func hexBytesToHash(hexBytes chainjson.HexBytes) bc.Hash {
	b32 := [32]byte{}
	copy(b32[:], hexBytes)
	return bc.NewHash(b32)
}

// MerkleBlockReq is used to handle getTxOutProof req
type MerkleBlockReq struct {
	TxIDs     []chainjson.HexBytes `json:"tx_ids"`
	BlockHash chainjson.HexBytes   `json:"block_hash"`
}

// GetMerkleBlockResp is resp struct for GetTxOutProof API
type GetMerkleBlockResp struct {
	BlockHeader  types.BlockHeader `json:"block_header"`
	TxHashes     []*bc.Hash        `json:"tx_hashes"`
	Flags        []uint32          `json:"flags"`
	MatchedTxIDs []*bc.Hash        `json:"matched_tx_ids"`
}

func (a *API) getMerkleProof(ins MerkleBlockReq) Response {
	blockReq := BlockReq{BlockHash: ins.BlockHash}
	block, err := a.getBlockHelper(blockReq)
	if err != nil {
		return NewErrorResponse(err)
	}

	matchedTxs := getMatchedTx(block.Transactions, ins.TxIDs)
	var matchedTxIDs []*bc.Hash
	for _, tx := range matchedTxs {
		matchedTxIDs = append(matchedTxIDs, &tx.ID)
	}

	hashes, compactFlags := types.GetTxMerkleTreeProof(block.Transactions, matchedTxs)
	flags := make([]uint32, len(compactFlags))
	for i, flag := range compactFlags {
		flags[i] = uint32(flag)
	}

	resp := &GetMerkleBlockResp{
		BlockHeader:  block.BlockHeader,
		TxHashes:     hashes,
		Flags:        flags,
		MatchedTxIDs: matchedTxIDs,
	}
	return NewSuccessResponse(resp)
}

func getMatchedTx(txs []*types.Tx, filterTxIDs []chainjson.HexBytes) []*types.Tx {
	txIDSet := set.New()
	for _, txID := range filterTxIDs {
		hash := hexBytesToHash(txID)
		txIDSet.Add(hash.String())
	}

	var matchedTxs []*types.Tx
	for _, tx := range txs {
		hashStr := tx.ID.String()
		if txIDSet.Has(hashStr) {
			matchedTxs = append(matchedTxs, tx)
		}
	}
	return matchedTxs
}
<commit_msg>Add vote output in get block (#2082)<commit_after>package api

import (
	"gopkg.in/fatih/set.v0"

	"github.com/bytom/bytom/blockchain/query"
	chainjson "github.com/bytom/bytom/encoding/json"
	"github.com/bytom/bytom/protocol/bc"
	"github.com/bytom/bytom/protocol/bc/types"
)

// return best block hash
func (a *API) getBestBlockHash() Response {
	blockHash := map[string]string{"block_hash": a.chain.BestBlockHash().String()}
	return NewSuccessResponse(blockHash)
}

// return current block count
func (a *API) getBlockCount() Response {
	blockHeight := map[string]uint64{"block_count": a.chain.BestBlockHeight()}
	return NewSuccessResponse(blockHeight)
}

// BlockTx is the tx struct for getBlock func
type BlockTx struct {
	ID        bc.Hash                  `json:"id"`
	Version   uint64                   `json:"version"`
	Size      uint64                   `json:"size"`
	TimeRange uint64                   `json:"time_range"`
	Inputs    []*query.AnnotatedInput  `json:"inputs"`
	Outputs   []*query.AnnotatedOutput `json:"outputs"`
	MuxID     bc.Hash                  `json:"mux_id"`
}

// BlockReq is used to handle getBlock req
type BlockReq struct {
	BlockHeight uint64             `json:"block_height"`
	BlockHash   chainjson.HexBytes `json:"block_hash"`
}

// GetBlockResp is the resp for getBlock api
type GetBlockResp struct {
	Hash                   *bc.Hash   `json:"hash"`
	Size                   uint64     `json:"size"`
	Version                uint64     `json:"version"`
	Height                 uint64     `json:"height"`
	Validator              string     `json:"validator"`
	PreviousBlockHash      *bc.Hash   `json:"previous_block_hash"`
	Timestamp              uint64     `json:"timestamp"`
	TransactionsMerkleRoot *bc.Hash   `json:"transaction_merkle_root"`
	Transactions           []*BlockTx `json:"transactions"`
}

// return block by hash/height
func (a *API) getBlock(ins BlockReq) Response {
	block, err := a.getBlockHelper(ins)
	if err != nil {
		return NewErrorResponse(err)
	}

	blockHash := block.Hash()
	rawBlock, err := block.MarshalText()
	if err != nil {
		return NewErrorResponse(err)
	}

	var validatorPubKey string
	if block.Height > 0 {
		validator, err := a.chain.GetValidator(&block.PreviousBlockHash, block.Timestamp)
		if err != nil {
			return NewErrorResponse(err)
		}

		validatorPubKey = validator.PubKey
	}

	resp := &GetBlockResp{
		Hash:                   &blockHash,
		Size:                   uint64(len(rawBlock)),
		Version:                block.Version,
		Height:                 block.Height,
		Validator:              validatorPubKey,
		PreviousBlockHash:      &block.PreviousBlockHash,
		Timestamp:              block.Timestamp,
		TransactionsMerkleRoot: &block.TransactionsMerkleRoot,
		Transactions:           []*BlockTx{},
	}

	for _, orig := range block.Transactions {
		tx := &BlockTx{
			ID:        orig.ID,
			Version:   orig.Version,
			Size:      orig.SerializedSize,
			TimeRange: orig.TimeRange,
			Inputs:    []*query.AnnotatedInput{},
			Outputs:   []*query.AnnotatedOutput{},
		}

		resOutID := orig.ResultIds[0]
		resOut := orig.Entries[*resOutID]
		switch out :=resOut.(type) {
		case *bc.OriginalOutput:
			tx.MuxID = *out.Source.Ref
		case *bc.VoteOutput:
			tx.MuxID = *out.Source.Ref
		case *bc.Retirement:
			tx.MuxID = *out.Source.Ref
		}

		for i := range orig.Inputs {
			tx.Inputs = append(tx.Inputs, a.wallet.BuildAnnotatedInput(orig, uint32(i)))
		}
		for i := range orig.Outputs {
			tx.Outputs = append(tx.Outputs, a.wallet.BuildAnnotatedOutput(orig, i))
		}
		resp.Transactions = append(resp.Transactions, tx)
	}
	return NewSuccessResponse(resp)
}

// GetRawBlockResp is resp struct for getRawBlock API
type GetRawBlockResp struct {
	RawBlock  *types.Block `json:"raw_block"`
	Validator string       `json:"validator"`
}

func (a *API) getRawBlock(ins BlockReq) Response {
	block, err := a.getBlockHelper(ins)
	if err != nil {
		return NewErrorResponse(err)
	}

	var validatorPubKey string
	if block.Height > 0 {
		validator, err := a.chain.GetValidator(&block.PreviousBlockHash, block.Timestamp)
		if err != nil {
			return NewErrorResponse(err)
		}

		validatorPubKey = validator.PubKey
	}

	resp := GetRawBlockResp{
		RawBlock:  block,
		Validator: validatorPubKey,
	}
	return NewSuccessResponse(resp)
}

// GetBlockHeaderResp is resp struct for getBlockHeader API
type GetBlockHeaderResp struct {
	BlockHeader *types.BlockHeader `json:"block_header"`
}

func (a *API) getBlockHeader(ins BlockReq) Response {
	block, err := a.getBlockHelper(ins)
	if err != nil {
		return NewErrorResponse(err)
	}

	resp := &GetBlockHeaderResp{
		BlockHeader: &block.BlockHeader,
	}
	return NewSuccessResponse(resp)
}

func (a *API) getBlockHelper(ins BlockReq) (*types.Block, error) {
	if len(ins.BlockHash) == 32 {
		hash := hexBytesToHash(ins.BlockHash)
		return a.chain.GetBlockByHash(&hash)
	} else {
		return a.chain.GetBlockByHeight(ins.BlockHeight)
	}
}

func hexBytesToHash(hexBytes chainjson.HexBytes) bc.Hash {
	b32 := [32]byte{}
	copy(b32[:], hexBytes)
	return bc.NewHash(b32)
}

// MerkleBlockReq is used to handle getTxOutProof req
type MerkleBlockReq struct {
	TxIDs     []chainjson.HexBytes `json:"tx_ids"`
	BlockHash chainjson.HexBytes   `json:"block_hash"`
}

// GetMerkleBlockResp is resp struct for GetTxOutProof API
type GetMerkleBlockResp struct {
	BlockHeader  types.BlockHeader `json:"block_header"`
	TxHashes     []*bc.Hash        `json:"tx_hashes"`
	Flags        []uint32          `json:"flags"`
	MatchedTxIDs []*bc.Hash        `json:"matched_tx_ids"`
}

func (a *API) getMerkleProof(ins MerkleBlockReq) Response {
	blockReq := BlockReq{BlockHash: ins.BlockHash}
	block, err := a.getBlockHelper(blockReq)
	if err != nil {
		return NewErrorResponse(err)
	}

	matchedTxs := getMatchedTx(block.Transactions, ins.TxIDs)
	var matchedTxIDs []*bc.Hash
	for _, tx := range matchedTxs {
		matchedTxIDs = append(matchedTxIDs, &tx.ID)
	}

	hashes, compactFlags := types.GetTxMerkleTreeProof(block.Transactions, matchedTxs)
	flags := make([]uint32, len(compactFlags))
	for i, flag := range compactFlags {
		flags[i] = uint32(flag)
	}

	resp := &GetMerkleBlockResp{
		BlockHeader:  block.BlockHeader,
		TxHashes:     hashes,
		Flags:        flags,
		MatchedTxIDs: matchedTxIDs,
	}
	return NewSuccessResponse(resp)
}

func getMatchedTx(txs []*types.Tx, filterTxIDs []chainjson.HexBytes) []*types.Tx {
	txIDSet := set.New()
	for _, txID := range filterTxIDs {
		hash := hexBytesToHash(txID)
		txIDSet.Add(hash.String())
	}

	var matchedTxs []*types.Tx
	for _, tx := range txs {
		hashStr := tx.ID.String()
		if txIDSet.Has(hashStr) {
			matchedTxs = append(matchedTxs, tx)
		}
	}
	return matchedTxs
}
<|endoftext|>


<commit_before>package zoom_test

import (
	"log"
	"os"

	"github.com/himalayan-institute/zoom-lib-golang"
)

// ExampleUser contains examples for the /user endpoints
func ExampleUser() {
	apiKey := os.Getenv("ZOOM_API_KEY")
	apiSecret := os.Getenv("ZOOM_API_SECRET")
	email := os.Getenv("ZOOM_EXAMPLE_EMAIL")

	zoom.APIKey = apiKey
	zoom.APISecret = apiSecret
	zoom.Debug = true

	users, err := zoom.ListUsers(zoom.ListUsersOptions{})
	if err != nil {
		log.Fatalf("got error listing users: %+v\n", err)
	}

	log.Printf("Users: %+v\n", users)

	client := zoom.NewClient(apiKey, apiSecret)

	user, err := client.GetUser(zoom.GetUserOpts{EmailOrID: email})
	if err != nil {
		log.Fatalf("got error listing users: %+v\n", err)
	}

	log.Printf("Got user: %+v\n", user)
}
<commit_msg>Delete unused example file<commit_after><|endoftext|>


<commit_before>package main

import (
	"flag"
	"github.com/bmizerany/pat"
	"github.com/timeredbull/tsuru/api/app"
	"github.com/timeredbull/tsuru/api/auth"
	"github.com/timeredbull/tsuru/api/service/consumption"
	"github.com/timeredbull/tsuru/api/service/provision"
	"github.com/timeredbull/tsuru/config"
	"github.com/timeredbull/tsuru/db"
	"github.com/timeredbull/tsuru/log"
	"github.com/timeredbull/tsuru/repository"
	stdlog "log"
	"log/syslog"
	"net/http"
)

func main() {
	var err error
	log.Target, err = syslog.NewLogger(syslog.LOG_INFO, stdlog.LstdFlags)
	if err != nil {
		panic(err)
	}
	configFile := flag.String("config", "/etc/tsuru/tsuru.conf", "tsuru config file")
	dry := flag.Bool("dry", false, "dry-run: does not start the server (for testing purpose)")
	flag.Parse()
	err = config.ReadConfigFile(*configFile)
	if err != nil {
		log.Panic(err.Error())
	}
	connString, err := config.GetString("database:url")
	if err != nil {
		panic(err)
	}
	dbName, err := config.GetString("database:name")
	if err != nil {
		panic(err)
	}
	db.Session, err = db.Open(connString, dbName)
	if err != nil {
		log.Panic(err.Error())
	}
	defer db.Session.Close()

	repository.RunAgent()
	m := pat.New()

	m.Get("/services/instances", AuthorizationRequiredHandler(consumption.ServicesInstancesHandler))
	m.Post("/services/instances", AuthorizationRequiredHandler(consumption.CreateInstanceHandler))
	m.Put("/services/instances/:instance/:app", AuthorizationRequiredHandler(app.BindHandler))
	m.Del("/services/instances/:instance/:app", AuthorizationRequiredHandler(app.UnbindHandler))
	m.Del("/services/c/instances/:name", AuthorizationRequiredHandler(consumption.RemoveServiceInstanceHandler))
	m.Get("/services/instances/:instance/status", AuthorizationRequiredHandler(consumption.ServiceInstanceStatusHandler))

	m.Get("/services", AuthorizationRequiredHandler(consumption.ServicesHandler))
	m.Post("/services", AuthorizationRequiredHandler(provision.CreateHandler))
	m.Put("/services", AuthorizationRequiredHandler(provision.UpdateHandler))
	m.Del("/services/:name", AuthorizationRequiredHandler(provision.DeleteHandler))
	m.Get("/services/:name", AuthorizationRequiredHandler(consumption.ServiceInfoHandler))
	m.Get("/services/c/:name/doc", AuthorizationRequiredHandler(consumption.Doc))
	m.Get("/services/:name/doc", AuthorizationRequiredHandler(provision.GetDocHandler))
	m.Put("/services/:name/doc", AuthorizationRequiredHandler(provision.AddDocHandler))
	m.Put("/services/:service/:team", AuthorizationRequiredHandler(provision.GrantAccessToTeamHandler))
	m.Del("/services/:service/:team", AuthorizationRequiredHandler(provision.RevokeAccessFromTeamHandler))

	m.Del("/apps/:name", AuthorizationRequiredHandler(app.AppDelete))
	m.Get("/apps/:name/repository/clone", Handler(app.CloneRepositoryHandler))
	m.Get("/apps/:name", AuthorizationRequiredHandler(app.AppInfo))
	m.Post("/apps/:name/run", AuthorizationRequiredHandler(app.RunCommand))
	m.Get("/apps/:name/env", AuthorizationRequiredHandler(app.GetEnv))
	m.Post("/apps/:name/env", AuthorizationRequiredHandler(app.SetEnv))
	m.Del("/apps/:name/env", AuthorizationRequiredHandler(app.UnsetEnv))
	m.Get("/apps", AuthorizationRequiredHandler(app.AppList))
	m.Post("/apps", AuthorizationRequiredHandler(app.CreateAppHandler))
	m.Put("/apps/:app/:team", AuthorizationRequiredHandler(app.GrantAccessToTeamHandler))
	m.Del("/apps/:app/:team", AuthorizationRequiredHandler(app.RevokeAccessFromTeamHandler))
	m.Get("/apps/:name/log", AuthorizationRequiredHandler(app.AppLog))

	m.Post("/users", Handler(auth.CreateUser))
	m.Post("/users/:email/tokens", Handler(auth.Login))
	m.Post("/users/keys", AuthorizationRequiredHandler(auth.AddKeyToUser))
	m.Del("/users/keys", AuthorizationRequiredHandler(auth.RemoveKeyFromUser))

	m.Get("/teams", AuthorizationRequiredHandler(auth.ListTeams))
	m.Post("/teams", AuthorizationRequiredHandler(auth.CreateTeam))
	m.Put("/teams/:team/:user", AuthorizationRequiredHandler(auth.AddUserToTeam))
	m.Del("/teams/:team/:user", AuthorizationRequiredHandler(auth.RemoveUserFromTeam))

	listen, err := config.GetString("listen")
	if err != nil {
		panic(err)
	}
	if !*dry {
		log.Fatal(http.ListenAndServe(listen, m))
	}
}
<commit_msg>api.webserver: changing pkg from ServicesHandler<commit_after>package main

import (
	"flag"
	"github.com/bmizerany/pat"
	"github.com/timeredbull/tsuru/api/app"
	"github.com/timeredbull/tsuru/api/auth"
	"github.com/timeredbull/tsuru/api/service/consumption"
	"github.com/timeredbull/tsuru/api/service/provision"
	"github.com/timeredbull/tsuru/config"
	"github.com/timeredbull/tsuru/db"
	"github.com/timeredbull/tsuru/log"
	"github.com/timeredbull/tsuru/repository"
	stdlog "log"
	"log/syslog"
	"net/http"
)

func main() {
	var err error
	log.Target, err = syslog.NewLogger(syslog.LOG_INFO, stdlog.LstdFlags)
	if err != nil {
		panic(err)
	}
	configFile := flag.String("config", "/etc/tsuru/tsuru.conf", "tsuru config file")
	dry := flag.Bool("dry", false, "dry-run: does not start the server (for testing purpose)")
	flag.Parse()
	err = config.ReadConfigFile(*configFile)
	if err != nil {
		log.Panic(err.Error())
	}
	connString, err := config.GetString("database:url")
	if err != nil {
		panic(err)
	}
	dbName, err := config.GetString("database:name")
	if err != nil {
		panic(err)
	}
	db.Session, err = db.Open(connString, dbName)
	if err != nil {
		log.Panic(err.Error())
	}
	defer db.Session.Close()

	repository.RunAgent()
	m := pat.New()

	m.Get("/services/instances", AuthorizationRequiredHandler(consumption.ServicesInstancesHandler))
	m.Post("/services/instances", AuthorizationRequiredHandler(consumption.CreateInstanceHandler))
	m.Put("/services/instances/:instance/:app", AuthorizationRequiredHandler(app.BindHandler))
	m.Del("/services/instances/:instance/:app", AuthorizationRequiredHandler(app.UnbindHandler))
	m.Del("/services/c/instances/:name", AuthorizationRequiredHandler(consumption.RemoveServiceInstanceHandler))
	m.Get("/services/instances/:instance/status", AuthorizationRequiredHandler(consumption.ServiceInstanceStatusHandler))

	m.Get("/services", AuthorizationRequiredHandler(provision.ServicesHandler))
	m.Post("/services", AuthorizationRequiredHandler(provision.CreateHandler))
	m.Put("/services", AuthorizationRequiredHandler(provision.UpdateHandler))
	m.Del("/services/:name", AuthorizationRequiredHandler(provision.DeleteHandler))
	m.Get("/services/:name", AuthorizationRequiredHandler(consumption.ServiceInfoHandler))
	m.Get("/services/c/:name/doc", AuthorizationRequiredHandler(consumption.Doc))
	m.Get("/services/:name/doc", AuthorizationRequiredHandler(provision.GetDocHandler))
	m.Put("/services/:name/doc", AuthorizationRequiredHandler(provision.AddDocHandler))
	m.Put("/services/:service/:team", AuthorizationRequiredHandler(provision.GrantAccessToTeamHandler))
	m.Del("/services/:service/:team", AuthorizationRequiredHandler(provision.RevokeAccessFromTeamHandler))

	m.Del("/apps/:name", AuthorizationRequiredHandler(app.AppDelete))
	m.Get("/apps/:name/repository/clone", Handler(app.CloneRepositoryHandler))
	m.Get("/apps/:name", AuthorizationRequiredHandler(app.AppInfo))
	m.Post("/apps/:name/run", AuthorizationRequiredHandler(app.RunCommand))
	m.Get("/apps/:name/env", AuthorizationRequiredHandler(app.GetEnv))
	m.Post("/apps/:name/env", AuthorizationRequiredHandler(app.SetEnv))
	m.Del("/apps/:name/env", AuthorizationRequiredHandler(app.UnsetEnv))
	m.Get("/apps", AuthorizationRequiredHandler(app.AppList))
	m.Post("/apps", AuthorizationRequiredHandler(app.CreateAppHandler))
	m.Put("/apps/:app/:team", AuthorizationRequiredHandler(app.GrantAccessToTeamHandler))
	m.Del("/apps/:app/:team", AuthorizationRequiredHandler(app.RevokeAccessFromTeamHandler))
	m.Get("/apps/:name/log", AuthorizationRequiredHandler(app.AppLog))

	m.Post("/users", Handler(auth.CreateUser))
	m.Post("/users/:email/tokens", Handler(auth.Login))
	m.Post("/users/keys", AuthorizationRequiredHandler(auth.AddKeyToUser))
	m.Del("/users/keys", AuthorizationRequiredHandler(auth.RemoveKeyFromUser))

	m.Get("/teams", AuthorizationRequiredHandler(auth.ListTeams))
	m.Post("/teams", AuthorizationRequiredHandler(auth.CreateTeam))
	m.Put("/teams/:team/:user", AuthorizationRequiredHandler(auth.AddUserToTeam))
	m.Del("/teams/:team/:user", AuthorizationRequiredHandler(auth.RemoveUserFromTeam))

	listen, err := config.GetString("listen")
	if err != nil {
		panic(err)
	}
	if !*dry {
		log.Fatal(http.ListenAndServe(listen, m))
	}
}
<|endoftext|>


<commit_before>/* Copyright 2014 Ooyala, Inc. All rights reserved.
 *
 * This file is licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 */

package client

import (
	atlantis "atlantis/common"
	. "atlantis/manager/constant"
)

type ManagerRPCClient struct {
	atlantis.RPCClient
	User    string
	Secrets map[string]string
}

type authedArg interface {
	SetCredentials(string, string)
}

func (r *ManagerRPCClient) CallAuthed(name string, arg authedArg, reply interface{}) error {
	arg.SetCredentials(r.User, r.Secrets[r.Opts.RPCHostAndPort()])

	return r.RPCClient.Call(name, arg, reply)
}

func NewManagerRPCClient(hostAndPort string) *atlantis.RPCClient {
	return atlantis.NewRPCClient(hostAndPort, "ManagerRPC", ManagerRPCVersion, true)
}

func NewManagerRPCClientWithConfig(cfg atlantis.RPCServerOpts) *atlantis.RPCClient {
	return atlantis.NewRPCClientWithConfig(cfg, "ManagerRPC", ManagerRPCVersion, true)
}
<commit_msg>Rpc: export AuthedArg so it can be cast to in the client.<commit_after>/* Copyright 2014 Ooyala, Inc. All rights reserved.
 *
 * This file is licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 */

package client

import (
	atlantis "atlantis/common"
	. "atlantis/manager/constant"
)

type ManagerRPCClient struct {
	atlantis.RPCClient
	User    string
	Secrets map[string]string
}

type AuthedArg interface {
	SetCredentials(string, string)
}

func (r *ManagerRPCClient) CallAuthed(name string, arg AuthedArg, reply interface{}) error {
	arg.SetCredentials(r.User, r.Secrets[r.Opts.RPCHostAndPort()])

	return r.RPCClient.Call(name, arg, reply)
}

func NewManagerRPCClient(hostAndPort string) *atlantis.RPCClient {
	return atlantis.NewRPCClient(hostAndPort, "ManagerRPC", ManagerRPCVersion, true)
}

func NewManagerRPCClientWithConfig(cfg atlantis.RPCServerOpts) *atlantis.RPCClient {
	return atlantis.NewRPCClientWithConfig(cfg, "ManagerRPC", ManagerRPCVersion, true)
}
<|endoftext|>


<commit_before>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package s3

import (
	"context"
	"io"
	"net/url"
	"os"
	"path"
	"strings"

	"github.com/webx-top/echo"
	"github.com/webx-top/echo/param"

	"github.com/admpub/nging/application/registry/upload"
	"github.com/admpub/nging/application/registry/upload/helper"
	"github.com/admpub/nging/application/registry/upload/driver/local"
	"github.com/admpub/nging/application/library/s3manager"
	"github.com/admpub/nging/application/library/s3manager/s3client"
	"github.com/admpub/nging/application/dbschema"
	"github.com/admpub/nging/application/model/file/storer"
)

const (
	Name = `s3`
	AccountIDKey = `cloudStorageAccountID`
)

var _ upload.Storer = &Filesystem{}

func init() {
	upload.StorerRegister(Name, func(ctx context.Context, typ string) upload.Storer {
		return NewFilesystem(ctx, typ)
	})
}

func NewFilesystem(ctx context.Context, typ string) *Filesystem {
	m := &dbschema.NgingCloudStorage{}
	cloudAccountID := param.AsString(ctx.Value(AccountIDKey))
	if len(cloudAccountID) == 0 || cloudAccountID == `0` {
		storerConfig, ok := storer.GetOk()
		if ok {
			cloudAccountID = storerConfig.ID
		}
	}
	if err := m.EventOFF().Get(nil, `id`, cloudAccountID); err != nil {
		panic(err)
	}
	mgr, err := s3client.New(m, 0)
	if err != nil {
		panic(err)
	}
	return &Filesystem{
		Filesystem: local.NewFilesystem(ctx, typ),
		cdn: strings.TrimSuffix(m.Baseurl, `/`),
		mgr: mgr,
	}
}

// Filesystem 文件系统存储引擎
type Filesystem struct {
	*local.Filesystem
	cdn string // CDN URL
	mgr *s3manager.S3Manager
}

// Name 引擎名
func (f *Filesystem) Name() string {
	return Name
}

// Exists 判断文件是否存在
func (f *Filesystem) Exists(file string) (bool, error) {
	return f.mgr.Exists(file)
}

// FileInfo 获取文件信息
func (f *Filesystem) FileInfo(file string) (os.FileInfo, error) {
	objectInfo, err := f.mgr.Stat(file)
	if err != nil {
		return nil, err
	}
	return s3manager.NewFileInfo(objectInfo), err
}

// SendFile 下载文件
func (f *Filesystem) SendFile(ctx echo.Context, file string) error {
	fp, err := f.mgr.Get(file)
	if err != nil {
		return err
	}
	defer fp.Close()
	fileName := path.Base(file)
	inline := true
	return ctx.Attachment(fp, fileName, inline)
}

// FileDir 物理路径文件夹
func (f *Filesystem) FileDir(subpath string) string {
	return path.Join(helper.UploadURLPath, f.Type, subpath)
}

// Put 上传文件
func (f *Filesystem) Put(dstFile string, src io.Reader, size int64) (savePath string, viewURL string, err error) {
	savePath = f.FileDir(dstFile)
	viewURL = f.PublicURL(dstFile)
	err = f.mgr.Put(src, savePath, size)
	return
}

// Get 获取文件读取接口
func (f *Filesystem) Get(dstFile string) (io.ReadCloser, error) {
	return f.mgr.Get(dstFile)
}

// Delete 删除文件
func (f *Filesystem) Delete(dstFile string) error {
	return f.mgr.Remove(dstFile)
}

// DeleteDir 删除文件夹及其内部文件
func (f *Filesystem) DeleteDir(dstDir string) error {
	return f.mgr.Remove(dstDir)
}

// Move 移动文件
func (f *Filesystem) Move(src, dst string) error {
	return f.mgr.Rename(src, dst)
}

// Close 关闭连接
func (f *Filesystem) Close() error {
	return nil
}

// PublicURL 文件物理路径转为文件网址
func (f *Filesystem) PublicURL(dstFile string) string {
	return f.cdn + f.URLDir(dstFile)
}

// URLToFile 文件网址转为文件物理路径
func (f *Filesystem) URLToFile(publicURL string) string {
	dstFile := strings.TrimPrefix(publicURL, strings.TrimRight(f.PublicURL(``), `/`)+`/`)
	return dstFile
}

// FixURL 改写文件网址
func (f *Filesystem) FixURL(content string, embedded ...bool) string {
	return content
}

// FixURLWithParams 替换文件网址为带参数的网址
func (f *Filesystem) FixURLWithParams(content string, values url.Values, embedded ...bool) string {
	if len(embedded) > 0 && embedded[0] {
		return helper.ReplaceAnyFileName(content, func(r string) string {
			return f.URLWithParams(f.PublicURL(r), values)
		})
	}
	return f.URLWithParams(f.PublicURL(content), values)
}
<commit_msg>update<commit_after>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package s3

import (
	"context"
	"io"
	"net/url"
	"os"
	"path"
	"strings"

	"github.com/webx-top/echo"
	"github.com/webx-top/echo/param"

	"github.com/admpub/nging/application/registry/upload"
	"github.com/admpub/nging/application/registry/upload/helper"
	"github.com/admpub/nging/application/registry/upload/driver/local"
	"github.com/admpub/nging/application/library/s3manager"
	"github.com/admpub/nging/application/library/s3manager/s3client"
	"github.com/admpub/nging/application/dbschema"
	"github.com/admpub/nging/application/model/file/storer"
	"github.com/admpub/nging/application/library/common"
)

const (
	Name = `s3`
	AccountIDKey = `cloudStorageAccountID`
)

var _ upload.Storer = &Filesystem{}

func init() {
	upload.StorerRegister(Name, func(ctx context.Context, typ string) upload.Storer {
		return NewFilesystem(ctx, typ)
	})
}

func NewFilesystem(ctx context.Context, typ string) *Filesystem {
	m := &dbschema.NgingCloudStorage{}
	cloudAccountID := param.AsString(ctx.Value(AccountIDKey))
	if len(cloudAccountID) == 0 || cloudAccountID == `0` {
		storerConfig, ok := storer.GetOk()
		if ok {
			cloudAccountID = storerConfig.ID
		}
	}
	if err := m.EventOFF().Get(nil, `id`, cloudAccountID); err != nil {
		panic(err)
	}
	m.Secret = common.Crypto().Decode(m.Secret)
	mgr, err := s3client.New(m, 0)
	if err != nil {
		panic(err)
	}
	return &Filesystem{
		Filesystem: local.NewFilesystem(ctx, typ),
		cdn: strings.TrimSuffix(m.Baseurl, `/`),
		mgr: mgr,
	}
}

// Filesystem 文件系统存储引擎
type Filesystem struct {
	*local.Filesystem
	cdn string // CDN URL
	mgr *s3manager.S3Manager
}

// Name 引擎名
func (f *Filesystem) Name() string {
	return Name
}

// Exists 判断文件是否存在
func (f *Filesystem) Exists(file string) (bool, error) {
	return f.mgr.Exists(file)
}

// FileInfo 获取文件信息
func (f *Filesystem) FileInfo(file string) (os.FileInfo, error) {
	objectInfo, err := f.mgr.Stat(file)
	if err != nil {
		return nil, err
	}
	return s3manager.NewFileInfo(objectInfo), err
}

// SendFile 下载文件
func (f *Filesystem) SendFile(ctx echo.Context, file string) error {
	fp, err := f.mgr.Get(file)
	if err != nil {
		return err
	}
	defer fp.Close()
	fileName := path.Base(file)
	inline := true
	return ctx.Attachment(fp, fileName, inline)
}

// FileDir 物理路径文件夹
func (f *Filesystem) FileDir(subpath string) string {
	return path.Join(helper.UploadURLPath, f.Type, subpath)
}

// Put 上传文件
func (f *Filesystem) Put(dstFile string, src io.Reader, size int64) (savePath string, viewURL string, err error) {
	savePath = f.FileDir(dstFile)
	viewURL = f.PublicURL(dstFile)
	err = f.mgr.Put(src, savePath, size)
	return
}

// Get 获取文件读取接口
func (f *Filesystem) Get(dstFile string) (io.ReadCloser, error) {
	return f.mgr.Get(dstFile)
}

// Delete 删除文件
func (f *Filesystem) Delete(dstFile string) error {
	return f.mgr.Remove(dstFile)
}

// DeleteDir 删除文件夹及其内部文件
func (f *Filesystem) DeleteDir(dstDir string) error {
	return f.mgr.Remove(dstDir)
}

// Move 移动文件
func (f *Filesystem) Move(src, dst string) error {
	return f.mgr.Rename(src, dst)
}

// Close 关闭连接
func (f *Filesystem) Close() error {
	return nil
}

// PublicURL 文件物理路径转为文件网址
func (f *Filesystem) PublicURL(dstFile string) string {
	return f.cdn + f.URLDir(dstFile)
}

// URLToFile 文件网址转为文件物理路径
func (f *Filesystem) URLToFile(publicURL string) string {
	dstFile := strings.TrimPrefix(publicURL, strings.TrimRight(f.PublicURL(``), `/`)+`/`)
	return dstFile
}

// FixURL 改写文件网址
func (f *Filesystem) FixURL(content string, embedded ...bool) string {
	return content
}

// FixURLWithParams 替换文件网址为带参数的网址
func (f *Filesystem) FixURLWithParams(content string, values url.Values, embedded ...bool) string {
	if len(embedded) > 0 && embedded[0] {
		return helper.ReplaceAnyFileName(content, func(r string) string {
			return f.URLWithParams(f.PublicURL(r), values)
		})
	}
	return f.URLWithParams(f.PublicURL(content), values)
}
<|endoftext|>


<commit_before>// Copyright © 2017 Alexander Sosna <alexander@xxor.de>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package util

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"os"
	"strings"

	log "github.com/Sirupsen/logrus"
)

func AnswerConfirmation(msg string) (confirmed bool, err error) {
	var input string
	log.Warn(msg)

	_, err = fmt.Scanln(&input)
	if err != nil {
		return false, err
	}
	positive := []string{"j", "ja", "y", "yes", "do it", "let's rock"}
	negative := []string{"nein", "n", "no", "hell no", "fuck off"}

	input = strings.ToLower(input)

	for _, element := range positive {
		if element == input {
			return true, nil
		}
	}

	for _, element := range negative {
		if element == input {
			return false, nil
		}
	}
	doesNotParse := errors.New("Answer can not be parsed: " + input)
	return false, doesNotParse
}

func MustAnswerConfirmation(msg string) (confirmed bool) {
	if confirmed, err := AnswerConfirmation(msg); err == nil {
		return confirmed
	}
	return MustAnswerConfirmation(msg)
}

func WatchOutput(input io.Reader, outputFunc func(args ...interface{})) {
	log.Debug("watchOutput started")
	scanner := bufio.NewScanner(input)
	for scanner.Scan() {
		outputFunc(scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		outputFunc("error reading input:", err)
	}
	log.Debug("watchOutput end")
}

// Exists returns whether the given file or directory exists or not
func Exists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return true, err
}

// IsEmpty returns true if the given dir is empty
func IsEmpty(path string) (bool, error) {
	f, err := os.Open(path)
	if err != nil {
		return false, err
	}
	defer f.Close()

	// Readdirnames returns at most n names, or io.EOF if not enough are available
	_, err = f.Readdirnames(1)
	if err == io.EOF {
		return true, nil
	}
	return false, err
}
<commit_msg>Test the testsuite<commit_after>// Copyright © 2017 Alexander Sosna <alexander@xxor.de>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package util

import (
	"errors"
	"fmt"
	"io"
	"os"
	"strings"

	log "github.com/Sirupsen/logrus"
)

func AnswerConfirmation(msg string) (confirmed bool, err error) {
	var input string
	log.Warn(msg)

	_, err = fmt.Scanln(&input)
	if err != nil {
		return false, err
	}
	positive := []string{"j", "ja", "y", "yes", "do it", "let's rock"}
	negative := []string{"nein", "n", "no", "hell no", "fuck off"}

	input = strings.ToLower(input)

	for _, element := range positive {
		if element == input {
			return true, nil
		}
	}

	for _, element := range negative {
		if element == input {
			return false, nil
		}
	}
	doesNotParse := errors.New("Answer can not be parsed: " + input)
	return false, doesNotParse
}

func MustAnswerConfirmation(msg string) (confirmed bool) {
	if confirmed, err := AnswerConfirmation(msg); err == nil {
		return confirmed
	}
	return MustAnswerConfirmation(msg)
}

func WatchOutput(input io.Reader, outputFunc func(args ...interface{})) {
	log.Debug("nop")
}

// Exists returns whether the given file or directory exists or not
func Exists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return true, err
}

// IsEmpty returns true if the given dir is empty
func IsEmpty(path string) (bool, error) {
	f, err := os.Open(path)
	if err != nil {
		return false, err
	}
	defer f.Close()

	// Readdirnames returns at most n names, or io.EOF if not enough are available
	_, err = f.Readdirnames(1)
	if err == io.EOF {
		return true, nil
	}
	return false, err
}
<|endoftext|>


<commit_before>// khan
// https://github.com/topfreegames/khan
//
// Licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license
// Copyright © 2016 Top Free Games <backend@tfgco.com>

package util

// VERSION identifies Khan's current version
var VERSION = "3.2.1"
<commit_msg>Updating version<commit_after>// khan
// https://github.com/topfreegames/khan
//
// Licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license
// Copyright © 2016 Top Free Games <backend@tfgco.com>

package util

// VERSION identifies Khan's current version
var VERSION = "3.3.0"
<|endoftext|>


<commit_before>// Copyright 2015 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package infoschema

import (
	"encoding/json"
	"sync/atomic"

	"github.com/juju/errors"
	"github.com/pingcap/tidb/kv"
	"github.com/pingcap/tidb/meta/autoid"
	"github.com/pingcap/tidb/model"
	"github.com/pingcap/tidb/table"
	_ "github.com/pingcap/tidb/table/tables"
)

// InfoSchema is the interface used to retrieve the schema information.
// It works as a in memory cache and doesn't handle any schema change.
// InfoSchema is read-only, and the returned value is a copy.
type InfoSchema interface {
	SchemaByName(schema model.CIStr) (*model.DBInfo, bool)
	SchemaExists(schema model.CIStr) bool
	TableByName(schema, table model.CIStr) (table.Table, error)
	TableExists(schema, table model.CIStr) bool
	ColumnByName(schema, table, column model.CIStr) (*model.ColumnInfo, bool)
	ColumnExists(schema, table, column model.CIStr) bool
	IndexByName(schema, table, index model.CIStr) (*model.IndexInfo, bool)
	SchemaByID(id int64) (*model.DBInfo, bool)
	TableByID(id int64) (table.Table, bool)
	ColumnByID(id int64) (*model.ColumnInfo, bool)
	ColumnIndices(id int64) []*model.IndexInfo
	AllSchemaNames() []string
	AllSchemas() []*model.DBInfo
	Clone() (result []*model.DBInfo)
	SchemaTables(schema model.CIStr) []table.Table
	// TODO: add more methods to retrieve tables and columns.
}

// Infomation Schema Name.
const (
	Name = "INFORMATION_SCHEMA"
)

type infoSchema struct {
	schemaNameToID map[string]int64
	tableNameToID  map[tableName]int64
	columnNameToID map[columnName]int64
	schemas        map[int64]*model.DBInfo
	tables         map[int64]table.Table
	columns        map[int64]*model.ColumnInfo
	indices        map[indexName]*model.IndexInfo
	columnIndices  map[int64][]*model.IndexInfo
}

type tableName struct {
	schema string
	table  string
}

type columnName struct {
	tableName
	name string
}

type indexName struct {
	tableName
	name string
}

func (is *infoSchema) SchemaByName(schema model.CIStr) (val *model.DBInfo, ok bool) {
	id, ok := is.schemaNameToID[schema.L]
	if !ok {
		return
	}
	val, ok = is.schemas[id]
	return
}

func (is *infoSchema) SchemaExists(schema model.CIStr) bool {
	_, ok := is.schemaNameToID[schema.L]
	return ok
}

func (is *infoSchema) TableByName(schema, table model.CIStr) (t table.Table, err error) {
	id, ok := is.tableNameToID[tableName{schema: schema.L, table: table.L}]
	if !ok {
		return nil, errors.Errorf("table %s.%s does not exist", schema, table)
	}
	t = is.tables[id]
	return
}

func (is *infoSchema) TableExists(schema, table model.CIStr) bool {
	_, ok := is.tableNameToID[tableName{schema: schema.L, table: table.L}]
	return ok
}

func (is *infoSchema) ColumnByName(schema, table, column model.CIStr) (val *model.ColumnInfo, ok bool) {
	id, ok := is.columnNameToID[columnName{tableName: tableName{schema: schema.L, table: table.L}, name: column.L}]
	if !ok {
		return
	}
	val, ok = is.columns[id]
	return
}

func (is *infoSchema) ColumnExists(schema, table, column model.CIStr) bool {
	_, ok := is.columnNameToID[columnName{tableName: tableName{schema: schema.L, table: table.L}, name: column.L}]
	return ok
}

func (is *infoSchema) IndexByName(schema, table, index model.CIStr) (val *model.IndexInfo, ok bool) {
	val, ok = is.indices[indexName{tableName: tableName{schema: schema.L, table: table.L}, name: index.L}]
	return
}

func (is *infoSchema) SchemaByID(id int64) (val *model.DBInfo, ok bool) {
	val, ok = is.schemas[id]
	return
}

func (is *infoSchema) TableByID(id int64) (val table.Table, ok bool) {
	val, ok = is.tables[id]
	return
}

func (is *infoSchema) ColumnByID(id int64) (val *model.ColumnInfo, ok bool) {
	val, ok = is.columns[id]
	return
}

func (is *infoSchema) ColumnIndices(id int64) (indices []*model.IndexInfo) {
	return is.columnIndices[id]
}

func (is *infoSchema) AllSchemaNames() (names []string) {
	for _, v := range is.schemas {
		names = append(names, v.Name.O)
	}
	return
}

func (is *infoSchema) AllSchemas() (schemas []*model.DBInfo) {
	for _, v := range is.schemas {
		schemas = append(schemas, v)
	}
	return
}

func (is *infoSchema) SchemaTables(schema model.CIStr) (tables []table.Table) {
	di, ok := is.SchemaByName(schema)
	if !ok {
		return
	}
	for _, ti := range di.Tables {
		tables = append(tables, is.tables[ti.ID])
	}
	return
}

func (is *infoSchema) Clone() (result []*model.DBInfo) {
	for _, v := range is.schemas {
		// TODO: this is a temporary solution, change to real clone later.
		b, _ := json.Marshal(v)
		var newInfo model.DBInfo
		json.Unmarshal(b, &newInfo)
		result = append(result, &newInfo)
	}
	return
}

// Handle handles information schema, including getting and setting.
type Handle struct {
	value atomic.Value
	store kv.Storage
}

// NewHandle creates a new Handle.
func NewHandle(store kv.Storage) *Handle {
	return &Handle{
		store: store,
	}
}

// Set sets DBInfo to information schema.
func (h *Handle) Set(newInfo []*model.DBInfo) {
	info := &infoSchema{
		schemaNameToID: map[string]int64{},
		tableNameToID:  map[tableName]int64{},
		columnNameToID: map[columnName]int64{},
		schemas:        map[int64]*model.DBInfo{},
		tables:         map[int64]table.Table{},
		columns:        map[int64]*model.ColumnInfo{},
		indices:        map[indexName]*model.IndexInfo{},
		columnIndices:  map[int64][]*model.IndexInfo{},
	}
	for _, di := range newInfo {
		info.schemas[di.ID] = di
		info.schemaNameToID[di.Name.L] = di.ID
		for _, t := range di.Tables {
			alloc := autoid.NewAllocator(h.store)
			info.tables[t.ID] = table.TableFromMeta(di.Name.L, alloc, t)
			tname := tableName{di.Name.L, t.Name.L}
			info.tableNameToID[tname] = t.ID
			for _, c := range t.Columns {
				info.columns[c.ID] = c
				info.columnNameToID[columnName{tname, c.Name.L}] = c.ID
			}
			for _, idx := range t.Indices {
				info.indices[indexName{tname, idx.Name.L}] = idx
				for _, idxCol := range idx.Columns {
					columnID := t.Columns[idxCol.Offset].ID
					columnIndices := info.columnIndices[columnID]
					info.columnIndices[columnID] = append(columnIndices, idx)
				}
			}
		}
	}
	h.value.Store(info)
}

// Get gets information schema from Handle.
func (h *Handle) Get() InfoSchema {
	return h.value.Load().(InfoSchema)
}
<commit_msg>make golint happy<commit_after>// Copyright 2015 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package infoschema

import (
	"encoding/json"
	"sync/atomic"

	"github.com/juju/errors"
	"github.com/pingcap/tidb/kv"
	"github.com/pingcap/tidb/meta/autoid"
	"github.com/pingcap/tidb/model"
	"github.com/pingcap/tidb/table"
	// import table implementation to init table.TableFromMeta
	_ "github.com/pingcap/tidb/table/tables"
)

// InfoSchema is the interface used to retrieve the schema information.
// It works as a in memory cache and doesn't handle any schema change.
// InfoSchema is read-only, and the returned value is a copy.
type InfoSchema interface {
	SchemaByName(schema model.CIStr) (*model.DBInfo, bool)
	SchemaExists(schema model.CIStr) bool
	TableByName(schema, table model.CIStr) (table.Table, error)
	TableExists(schema, table model.CIStr) bool
	ColumnByName(schema, table, column model.CIStr) (*model.ColumnInfo, bool)
	ColumnExists(schema, table, column model.CIStr) bool
	IndexByName(schema, table, index model.CIStr) (*model.IndexInfo, bool)
	SchemaByID(id int64) (*model.DBInfo, bool)
	TableByID(id int64) (table.Table, bool)
	ColumnByID(id int64) (*model.ColumnInfo, bool)
	ColumnIndices(id int64) []*model.IndexInfo
	AllSchemaNames() []string
	AllSchemas() []*model.DBInfo
	Clone() (result []*model.DBInfo)
	SchemaTables(schema model.CIStr) []table.Table
	// TODO: add more methods to retrieve tables and columns.
}

// Infomation Schema Name.
const (
	Name = "INFORMATION_SCHEMA"
)

type infoSchema struct {
	schemaNameToID map[string]int64
	tableNameToID  map[tableName]int64
	columnNameToID map[columnName]int64
	schemas        map[int64]*model.DBInfo
	tables         map[int64]table.Table
	columns        map[int64]*model.ColumnInfo
	indices        map[indexName]*model.IndexInfo
	columnIndices  map[int64][]*model.IndexInfo
}

type tableName struct {
	schema string
	table  string
}

type columnName struct {
	tableName
	name string
}

type indexName struct {
	tableName
	name string
}

func (is *infoSchema) SchemaByName(schema model.CIStr) (val *model.DBInfo, ok bool) {
	id, ok := is.schemaNameToID[schema.L]
	if !ok {
		return
	}
	val, ok = is.schemas[id]
	return
}

func (is *infoSchema) SchemaExists(schema model.CIStr) bool {
	_, ok := is.schemaNameToID[schema.L]
	return ok
}

func (is *infoSchema) TableByName(schema, table model.CIStr) (t table.Table, err error) {
	id, ok := is.tableNameToID[tableName{schema: schema.L, table: table.L}]
	if !ok {
		return nil, errors.Errorf("table %s.%s does not exist", schema, table)
	}
	t = is.tables[id]
	return
}

func (is *infoSchema) TableExists(schema, table model.CIStr) bool {
	_, ok := is.tableNameToID[tableName{schema: schema.L, table: table.L}]
	return ok
}

func (is *infoSchema) ColumnByName(schema, table, column model.CIStr) (val *model.ColumnInfo, ok bool) {
	id, ok := is.columnNameToID[columnName{tableName: tableName{schema: schema.L, table: table.L}, name: column.L}]
	if !ok {
		return
	}
	val, ok = is.columns[id]
	return
}

func (is *infoSchema) ColumnExists(schema, table, column model.CIStr) bool {
	_, ok := is.columnNameToID[columnName{tableName: tableName{schema: schema.L, table: table.L}, name: column.L}]
	return ok
}

func (is *infoSchema) IndexByName(schema, table, index model.CIStr) (val *model.IndexInfo, ok bool) {
	val, ok = is.indices[indexName{tableName: tableName{schema: schema.L, table: table.L}, name: index.L}]
	return
}

func (is *infoSchema) SchemaByID(id int64) (val *model.DBInfo, ok bool) {
	val, ok = is.schemas[id]
	return
}

func (is *infoSchema) TableByID(id int64) (val table.Table, ok bool) {
	val, ok = is.tables[id]
	return
}

func (is *infoSchema) ColumnByID(id int64) (val *model.ColumnInfo, ok bool) {
	val, ok = is.columns[id]
	return
}

func (is *infoSchema) ColumnIndices(id int64) (indices []*model.IndexInfo) {
	return is.columnIndices[id]
}

func (is *infoSchema) AllSchemaNames() (names []string) {
	for _, v := range is.schemas {
		names = append(names, v.Name.O)
	}
	return
}

func (is *infoSchema) AllSchemas() (schemas []*model.DBInfo) {
	for _, v := range is.schemas {
		schemas = append(schemas, v)
	}
	return
}

func (is *infoSchema) SchemaTables(schema model.CIStr) (tables []table.Table) {
	di, ok := is.SchemaByName(schema)
	if !ok {
		return
	}
	for _, ti := range di.Tables {
		tables = append(tables, is.tables[ti.ID])
	}
	return
}

func (is *infoSchema) Clone() (result []*model.DBInfo) {
	for _, v := range is.schemas {
		// TODO: this is a temporary solution, change to real clone later.
		b, _ := json.Marshal(v)
		var newInfo model.DBInfo
		json.Unmarshal(b, &newInfo)
		result = append(result, &newInfo)
	}
	return
}

// Handle handles information schema, including getting and setting.
type Handle struct {
	value atomic.Value
	store kv.Storage
}

// NewHandle creates a new Handle.
func NewHandle(store kv.Storage) *Handle {
	return &Handle{
		store: store,
	}
}

// Set sets DBInfo to information schema.
func (h *Handle) Set(newInfo []*model.DBInfo) {
	info := &infoSchema{
		schemaNameToID: map[string]int64{},
		tableNameToID:  map[tableName]int64{},
		columnNameToID: map[columnName]int64{},
		schemas:        map[int64]*model.DBInfo{},
		tables:         map[int64]table.Table{},
		columns:        map[int64]*model.ColumnInfo{},
		indices:        map[indexName]*model.IndexInfo{},
		columnIndices:  map[int64][]*model.IndexInfo{},
	}
	for _, di := range newInfo {
		info.schemas[di.ID] = di
		info.schemaNameToID[di.Name.L] = di.ID
		for _, t := range di.Tables {
			alloc := autoid.NewAllocator(h.store)
			info.tables[t.ID] = table.TableFromMeta(di.Name.L, alloc, t)
			tname := tableName{di.Name.L, t.Name.L}
			info.tableNameToID[tname] = t.ID
			for _, c := range t.Columns {
				info.columns[c.ID] = c
				info.columnNameToID[columnName{tname, c.Name.L}] = c.ID
			}
			for _, idx := range t.Indices {
				info.indices[indexName{tname, idx.Name.L}] = idx
				for _, idxCol := range idx.Columns {
					columnID := t.Columns[idxCol.Offset].ID
					columnIndices := info.columnIndices[columnID]
					info.columnIndices[columnID] = append(columnIndices, idx)
				}
			}
		}
	}
	h.value.Store(info)
}

// Get gets information schema from Handle.
func (h *Handle) Get() InfoSchema {
	return h.value.Load().(InfoSchema)
}
<|endoftext|>


<commit_before>// Copyright 2017 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pilosa_test

import (
	"encoding/json"
	"testing"

	"strings"

	"github.com/pilosa/pilosa"
	"github.com/pilosa/pilosa/internal"
	"github.com/pilosa/pilosa/test"
)

func TestInputDefinition_Open(t *testing.T) {
	index := test.MustOpenIndex()
	defer index.Close()

	// Create Input Definition.
	frames := internal.Frame{Name: "f", Meta: &internal.FrameMeta{RowLabel: "row"}}
	action := internal.InputDefinitionAction{Frame: "f", ValueDestination: "mapping", ValueMap: map[string]uint64{"Green": 1}}
	fields := internal.InputDefinitionField{Name: "id", PrimaryKey: true, InputDefinitionActions: []*internal.InputDefinitionAction{&action}}
	def := internal.InputDefinition{Name: "^", Frames: []*internal.Frame{&frames}, Fields: []*internal.InputDefinitionField{&fields}}
	inputDef, err := index.CreateInputDefinition(&def)
	if !strings.Contains(err.Error(), "invalid index or frame's name") {
		t.Fatalf("Expected Invalid name error, actual error: %s", err)
	}

	def = internal.InputDefinition{Name: "test", Frames: []*internal.Frame{&frames}, Fields: []*internal.InputDefinitionField{&fields}}
	inputDef, err = index.CreateInputDefinition(&def)
	if err != nil {
		t.Fatal(err)
	}
	err = inputDef.Open()
	if err != nil {
		t.Fatal(err)
	}
}

// Verify the InputDefinition Encoding to the internal format
func TestInputDefinition_Encoding(t *testing.T) {
	inputBody := []byte(`
			{
			"frames":[{
				"name":"event-time",
				"options":{
					"timeQuantum": "YMD",
					"inverseEnabled": true,
					"cacheType": "ranked"
				}
			}],
			"fields": [
				{
					"name": "id",
					"primaryKey": true
				},
				{
					"name": "cabType",
					"actions": [
						{
							"frame": "cab-type",
							"valueDestination": "mapping",
							"valueMap": {
								"Green": 1,
								"Yellow": 2
							}
						}
					]
				}
			]
		}`)
	var def pilosa.InputDefinitionInfo
	err := json.Unmarshal(inputBody, &def)
	if err != nil {
		t.Fatal(err)
	}

	internalDef := def.Encode()

	if internalDef.Frames[0].Name != "event-time" {
		t.Fatalf("unexpected frame: %v", internalDef)
	} else if internalDef.Frames[0].Meta.CacheType != "ranked" {
		t.Fatalf("unexpected frame meta data: %v", internalDef)
	} else if len(internalDef.Fields) != 2 {
		t.Fatalf("unexpected number of Fields: %d", len(internalDef.Fields))
	} else if len(internalDef.Fields[1].InputDefinitionActions) != 1 {
		t.Fatalf("unexpected number of Actions: %v", internalDef.Fields[1].InputDefinitionActions)
	} else if internalDef.Fields[1].InputDefinitionActions[0].ValueDestination != "mapping" {
		t.Fatalf("unexpected ValueDestination: %v", internalDef.Fields[1].InputDefinitionActions[0])
	}
}

// Test The Action validation cases
func TestActionValidation(t *testing.T) {
	rowID := uint64(100)

	action := pilosa.Action{Frame: "f", ValueDestination: pilosa.InputSingleRowBool, ValueMap: map[string]uint64{"Green": 1}}
	field := pilosa.InputDefinitionField{Name: "id", PrimaryKey: false, Actions: []pilosa.Action{action}}
	info := pilosa.InputDefinitionInfo{Fields: []pilosa.InputDefinitionField{field}}
	err := info.Validate("id")
	if err != pilosa.ErrInputDefinitionAttrsRequired {
		t.Fatalf("Expect error: %s, actual err: %s", pilosa.ErrInputDefinitionAttrsRequired, err)
	}

	frame := pilosa.InputFrame{Name: "f", Options: pilosa.FrameOptions{RowLabel: "row"}}
	info = pilosa.InputDefinitionInfo{Frames: []pilosa.InputFrame{frame}, Fields: []pilosa.InputDefinitionField{field}}
	err = info.Validate("id")
	if !strings.Contains(err.Error(), "rowID required for single-row-boolean") {
		t.Fatalf("Expected rowID required for single-row-boolean error, actual error: %s", err)
	}

	frame = pilosa.InputFrame{Name: "^", Options: pilosa.FrameOptions{RowLabel: "row"}}
	action = pilosa.Action{Frame: "f", ValueDestination: pilosa.InputSingleRowBool, RowID: &rowID}
	field = pilosa.InputDefinitionField{Name: "id", PrimaryKey: true, Actions: []pilosa.Action{action}}
	info = pilosa.InputDefinitionInfo{Frames: []pilosa.InputFrame{frame}, Fields: []pilosa.InputDefinitionField{field}}
	err = info.Validate("id")
	if err != pilosa.ErrName {
		t.Fatalf("Expect error: %s, actual err: %s", pilosa.ErrName, err)
	}

	frame = pilosa.InputFrame{Name: "f", Options: pilosa.FrameOptions{RowLabel: "row"}}
	action = pilosa.Action{ValueDestination: pilosa.InputSingleRowBool, RowID: &rowID}
	field = pilosa.InputDefinitionField{Name: "id", PrimaryKey: true, Actions: []pilosa.Action{action}}
	info = pilosa.InputDefinitionInfo{Frames: []pilosa.InputFrame{frame}, Fields: []pilosa.InputDefinitionField{field}}
	err = info.Validate("id")
	if err != pilosa.ErrFrameRequired {
		t.Fatalf("Expect error: %s, actual err: %s", pilosa.ErrFrameRequired, err)
	}

	action = pilosa.Action{Frame: "f", ValueDestination: pilosa.InputSingleRowBool, RowID: &rowID}
	field = pilosa.InputDefinitionField{Name: "id", PrimaryKey: true, Actions: []pilosa.Action{action}}
	info = pilosa.InputDefinitionInfo{Frames: []pilosa.InputFrame{frame}, Fields: []pilosa.InputDefinitionField{field}}
	err = info.Validate("test")
	if err != pilosa.ErrInputDefinitionColumnLabel {
		t.Fatalf("Expect error: %s, actual err: %s", pilosa.ErrInputDefinitionColumnLabel, err)
	}

	action = pilosa.Action{Frame: "f", ValueDestination: pilosa.InputSingleRowBool, RowID: &rowID}
	field = pilosa.InputDefinitionField{Name: "x", PrimaryKey: false, Actions: []pilosa.Action{action}}
	info = pilosa.InputDefinitionInfo{Frames: []pilosa.InputFrame{frame}, Fields: []pilosa.InputDefinitionField{field}}
	err = info.Validate("id")
	if err != pilosa.ErrInputDefinitionHasPrimaryKey {
		t.Fatalf("Expect error: %s, actual err: %s", pilosa.ErrInputDefinitionHasPrimaryKey, err)
	}

	action = pilosa.Action{Frame: "f", ValueDestination: "value-to-ROW", ValueMap: map[string]uint64{"Green": 1}}
	field = pilosa.InputDefinitionField{Name: "id", PrimaryKey: true, Actions: []pilosa.Action{action}}
	info = pilosa.InputDefinitionInfo{Frames: []pilosa.InputFrame{frame}, Fields: []pilosa.InputDefinitionField{field}}
	err = info.Validate("id")
	if !strings.Contains(err.Error(), "invalid ValueDestination") {
		t.Fatalf("Expected invalid ValueDestination error, actual error: %s", err)
	}

	action = pilosa.Action{Frame: "f", ValueDestination: pilosa.InputMapping, RowID: &rowID}
	field = pilosa.InputDefinitionField{Name: "id", PrimaryKey: true, Actions: []pilosa.Action{action}}
	info = pilosa.InputDefinitionInfo{Frames: []pilosa.InputFrame{frame}, Fields: []pilosa.InputDefinitionField{field}}
	err = info.Validate("id")
	if err != pilosa.ErrInputDefinitionValueMap {
		t.Fatalf("Expect error: %s, actual err: %s", pilosa.ErrInputDefinitionValueMap, err)
	}

	action = pilosa.Action{Frame: "f", ValueDestination: pilosa.InputSingleRowBool, RowID: &rowID}
	field = pilosa.InputDefinitionField{Name: "test", PrimaryKey: false, Actions: []pilosa.Action{action}}
	action1 := pilosa.Action{Frame: "f", ValueDestination: pilosa.InputSingleRowBool, RowID: &rowID}
	field1 := pilosa.InputDefinitionField{Name: "id", PrimaryKey: true, Actions: []pilosa.Action{action1}}
	info = pilosa.InputDefinitionInfo{Frames: []pilosa.InputFrame{frame}, Fields: []pilosa.InputDefinitionField{field, field1}}
	err = info.Validate("id")
	if !strings.Contains(err.Error(), "duplicate rowID with other field") {
		t.Fatalf("Expected duplicate rowID with other field error, actual error: %s", err)
	}

	field = pilosa.InputDefinitionField{Name: "id", PrimaryKey: true}
	field1 = pilosa.InputDefinitionField{Name: "test", PrimaryKey: false}
	info = pilosa.InputDefinitionInfo{Frames: []pilosa.InputFrame{frame}, Fields: []pilosa.InputDefinitionField{field, field1}}
	err = info.Validate("id")
	if err != pilosa.ErrInputDefinitionActionRequired {
		t.Fatalf("Expect error: %s, actual err: %s", pilosa.ErrInputDefinitionActionRequired, err)
	}
}

func TestHandleAction(t *testing.T) {
	var value interface{}
	colID := uint64(0)
	rowID := uint64(100)
	action := pilosa.Action{RowID: &rowID}
	timestamp := int64(0)

	tests := []struct {
		action   string
		name     string
		value    interface{}
		expected uint64
		err 	 string
	}{
		{name: "integer single-row-bool", action: pilosa.InputSingleRowBool, value: 1, err: "single-row-boolean value"},
		{name: "string single-row-bool", action: pilosa.InputSingleRowBool,value: "1", err: "single-row-boolean value 1 must equate to a Bool"},
		{name: "string value-to-row", action: pilosa.InputValueToRow,value: "25", err: "value-to-row value must equate to an integer"},
		{name: "string mapping", action: pilosa.InputMapping,value: "test", err: "Value test does not exist in definition map"},
		{name: "int mapping", action: pilosa.InputMapping,value: 25, err: "Mapping value must be a string"},
		{name: "handle single-row-value", action: "test",value: true, err: "Unrecognized Value Destination"},
	}
	for _, r := range tests {
		t.Run(r.name, func(t *testing.T) {
			action.ValueDestination = r.action
			_, err := pilosa.HandleAction(action, r.value, colID, timestamp)
			if !strings.Contains(err.Error(), r.err) {
				t.Fatalf("Expect err: %s, actual: %s", r.err, err.Error())
			}
		})

	}

	value = true
	action.ValueDestination = pilosa.InputSingleRowBool
	b, err := pilosa.HandleAction(action, value, colID, timestamp)
	if b != nil {
		if b.ColumnID != 0 {
			t.Fatalf("Unexpected ColumnID %v", b.ColumnID)
		}
		if b.RowID != 100 {
			t.Fatalf("Unexpected rowID %v", b.RowID)
		}
	}

	action.ValueDestination = pilosa.InputValueToRow
	rowID = 101
	value = float64(25.0)
	b, _ = pilosa.HandleAction(action, value, colID, timestamp)
	if b != nil {
		if b.RowID != 25 {
			t.Fatalf("Unexpected RowID %v", b.RowID)
		}
	}

	action.ValueDestination = pilosa.InputSetTimestamp
	t.Run("nil bit", func(t *testing.T) {
		b, err = pilosa.HandleAction(action, value, colID, timestamp)
		if b != nil {
			t.Fatalf("Expected nil bit is set")
		}
	})
}
<commit_msg>change test name<commit_after>// Copyright 2017 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pilosa_test

import (
	"encoding/json"
	"testing"

	"strings"

	"github.com/pilosa/pilosa"
	"github.com/pilosa/pilosa/internal"
	"github.com/pilosa/pilosa/test"
)

func TestInputDefinition_Open(t *testing.T) {
	index := test.MustOpenIndex()
	defer index.Close()

	// Create Input Definition.
	frames := internal.Frame{Name: "f", Meta: &internal.FrameMeta{RowLabel: "row"}}
	action := internal.InputDefinitionAction{Frame: "f", ValueDestination: "mapping", ValueMap: map[string]uint64{"Green": 1}}
	fields := internal.InputDefinitionField{Name: "id", PrimaryKey: true, InputDefinitionActions: []*internal.InputDefinitionAction{&action}}
	def := internal.InputDefinition{Name: "^", Frames: []*internal.Frame{&frames}, Fields: []*internal.InputDefinitionField{&fields}}
	inputDef, err := index.CreateInputDefinition(&def)
	if !strings.Contains(err.Error(), "invalid index or frame's name") {
		t.Fatalf("Expected Invalid name error, actual error: %s", err)
	}

	def = internal.InputDefinition{Name: "test", Frames: []*internal.Frame{&frames}, Fields: []*internal.InputDefinitionField{&fields}}
	inputDef, err = index.CreateInputDefinition(&def)
	if err != nil {
		t.Fatal(err)
	}
	err = inputDef.Open()
	if err != nil {
		t.Fatal(err)
	}
}

// Verify the InputDefinition Encoding to the internal format
func TestInputDefinition_Encoding(t *testing.T) {
	inputBody := []byte(`
			{
			"frames":[{
				"name":"event-time",
				"options":{
					"timeQuantum": "YMD",
					"inverseEnabled": true,
					"cacheType": "ranked"
				}
			}],
			"fields": [
				{
					"name": "id",
					"primaryKey": true
				},
				{
					"name": "cabType",
					"actions": [
						{
							"frame": "cab-type",
							"valueDestination": "mapping",
							"valueMap": {
								"Green": 1,
								"Yellow": 2
							}
						}
					]
				}
			]
		}`)
	var def pilosa.InputDefinitionInfo
	err := json.Unmarshal(inputBody, &def)
	if err != nil {
		t.Fatal(err)
	}

	internalDef := def.Encode()

	if internalDef.Frames[0].Name != "event-time" {
		t.Fatalf("unexpected frame: %v", internalDef)
	} else if internalDef.Frames[0].Meta.CacheType != "ranked" {
		t.Fatalf("unexpected frame meta data: %v", internalDef)
	} else if len(internalDef.Fields) != 2 {
		t.Fatalf("unexpected number of Fields: %d", len(internalDef.Fields))
	} else if len(internalDef.Fields[1].InputDefinitionActions) != 1 {
		t.Fatalf("unexpected number of Actions: %v", internalDef.Fields[1].InputDefinitionActions)
	} else if internalDef.Fields[1].InputDefinitionActions[0].ValueDestination != "mapping" {
		t.Fatalf("unexpected ValueDestination: %v", internalDef.Fields[1].InputDefinitionActions[0])
	}
}

// Test The Action validation cases
func TestActionValidation(t *testing.T) {
	rowID := uint64(100)

	action := pilosa.Action{Frame: "f", ValueDestination: pilosa.InputSingleRowBool, ValueMap: map[string]uint64{"Green": 1}}
	field := pilosa.InputDefinitionField{Name: "id", PrimaryKey: false, Actions: []pilosa.Action{action}}
	info := pilosa.InputDefinitionInfo{Fields: []pilosa.InputDefinitionField{field}}
	err := info.Validate("id")
	if err != pilosa.ErrInputDefinitionAttrsRequired {
		t.Fatalf("Expect error: %s, actual err: %s", pilosa.ErrInputDefinitionAttrsRequired, err)
	}

	frame := pilosa.InputFrame{Name: "f", Options: pilosa.FrameOptions{RowLabel: "row"}}
	info = pilosa.InputDefinitionInfo{Frames: []pilosa.InputFrame{frame}, Fields: []pilosa.InputDefinitionField{field}}
	err = info.Validate("id")
	if !strings.Contains(err.Error(), "rowID required for single-row-boolean") {
		t.Fatalf("Expected rowID required for single-row-boolean error, actual error: %s", err)
	}

	frame = pilosa.InputFrame{Name: "^", Options: pilosa.FrameOptions{RowLabel: "row"}}
	action = pilosa.Action{Frame: "f", ValueDestination: pilosa.InputSingleRowBool, RowID: &rowID}
	field = pilosa.InputDefinitionField{Name: "id", PrimaryKey: true, Actions: []pilosa.Action{action}}
	info = pilosa.InputDefinitionInfo{Frames: []pilosa.InputFrame{frame}, Fields: []pilosa.InputDefinitionField{field}}
	err = info.Validate("id")
	if err != pilosa.ErrName {
		t.Fatalf("Expect error: %s, actual err: %s", pilosa.ErrName, err)
	}

	frame = pilosa.InputFrame{Name: "f", Options: pilosa.FrameOptions{RowLabel: "row"}}
	action = pilosa.Action{ValueDestination: pilosa.InputSingleRowBool, RowID: &rowID}
	field = pilosa.InputDefinitionField{Name: "id", PrimaryKey: true, Actions: []pilosa.Action{action}}
	info = pilosa.InputDefinitionInfo{Frames: []pilosa.InputFrame{frame}, Fields: []pilosa.InputDefinitionField{field}}
	err = info.Validate("id")
	if err != pilosa.ErrFrameRequired {
		t.Fatalf("Expect error: %s, actual err: %s", pilosa.ErrFrameRequired, err)
	}

	action = pilosa.Action{Frame: "f", ValueDestination: pilosa.InputSingleRowBool, RowID: &rowID}
	field = pilosa.InputDefinitionField{Name: "id", PrimaryKey: true, Actions: []pilosa.Action{action}}
	info = pilosa.InputDefinitionInfo{Frames: []pilosa.InputFrame{frame}, Fields: []pilosa.InputDefinitionField{field}}
	err = info.Validate("test")
	if err != pilosa.ErrInputDefinitionColumnLabel {
		t.Fatalf("Expect error: %s, actual err: %s", pilosa.ErrInputDefinitionColumnLabel, err)
	}

	action = pilosa.Action{Frame: "f", ValueDestination: pilosa.InputSingleRowBool, RowID: &rowID}
	field = pilosa.InputDefinitionField{Name: "x", PrimaryKey: false, Actions: []pilosa.Action{action}}
	info = pilosa.InputDefinitionInfo{Frames: []pilosa.InputFrame{frame}, Fields: []pilosa.InputDefinitionField{field}}
	err = info.Validate("id")
	if err != pilosa.ErrInputDefinitionHasPrimaryKey {
		t.Fatalf("Expect error: %s, actual err: %s", pilosa.ErrInputDefinitionHasPrimaryKey, err)
	}

	action = pilosa.Action{Frame: "f", ValueDestination: "value-to-ROW", ValueMap: map[string]uint64{"Green": 1}}
	field = pilosa.InputDefinitionField{Name: "id", PrimaryKey: true, Actions: []pilosa.Action{action}}
	info = pilosa.InputDefinitionInfo{Frames: []pilosa.InputFrame{frame}, Fields: []pilosa.InputDefinitionField{field}}
	err = info.Validate("id")
	if !strings.Contains(err.Error(), "invalid ValueDestination") {
		t.Fatalf("Expected invalid ValueDestination error, actual error: %s", err)
	}

	action = pilosa.Action{Frame: "f", ValueDestination: pilosa.InputMapping, RowID: &rowID}
	field = pilosa.InputDefinitionField{Name: "id", PrimaryKey: true, Actions: []pilosa.Action{action}}
	info = pilosa.InputDefinitionInfo{Frames: []pilosa.InputFrame{frame}, Fields: []pilosa.InputDefinitionField{field}}
	err = info.Validate("id")
	if err != pilosa.ErrInputDefinitionValueMap {
		t.Fatalf("Expect error: %s, actual err: %s", pilosa.ErrInputDefinitionValueMap, err)
	}

	action = pilosa.Action{Frame: "f", ValueDestination: pilosa.InputSingleRowBool, RowID: &rowID}
	field = pilosa.InputDefinitionField{Name: "test", PrimaryKey: false, Actions: []pilosa.Action{action}}
	action1 := pilosa.Action{Frame: "f", ValueDestination: pilosa.InputSingleRowBool, RowID: &rowID}
	field1 := pilosa.InputDefinitionField{Name: "id", PrimaryKey: true, Actions: []pilosa.Action{action1}}
	info = pilosa.InputDefinitionInfo{Frames: []pilosa.InputFrame{frame}, Fields: []pilosa.InputDefinitionField{field, field1}}
	err = info.Validate("id")
	if !strings.Contains(err.Error(), "duplicate rowID with other field") {
		t.Fatalf("Expected duplicate rowID with other field error, actual error: %s", err)
	}

	field = pilosa.InputDefinitionField{Name: "id", PrimaryKey: true}
	field1 = pilosa.InputDefinitionField{Name: "test", PrimaryKey: false}
	info = pilosa.InputDefinitionInfo{Frames: []pilosa.InputFrame{frame}, Fields: []pilosa.InputDefinitionField{field, field1}}
	err = info.Validate("id")
	if err != pilosa.ErrInputDefinitionActionRequired {
		t.Fatalf("Expect error: %s, actual err: %s", pilosa.ErrInputDefinitionActionRequired, err)
	}
}

func TestHandleAction(t *testing.T) {
	var value interface{}
	colID := uint64(0)
	rowID := uint64(100)
	action := pilosa.Action{RowID: &rowID}
	timestamp := int64(0)

	tests := []struct {
		action   string
		name     string
		value    interface{}
		expected uint64
		err 	 string
	}{
		{name: "integer single-row-bool", action: pilosa.InputSingleRowBool, value: 1, err: "single-row-boolean value"},
		{name: "string single-row-bool", action: pilosa.InputSingleRowBool,value: "1", err: "single-row-boolean value 1 must equate to a Bool"},
		{name: "string value-to-row", action: pilosa.InputValueToRow,value: "25", err: "value-to-row value must equate to an integer"},
		{name: "string mapping", action: pilosa.InputMapping,value: "test", err: "Value test does not exist in definition map"},
		{name: "int mapping", action: pilosa.InputMapping,value: 25, err: "Mapping value must be a string"},
		{name: "invalid action", action: "test",value: true, err: "Unrecognized Value Destination"},
	}
	for _, r := range tests {
		t.Run(r.name, func(t *testing.T) {
			action.ValueDestination = r.action
			_, err := pilosa.HandleAction(action, r.value, colID, timestamp)
			if !strings.Contains(err.Error(), r.err) {
				t.Fatalf("Expect err: %s, actual: %s", r.err, err.Error())
			}
		})

	}

	value = true
	action.ValueDestination = pilosa.InputSingleRowBool
	b, err := pilosa.HandleAction(action, value, colID, timestamp)
	if b != nil {
		if b.ColumnID != 0 {
			t.Fatalf("Unexpected ColumnID %v", b.ColumnID)
		}
		if b.RowID != 100 {
			t.Fatalf("Unexpected rowID %v", b.RowID)
		}
	}

	action.ValueDestination = pilosa.InputValueToRow
	rowID = 101
	value = float64(25.0)
	b, _ = pilosa.HandleAction(action, value, colID, timestamp)
	if b != nil {
		if b.RowID != 25 {
			t.Fatalf("Unexpected RowID %v", b.RowID)
		}
	}

	action.ValueDestination = pilosa.InputSetTimestamp
	t.Run("nil bit", func(t *testing.T) {
		b, err = pilosa.HandleAction(action, value, colID, timestamp)
		if b != nil {
			t.Fatalf("Expected nil bit is set")
		}
	})
}
<|endoftext|>