<commit_before>/*
Package lmdbsync provides advanced synchronization for LMDB environments at the
cost of performance.  The package provides a drop-in replacement for *lmdb.Env
that can be used in situations where the database may be resized or where the
flag lmdb.NoLock is used.

Bypassing an Env's methods to access the underlying lmdb.Env is not safe.  The
severity of such usage depends such behavior should be strictly avoided as it
may produce undefined behavior from the LMDB C library.

Resizing the environment

The Env type synchronizes all calls to Env.SetMapSize so that it may, with some
caveats, be safely called in the presence of concurrent transactions after an
environment has been opened.  All running transactions must complete before the
method will be called on the underlying lmdb.Env.

If an open transaction depends on a change in map size then the database will
deadlock and block all future transactions in the environment.  Put simply, all
transactions must terminate independently of other transactions.

In the simplest example, a function in view transaction that attempts an update
will deadlock database if the map is full and an increase of the map size is
attempted so the transaction can be retried.  Instead the update should be
prepared inside the view and then executed following the termination of the
view.

The developers of LMDB officially recommend against applications changing the
memory map size for an open database.  It requires careful synchronization by
all processes accessing the database file.  And, a large memory map will not
affect disk usage on operating systems that support sparse files (e.g. Linux,
not OS X).

See mdb_env_set_mapsize.

Multi-processing (MapResized)

Using the Handler interface provided by the package MapResizedHandler can be
used to automatically resize an enviornment when a lmdb.MapResized error is
encountered.  Usage of the MapResizedHandler puts important caveats on how one
can safely work with transactions.  See the function documentation for more
detailed information.

When other processes may change an environment's map size it is extremely
important to ensure that transactions terminate independent of all other
transactions.  The MapResized error may be returned at the beginning of any
transaction.

See mdb_txn_begin and MDB_MAP_RESIZED.

MapFull

Similar to the MapResizedHandler the MapFullHandler will automatically resize
the map and retry transactions when a MapFull error is encountered.  Usage of
the MapFullHandler puts important caveats on how one can safely work with
transactions.  See the function documentation for more detailed information.

The caveats on transactions are lessened if lmdb.MapFull is the only error
being handled (when multi-processing is not a concern).  The only requirement
then is that view transactions not depend on the termination of updates
transactions.

See mdb_env_set_mapsize and MDB_MAP_FULL.

NoLock

When the lmdb.NoLock flag is set on an environment Env handles all transaction
synchronization using Go structures and is an experimental feature.  It is
unclear what benefits this provides.

Usage of lmdb.NoLock requires that update transactions acquire an exclusive
lock on the environment.  In such cases it is required that view transactions
execute independently of update transactions, a requirement more strict than
that from handling MapFull.

See mdb_env_open and MDB_NOLOCK.
*/
package lmdbsync

import (
	"fmt"
	"os"
	"sync"
	"time"

	"github.com/bmatsuo/lmdb-go/lmdb"
)

type envBagKey int

// BagEnv returns the Env corresponding to a Bag in the HandleTxnErr method of
// a Handler.
func BagEnv(b Bag) *Env {
	env, _ := b.Value(envBagKey(0)).(*Env)
	return env
}

func bagWithEnv(b Bag, env *Env) Bag {
	return BagWith(b, envBagKey(0), env)
}

// Env wraps an *lmdb.Env, receiving all the same methods and proxying some to
// provide transaction management.  Transactions run by an Env handle
// lmdb.MapResized error transparently through additional synchronization.
// Additionally, Env is safe to use on environments setting the lmdb.NoLock
// flag.  When in NoLock mode write transactions block all read transactions
// from running (in addition to blocking other write transactions like a normal
// lmdb.Env would).
//
// Env proxies several methods to provide synchronization required for safe
// operation in some scenarios.  It is important not byprass proxies and call
// the methods directly on the underlying lmdb.Env or synchronization may be
// interfered with.  Calling proxied methods directly on the lmdb.Env may
// result in poor transaction performance or unspecified behavior in from the C
// library.
type Env struct {
	*lmdb.Env
	Handlers HandlerChain
	bag      Bag
	noLock   bool
	txnlock  sync.RWMutex
}

// NewEnv returns an newly allocated Env that wraps env.  If env is nil then
// lmdb.NewEnv() will be called to allocate an lmdb.Env.
func NewEnv(env *lmdb.Env, h ...Handler) (*Env, error) {
	var err error
	if env == nil {
		env, err = lmdb.NewEnv()
		if err != nil {
			return nil, err
		}
	}

	flags, err := env.Flags()
	if err != nil {
		return nil, err
	}
	noLock := flags&lmdb.NoLock != 0

	chain := append(HandlerChain(nil), h...)

	_env := &Env{
		Env:      env,
		Handlers: chain,
		noLock:   noLock,
		bag:      Background(),
	}
	return _env, nil
}

// Open is a proxy for r.Env.Open() that detects the lmdb.NoLock flag to
// properly manage transaction synchronization.
func (r *Env) Open(path string, flags uint, mode os.FileMode) error {
	err := r.Env.Open(path, flags, mode)
	if err != nil {
		// no update to flags occurred
		return err
	}

	if flags&lmdb.NoLock != 0 {
		r.noLock = true
	}

	return nil
}

// SetMapSize is a proxy for r.Env.SetMapSize() that blocks while concurrent
// transactions are in progress.
func (r *Env) SetMapSize(size int64) error {
	return r.setMapSize(size, 0)
}

func (r *Env) setMapSize(size int64, delay time.Duration) error {
	r.txnlock.Lock()
	if delay > 0 {
		// wait before adopting a map size set from another process. hold on to
		// the transaction lock so that other transactions don't attempt to
		// begin while waiting.
		time.Sleep(delay)
	}
	err := r.Env.SetMapSize(size)
	r.txnlock.Unlock()
	return err
}

// BeginTxn overrides the r.Env.BeginTxn and always returns an error.  An
// unmanaged transaction.
func (r *Env) BeginTxn(parent *lmdb.Txn, flags uint) (*lmdb.Txn, error) {
	return nil, fmt.Errorf("lmdbsync: unmanaged transactions are not supported")
}

// RunTxn is a proxy for r.Env.RunTxn().
//
// If lmdb.NoLock is set on r.Env then RunTxn will block while other updates
// are in progress, regardless of flags.
//
// If RunTxn returns MapResized it means another process(es) was writing too
// fast to the database and the calling process could not get a valid
// transaction handle.
func (r *Env) RunTxn(flags uint, op lmdb.TxnOp) (err error) {
	readonly := flags&lmdb.Readonly != 0
	return r.runHandler(readonly, func() error { return r.Env.RunTxn(flags, op) }, r.Handlers)
}

// View is a proxy for r.Env.RunTxn().
//
// If lmdb.NoLock is set on r.Env then View will block until any running update
// completes.
//
// If View returns MapResized it means another process(es) was writing too fast
// to the database and the calling process could not get a valid transaction
// handle.
func (r *Env) View(op lmdb.TxnOp) error {
	return r.runHandler(true, func() error { return r.Env.View(op) }, r.Handlers)
}

// Update is a proxy for r.Env.RunTxn().
//
// If lmdb.NoLock is set on r.Env then Update blocks until all other
// transactions have terminated and blocks all other transactions from running
// while in progress (including readonly transactions).
//
// If Update returns MapResized it means another process(es) was writing too
// fast to the database and the calling process could not get a valid
// transaction handle.
func (r *Env) Update(op lmdb.TxnOp) error {
	return r.runHandler(false, func() error { return r.Env.Update(op) }, r.Handlers)
}

// UpdateLocked is a proxy for r.Env.RunTxn().
//
// If lmdb.NoLock is set on r.Env then UpdateLocked blocks until all other
// transactions have terminated and blocks all other transactions from running
// while in progress (including readonly transactions).
//
// If UpdateLocked returns MapResized it means another process(es) was writing
// too fast to the database and the calling process could not get a valid
// transaction handle.
func (r *Env) UpdateLocked(op lmdb.TxnOp) error {
	return r.runHandler(false, func() error { return r.Env.UpdateLocked(op) }, r.Handlers)
}

// WithHandler returns a TxnRunner than handles transaction errors r.Handlers
// chained with h.
func (r *Env) WithHandler(h Handler) TxnRunner {
	return &handlerRunner{
		env: r,
		h:   r.Handlers.Append(h),
	}
}

func (r *Env) runHandler(readonly bool, fn func() error, h Handler) error {
	b := bagWithEnv(r.bag, r)
	for {
		err := r.run(readonly, fn)
		b, err = h.HandleTxnErr(b, err)
		if err != ErrTxnRetry {
			return err
		}
	}
}
func (r *Env) run(readonly bool, fn func() error) error {
	var err error
	if r.noLock && !readonly {
		r.txnlock.Lock()
		err = fn()
		r.txnlock.Unlock()
	} else {
		r.txnlock.RLock()
		err = fn()
		r.txnlock.RUnlock()
	}
	return err
}
<commit_msg>exp/lmdbsync: clarify some documentation and demonstrate deadlock<commit_after>/*
Package lmdbsync provides advanced synchronization for LMDB environments at the
cost of performance.  The package provides a drop-in replacement for *lmdb.Env
that can be used in situations where the database may be resized or where the
flag lmdb.NoLock is used.

Bypassing an Env's methods to access the underlying lmdb.Env is not safe.  The
severity of such usage depends such behavior should be strictly avoided as it
may produce undefined behavior from the LMDB C library.

Resizing the environment

The Env type synchronizes all calls to Env.SetMapSize so that it may, with some
caveats, be safely called in the presence of concurrent transactions after an
environment has been opened.  All running transactions must complete before the
method will be called on the underlying lmdb.Env.

If an open transaction depends on a change in map size then the Env will
deadlock and block all future transactions.  When using a Handler to
automatically resize the map this implies the restriction that transactions
must terminate independently of the creation and termination of other
transactions to avoid deadlock

In the simplest example, a view transaction that attempts an update on the
underlying Env will deadlock the environment if the map is full and a Handler
attempts to resize the map so the update may be retried.

	env.View(func(txn *lmdb.Txn) (err error) {
		v, err := txn.Get(db, key)
		if err != nil {
			return err
		}
		err = env.Update(func(txn *lmdb.Txn) (err error) { // deadlock on lmdb.MapFull!
			txn.Put(dbi, key, append(v, b...))
		})
		return err
	}

The update should instead be prepared inside the view and then executed
following its termination.  This removes the implicit dependence of the view on
calls to Env.SetMapSize().

	var v []byte
	env.View(func(txn *lmdb.Txn) (err error) {
		// RawRead isn't used because the value will be used outside the
		// transaction.
		v, err = txn.Get(db, key)
		if err != nil {
			return err
		}
		return nil
	}
	if err != nil {
		// ...
	}
	err = env.Update(func(txn *lmdb.Txn) (err error) { // no deadlock, even if env is resized!
		txn.Put(dbi, key, append(v, b...))
	})

The developers of LMDB officially recommend against applications changing the
memory map size for an open database.  It requires careful synchronization by
all processes accessing the database file.  And, a large memory map will not
affect disk usage on operating systems that support sparse files (e.g. Linux,
not OS X).

See mdb_env_set_mapsize.

Multi-processing (MapResized)

Using the Handler interface provided by the package MapResizedHandler can be
used to automatically resize an enviornment when a lmdb.MapResized error is
encountered.  Usage of the MapResizedHandler puts important caveats on how one
can safely work with transactions.  See the function documentation for more
detailed information.

When other processes may change an environment's map size it is extremely
important to ensure that transactions terminate independent of all other
transactions.  The MapResized error may be returned at the beginning of any
transaction.

See mdb_txn_begin and MDB_MAP_RESIZED.

MapFull

Similar to the MapResizedHandler the MapFullHandler will automatically resize
the map and retry transactions when a MapFull error is encountered.  Usage of
the MapFullHandler puts important caveats on how one can safely work with
transactions.  See the function documentation for more detailed information.

The caveats on transactions are lessened if lmdb.MapFull is the only error
being handled (when multi-processing is not a concern).  The only requirement
then is that view transactions not depend on the termination of updates
transactions.

See mdb_env_set_mapsize and MDB_MAP_FULL.

NoLock

When the lmdb.NoLock flag is set on an environment Env handles all transaction
synchronization using Go structures and is an experimental feature.  It is
unclear what benefits this provides.

Usage of lmdb.NoLock requires that update transactions acquire an exclusive
lock on the environment.  In such cases it is required that view transactions
execute independently of update transactions, a requirement more strict than
that from handling MapFull.

See mdb_env_open and MDB_NOLOCK.
*/
package lmdbsync

import (
	"fmt"
	"os"
	"sync"
	"time"

	"github.com/bmatsuo/lmdb-go/lmdb"
)

type envBagKey int

// BagEnv returns the Env corresponding to a Bag in the HandleTxnErr method of
// a Handler.
func BagEnv(b Bag) *Env {
	env, _ := b.Value(envBagKey(0)).(*Env)
	return env
}

func bagWithEnv(b Bag, env *Env) Bag {
	return BagWith(b, envBagKey(0), env)
}

// Env wraps an *lmdb.Env, receiving all the same methods and proxying some to
// provide transaction management.  Transactions run by an Env handle
// lmdb.MapResized error transparently through additional synchronization.
// Additionally, Env is safe to use on environments setting the lmdb.NoLock
// flag.  When in NoLock mode write transactions block all read transactions
// from running (in addition to blocking other write transactions like a normal
// lmdb.Env would).
//
// Env proxies several methods to provide synchronization required for safe
// operation in some scenarios.  It is important not byprass proxies and call
// the methods directly on the underlying lmdb.Env or synchronization may be
// interfered with.  Calling proxied methods directly on the lmdb.Env may
// result in poor transaction performance or unspecified behavior in from the C
// library.
type Env struct {
	*lmdb.Env
	Handlers HandlerChain
	bag      Bag
	noLock   bool
	txnlock  sync.RWMutex
}

// NewEnv returns an newly allocated Env that wraps env.  If env is nil then
// lmdb.NewEnv() will be called to allocate an lmdb.Env.
func NewEnv(env *lmdb.Env, h ...Handler) (*Env, error) {
	var err error
	if env == nil {
		env, err = lmdb.NewEnv()
		if err != nil {
			return nil, err
		}
	}

	flags, err := env.Flags()
	if err != nil {
		return nil, err
	}
	noLock := flags&lmdb.NoLock != 0

	chain := append(HandlerChain(nil), h...)

	_env := &Env{
		Env:      env,
		Handlers: chain,
		noLock:   noLock,
		bag:      Background(),
	}
	return _env, nil
}

// Open is a proxy for r.Env.Open() that detects the lmdb.NoLock flag to
// properly manage transaction synchronization.
func (r *Env) Open(path string, flags uint, mode os.FileMode) error {
	err := r.Env.Open(path, flags, mode)
	if err != nil {
		// no update to flags occurred
		return err
	}

	if flags&lmdb.NoLock != 0 {
		r.noLock = true
	}

	return nil
}

// SetMapSize is a proxy for r.Env.SetMapSize() that blocks while concurrent
// transactions are in progress.
func (r *Env) SetMapSize(size int64) error {
	return r.setMapSize(size, 0)
}

func (r *Env) setMapSize(size int64, delay time.Duration) error {
	r.txnlock.Lock()
	if delay > 0 {
		// wait before adopting a map size set from another process. hold on to
		// the transaction lock so that other transactions don't attempt to
		// begin while waiting.
		time.Sleep(delay)
	}
	err := r.Env.SetMapSize(size)
	r.txnlock.Unlock()
	return err
}

// BeginTxn overrides the r.Env.BeginTxn and always returns an error.  An
// unmanaged transaction.
func (r *Env) BeginTxn(parent *lmdb.Txn, flags uint) (*lmdb.Txn, error) {
	return nil, fmt.Errorf("lmdbsync: unmanaged transactions are not supported")
}

// RunTxn is a proxy for r.Env.RunTxn().
//
// If lmdb.NoLock is set on r.Env then RunTxn will block while other updates
// are in progress, regardless of flags.
//
// If RunTxn returns MapResized it means another process(es) was writing too
// fast to the database and the calling process could not get a valid
// transaction handle.
func (r *Env) RunTxn(flags uint, op lmdb.TxnOp) (err error) {
	readonly := flags&lmdb.Readonly != 0
	return r.runHandler(readonly, func() error { return r.Env.RunTxn(flags, op) }, r.Handlers)
}

// View is a proxy for r.Env.RunTxn().
//
// If lmdb.NoLock is set on r.Env then View will block until any running update
// completes.
//
// If View returns MapResized it means another process(es) was writing too fast
// to the database and the calling process could not get a valid transaction
// handle.
func (r *Env) View(op lmdb.TxnOp) error {
	return r.runHandler(true, func() error { return r.Env.View(op) }, r.Handlers)
}

// Update is a proxy for r.Env.RunTxn().
//
// If lmdb.NoLock is set on r.Env then Update blocks until all other
// transactions have terminated and blocks all other transactions from running
// while in progress (including readonly transactions).
//
// If Update returns MapResized it means another process(es) was writing too
// fast to the database and the calling process could not get a valid
// transaction handle.
func (r *Env) Update(op lmdb.TxnOp) error {
	return r.runHandler(false, func() error { return r.Env.Update(op) }, r.Handlers)
}

// UpdateLocked is a proxy for r.Env.RunTxn().
//
// If lmdb.NoLock is set on r.Env then UpdateLocked blocks until all other
// transactions have terminated and blocks all other transactions from running
// while in progress (including readonly transactions).
//
// If UpdateLocked returns MapResized it means another process(es) was writing
// too fast to the database and the calling process could not get a valid
// transaction handle.
func (r *Env) UpdateLocked(op lmdb.TxnOp) error {
	return r.runHandler(false, func() error { return r.Env.UpdateLocked(op) }, r.Handlers)
}

// WithHandler returns a TxnRunner than handles transaction errors r.Handlers
// chained with h.
func (r *Env) WithHandler(h Handler) TxnRunner {
	return &handlerRunner{
		env: r,
		h:   r.Handlers.Append(h),
	}
}

func (r *Env) runHandler(readonly bool, fn func() error, h Handler) error {
	b := bagWithEnv(r.bag, r)
	for {
		err := r.run(readonly, fn)
		b, err = h.HandleTxnErr(b, err)
		if err != ErrTxnRetry {
			return err
		}
	}
}
func (r *Env) run(readonly bool, fn func() error) error {
	var err error
	if r.noLock && !readonly {
		r.txnlock.Lock()
		err = fn()
		r.txnlock.Unlock()
	} else {
		r.txnlock.RLock()
		err = fn()
		r.txnlock.RUnlock()
	}
	return err
}
<|endoftext|>


<commit_before>/*
                       WARNING WARNING WARNING

                Attention all potential contributors

   This testfile is not in the best state. We've been slowly transitioning
   from the built in "testing" package to using Ginkgo. As you can see, we've
   changed the format, but a lot of the setup, test body, descriptions, etc
   are either hardcoded, completely lacking, or misleading.

   For example:

   Describe("Testing with ginkgo"...)      // This is not a great description
   It("TestDoesSoemthing"...)              // This is a horrible description

   Describe("create-user command"...       // Describe the actual object under test
   It("creates a user when provided ..."   // this is more descriptive

   For good examples of writing Ginkgo tests for the cli, refer to

   src/cf/commands/application/delete_app_test.go
   src/cf/terminal/ui_test.go
   src/github.com/cloudfoundry/loggregator_consumer/consumer_test.go
*/

package application_test

import (
	. "cf/commands/application"
	"cf/errors"
	"cf/formatters"
	"cf/models"
	. "github.com/onsi/gomega"
	testapi "testhelpers/api"
	testassert "testhelpers/assert"
	testcmd "testhelpers/commands"
	testconfig "testhelpers/configuration"
	testreq "testhelpers/requirements"
	testterm "testhelpers/terminal"

	. "github.com/onsi/ginkgo"
	"time"
)

var _ = Describe("Show App Command", func() {
	It("requires the user to be logged in and have a targeted space", func() {
		args := []string{"my-app", "/foo"}
		appSummaryRepo := &testapi.FakeAppSummaryRepo{}
		appInstancesRepo := &testapi.FakeAppInstancesRepo{}

		requirementsFactory := &testreq.FakeReqFactory{LoginSuccess: false, TargetedSpaceSuccess: true, Application: models.Application{}}
		callApp(args, requirementsFactory, appSummaryRepo, appInstancesRepo)
		Expect(testcmd.CommandDidPassRequirements).To(BeFalse())

		requirementsFactory = &testreq.FakeReqFactory{LoginSuccess: true, TargetedSpaceSuccess: false, Application: models.Application{}}
		callApp(args, requirementsFactory, appSummaryRepo, appInstancesRepo)
		Expect(testcmd.CommandDidPassRequirements).To(BeFalse())

		requirementsFactory = &testreq.FakeReqFactory{LoginSuccess: true, TargetedSpaceSuccess: true, Application: models.Application{}}
		callApp(args, requirementsFactory, appSummaryRepo, appInstancesRepo)
		Expect(testcmd.CommandDidPassRequirements).To(BeTrue())
		Expect(requirementsFactory.ApplicationName).To(Equal("my-app"))
	})

	It("requires an app name", func() {
		appSummaryRepo := &testapi.FakeAppSummaryRepo{}
		appInstancesRepo := &testapi.FakeAppInstancesRepo{}
		requirementsFactory := &testreq.FakeReqFactory{LoginSuccess: true, TargetedSpaceSuccess: true, Application: models.Application{}}
		ui := callApp([]string{}, requirementsFactory, appSummaryRepo, appInstancesRepo)

		Expect(ui.FailedWithUsage).To(BeTrue())
		Expect(testcmd.CommandDidPassRequirements).To(BeFalse())
	})

	It("displays a summary of the app", func() {
		reqApp := models.Application{}
		reqApp.Name = "my-app"
		reqApp.Guid = "my-app-guid"

		route1 := models.RouteSummary{}
		route1.Host = "my-app"

		domain := models.DomainFields{}
		domain.Name = "example.com"
		route1.Domain = domain

		route2 := models.RouteSummary{}
		route2.Host = "foo"
		domain2 := models.DomainFields{}
		domain2.Name = "example.com"
		route2.Domain = domain2

		application := models.Application{}
		application.State = "started"
		application.InstanceCount = 2
		application.RunningInstances = 2
		application.Memory = 256
		application.Routes = []models.RouteSummary{route1, route2}

		time1, err := time.Parse("Mon Jan 2 15:04:05 -0700 MST 2006", "Mon Jan 2 15:04:05 -0700 MST 2012")
		Expect(err).NotTo(HaveOccurred())

		time2, err := time.Parse("Mon Jan 2 15:04:05 -0700 MST 2006", "Mon Apr 1 15:04:05 -0700 MST 2012")
		Expect(err).NotTo(HaveOccurred())

		appInstance := models.AppInstanceFields{}
		appInstance.State = models.InstanceRunning
		appInstance.Since = time1
		appInstance.CpuUsage = 1.0
		appInstance.DiskQuota = 1 * formatters.GIGABYTE
		appInstance.DiskUsage = 32 * formatters.MEGABYTE
		appInstance.MemQuota = 64 * formatters.MEGABYTE
		appInstance.MemUsage = 13 * formatters.BYTE

		appInstance2 := models.AppInstanceFields{}
		appInstance2.State = models.InstanceDown
		appInstance2.Since = time2

		instances := []models.AppInstanceFields{appInstance, appInstance2}

		appSummaryRepo := &testapi.FakeAppSummaryRepo{GetSummarySummary: application}
		appInstancesRepo := &testapi.FakeAppInstancesRepo{GetInstancesResponses: [][]models.AppInstanceFields{instances}}
		requirementsFactory := &testreq.FakeReqFactory{LoginSuccess: true, TargetedSpaceSuccess: true, Application: reqApp}
		ui := callApp([]string{"my-app"}, requirementsFactory, appSummaryRepo, appInstancesRepo)

		Expect(appSummaryRepo.GetSummaryAppGuid).To(Equal("my-app-guid"))

		testassert.SliceContains(ui.Outputs, testassert.Lines{
			{"Showing health and status", "my-app"},
			{"state", "started"},
			{"instances", "2/2"},
			{"usage", "256M x 2 instances"},
			{"urls", "my-app.example.com", "foo.example.com"},
			{"#0", "running", "2012-01-02 03:04:05 PM", "100.0%", "13 of 64M", "32M of 1G"},
			{"#1", "down", "2012-04-01 03:04:05 PM", "0%", "0 of 0", "0 of 0"},
		})
	})

	It("TestDisplayingStoppedAppSummary", func() {
		testDisplayingAppSummaryWithErrorCode(errors.APP_STOPPED)
	})

	It("TestDisplayingNotStagedAppSummary", func() {
		testDisplayingAppSummaryWithErrorCode(errors.APP_NOT_STAGED)
	})
})

func testDisplayingAppSummaryWithErrorCode(errorCode string) {
	reqApp := models.Application{}
	reqApp.Name = "my-app"
	reqApp.Guid = "my-app-guid"

	domain3 := models.DomainFields{}
	domain3.Name = "example.com"
	domain4 := models.DomainFields{}
	domain4.Name = "example.com"

	route1 := models.RouteSummary{}
	route1.Host = "my-app"
	route1.Domain = domain3

	route2 := models.RouteSummary{}
	route2.Host = "foo"
	route2.Domain = domain4

	routes := []models.RouteSummary{
		route1,
		route2,
	}

	app := models.ApplicationFields{}
	app.State = "stopped"
	app.InstanceCount = 2
	app.RunningInstances = 0
	app.Memory = 256

	application := models.Application{}
	application.ApplicationFields = app
	application.Routes = routes

	appSummaryRepo := &testapi.FakeAppSummaryRepo{GetSummarySummary: application, GetSummaryErrorCode: errorCode}
	appInstancesRepo := &testapi.FakeAppInstancesRepo{}
	requirementsFactory := &testreq.FakeReqFactory{LoginSuccess: true, TargetedSpaceSuccess: true, Application: reqApp}
	ui := callApp([]string{"my-app"}, requirementsFactory, appSummaryRepo, appInstancesRepo)

	Expect(appSummaryRepo.GetSummaryAppGuid).To(Equal("my-app-guid"))
	Expect(appInstancesRepo.GetInstancesAppGuid).To(Equal("my-app-guid"))

	testassert.SliceContains(ui.Outputs, testassert.Lines{
		{"Showing health and status", "my-app", "my-org", "my-space", "my-user"},
		{"state", "stopped"},
		{"instances", "0/2"},
		{"usage", "256M x 2 instances"},
		{"urls", "my-app.example.com, foo.example.com"},
		{"no running instances"},
	})
}

func callApp(args []string, requirementsFactory *testreq.FakeReqFactory, appSummaryRepo *testapi.FakeAppSummaryRepo, appInstancesRepo *testapi.FakeAppInstancesRepo) (ui *testterm.FakeUI) {
	ui = &testterm.FakeUI{}
	ctxt := testcmd.NewContext("app", args)

	configRepo := testconfig.NewRepositoryWithDefaults()
	cmd := NewShowApp(ui, configRepo, appSummaryRepo, appInstancesRepo)
	testcmd.RunCommand(cmd, ctxt, requirementsFactory)

	return
}
<commit_msg>cleanup show app tests<commit_after>package application_test

import (
	. "cf/commands/application"
	"cf/configuration"
	"cf/errors"
	"cf/formatters"
	"cf/models"
	testapi "testhelpers/api"
	testassert "testhelpers/assert"
	testcmd "testhelpers/commands"
	testconfig "testhelpers/configuration"
	testreq "testhelpers/requirements"
	testterm "testhelpers/terminal"
	testtime "testhelpers/time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("app Command", func() {
	var (
		ui                  *testterm.FakeUI
		configRepo          configuration.ReadWriter
		appSummaryRepo      *testapi.FakeAppSummaryRepo
		appInstancesRepo    *testapi.FakeAppInstancesRepo
		requirementsFactory *testreq.FakeReqFactory
	)

	BeforeEach(func() {
		ui = &testterm.FakeUI{}
		appSummaryRepo = &testapi.FakeAppSummaryRepo{}
		appInstancesRepo = &testapi.FakeAppInstancesRepo{}
		configRepo = testconfig.NewRepositoryWithDefaults()
		requirementsFactory = &testreq.FakeReqFactory{
			LoginSuccess:         true,
			TargetedSpaceSuccess: true,
		}
	})

	runCommand := func(args ...string) {
		cmd := NewShowApp(ui, configRepo, appSummaryRepo, appInstancesRepo)
		testcmd.RunCommand(cmd, testcmd.NewContext("apps", args), requirementsFactory)
	}

	Describe("requirements", func() {
		It("fails if not logged in", func() {
			requirementsFactory.LoginSuccess = false
			runCommand("cf-plays-dwarf-fortress")
			Expect(testcmd.CommandDidPassRequirements).To(BeFalse())
		})

		It("fails if a space is not targeted", func() {
			requirementsFactory.TargetedSpaceSuccess = false
			runCommand("cf-plays-dwarf-fortress")
			Expect(testcmd.CommandDidPassRequirements).To(BeFalse())
		})

		It("fails with usage when no arguments are given", func() {
			runCommand()
			Expect(ui.FailedWithUsage).To(BeTrue())
			Expect(testcmd.CommandDidPassRequirements).To(BeFalse())
		})

	})

	Describe("displaying a summary of an app", func() {
		BeforeEach(func() {
			app := makeAppWithRoute("my-app")
			appInstance := models.AppInstanceFields{
				State:     models.InstanceRunning,
				Since:     testtime.MustParse("Mon Jan 2 15:04:05 -0700 MST 2006", "Mon Jan 2 15:04:05 -0700 MST 2012"),
				CpuUsage:  1.0,
				DiskQuota: 1 * formatters.GIGABYTE,
				DiskUsage: 32 * formatters.MEGABYTE,
				MemQuota:  64 * formatters.MEGABYTE,
				MemUsage:  13 * formatters.BYTE,
			}

			appInstance2 := models.AppInstanceFields{
				State: models.InstanceDown,
				Since: testtime.MustParse("Mon Jan 2 15:04:05 -0700 MST 2006", "Mon Apr 1 15:04:05 -0700 MST 2012"),
			}

			instances := []models.AppInstanceFields{appInstance, appInstance2}

			appSummaryRepo.GetSummarySummary = app
			appInstancesRepo.GetInstancesResponses = [][]models.AppInstanceFields{instances}
			requirementsFactory.Application = app
		})

		It("displays a summary of the app", func() {
			runCommand("my-app")

			Expect(appSummaryRepo.GetSummaryAppGuid).To(Equal("app-guid"))

			testassert.SliceContains(ui.Outputs, testassert.Lines{
				{"Showing health and status", "my-app"},
				{"state", "started"},
				{"instances", "2/2"},
				{"usage", "256M x 2 instances"},
				{"urls", "my-app.example.com", "foo.example.com"},
				{"#0", "running", "2012-01-02 03:04:05 PM", "100.0%", "13 of 64M", "32M of 1G"},
				{"#1", "down", "2012-04-01 03:04:05 PM", "0%", "0 of 0", "0 of 0"},
			})
		})
	})

	Describe("when the app is not running", func() {
		BeforeEach(func() {
			application := models.Application{}
			application.Name = "my-app"
			application.Guid = "my-app-guid"
			application.State = "stopped"
			application.InstanceCount = 2
			application.RunningInstances = 0
			application.Memory = 256

			appSummaryRepo.GetSummarySummary = application
			requirementsFactory.Application = application
		})

		It("displays nice output when the app is stopped", func() {
			appSummaryRepo.GetSummaryErrorCode = errors.APP_STOPPED
			runCommand("my-app")

			Expect(appSummaryRepo.GetSummaryAppGuid).To(Equal("my-app-guid"))
			Expect(appInstancesRepo.GetInstancesAppGuid).To(Equal("my-app-guid"))

			testassert.SliceContains(ui.Outputs, testassert.Lines{
				{"Showing health and status", "my-app", "my-org", "my-space", "my-user"},
				{"state", "stopped"},
				{"instances", "0/2"},
				{"usage", "256M x 2 instances"},
				{"no running instances"},
			})
		})

		It("displays nice output when the app has not yet finished staging", func() {
			appSummaryRepo.GetSummaryErrorCode = errors.APP_NOT_STAGED
			runCommand("my-app")

			Expect(appSummaryRepo.GetSummaryAppGuid).To(Equal("my-app-guid"))
			Expect(appInstancesRepo.GetInstancesAppGuid).To(Equal("my-app-guid"))

			testassert.SliceContains(ui.Outputs, testassert.Lines{
				{"Showing health and status", "my-app", "my-org", "my-space", "my-user"},
				{"state", "stopped"},
				{"instances", "0/2"},
				{"usage", "256M x 2 instances"},
				{"no running instances"},
			})
		})
	})
})

func makeAppWithRoute(appName string) models.Application {
	application := models.Application{}
	application.Name = appName
	application.Guid = "app-guid"

	domain := models.DomainFields{}
	domain.Name = "example.com"

	route := models.RouteSummary{Host: "foo", Domain: domain}
	secondRoute := models.RouteSummary{Host: appName, Domain: domain}

	application.State = "started"
	application.InstanceCount = 2
	application.RunningInstances = 2
	application.Memory = 256
	application.Routes = []models.RouteSummary{route, secondRoute}

	return application
}
<|endoftext|>


<commit_before>package gameserver

import (
	"fmt"

	"net"

	"log"

	"sync"

	"github.com/xiaonanln/typeconv"
	"github.com/xiaonanln/vacuum/ext/entity"
	"github.com/xiaonanln/vacuum/netutil"
	"github.com/xiaonanln/vacuum/vlog"
)

type GSGateID entity.EntityID

type GSGate struct {
	entity.Entity
	ID          GSGateID
	clientsLock sync.RWMutex
	clients     map[GSClientID]*GSClient
}

func init() {
}

func runGates(config *GameserverConfig) {
	for i := 0; i < config.GatesNum; i++ {
		port := config.GatesStartPort + i*config.GatesPortStep
		entity.CreateEntity("GSGate", i, port)
	}
}

func (gate *GSGate) String() string {
	return fmt.Sprintf("GSGate<%s>", gate.ID)
}

func (gate *GSGate) Init() {
	// called from GSGate.Entity routine
	gate.ID = GSGateID(gate.Entity.ID)

	// initialize clients
	gate.clients = map[GSClientID]*GSClient{}

	gateIndex := typeconv.Int(gate.Args()[0])
	port := typeconv.Int(gate.Args()[1])
	vlog.Debug("Initializing gate %v port %v: %s ...", gateIndex, port, gate)

	// start goroutine to serve clients
	serveAddr := fmt.Sprintf(":%d", port)
	go netutil.ServeTCPForever(serveAddr, gate)
}

// handle client connection to gate
func (gate *GSGate) ServeTCPConnection(conn net.Conn) {
	// called from TCP Service
	vlog.Debug("%s: new connection %s ...", gate, conn.RemoteAddr())
	client := newGSClient(gate, conn)
	gate.clientsLock.Lock()
	gate.clients[client.ClientID] = client // add client to gate clients-map
	gate.clientsLock.Unlock()

	bootEntityKindName := gameserverConfig.BootEntityKind
	entityID := nilSpace.CreateEntity(bootEntityKindName, Vec3{})
	// set entity client

	client.setOwner(entityID)
	client.clientCreateEntity(bootEntityKindName, entityID) // create entity on client side
	entityID.notifyGetClient(gate.ID, client.ClientID)

	go client.serve()
}

func (gate *GSGate) CallClient(clientID GSClientID, entityID GSEntityID, methodName string, args []interface{}) {
	// Send RPC call to the client, called from GSGate.Entity routine
	gate.clientsLock.RLock()
	client, ok := gate.clients[clientID]
	gate.clientsLock.RUnlock()
	if !ok {
		log.Panicf("%s.CallClient: %s: Client %s not found", gate, methodName, clientID)
	}

	client.clientCallEntityMethod(entityID, methodName, args)
}

// notify the client to change owner
func (gate *GSGate) NotifyClientChangeOwner(clientID GSClientID, ownerID GSEntityID, otherID GSEntityID) {
	gate.clientsLock.RLock()
	client, ok := gate.clients[clientID]
	gate.clientsLock.RUnlock()
	if !ok {
		log.Panicf("%s.NotifyClientChangeOwner: client %s not found", gate, clientID)
		return
	}

	client.notifyChangeOwner(ownerID, otherID)
	// tell other entity to get the client
	otherID.notifyGetClient(gate.ID, client.ClientID)
}

func (gate *GSGate) onClientDisconnect(client *GSClient) {
	// called from GSClient serve routine
	gate.clientsLock.Lock()
	delete(gate.clients, client.ClientID)
	gate.clientsLock.Unlock()
}

//func (gate *GSGate) getClient(id GSClientID) *GSClient {
//	return gate.clients[id]
//}
<commit_msg>GiveClientTo<commit_after>package gameserver

import (
	"fmt"

	"net"

	"log"

	"sync"

	"github.com/xiaonanln/typeconv"
	"github.com/xiaonanln/vacuum/ext/entity"
	"github.com/xiaonanln/vacuum/netutil"
	"github.com/xiaonanln/vacuum/vlog"
)

type GSGateID entity.EntityID

type GSGate struct {
	entity.Entity
	ID          GSGateID
	clientsLock sync.RWMutex
	clients     map[GSClientID]*GSClient
}

func init() {
}

func runGates(config *GameserverConfig) {
	for i := 0; i < config.GatesNum; i++ {
		port := config.GatesStartPort + i*config.GatesPortStep
		entity.CreateEntity("GSGate", i, port)
	}
}

func (gate *GSGate) String() string {
	return fmt.Sprintf("GSGate<%s>", gate.ID)
}

func (gate *GSGate) Init() {
	// called from GSGate.Entity routine
	gate.ID = GSGateID(gate.Entity.ID)

	// initialize clients
	gate.clients = map[GSClientID]*GSClient{}

	gateIndex := typeconv.Int(gate.Args()[0])
	port := typeconv.Int(gate.Args()[1])
	vlog.Debug("Initializing gate %v port %v: %s ...", gateIndex, port, gate)

	// start goroutine to serve clients
	serveAddr := fmt.Sprintf(":%d", port)
	go netutil.ServeTCPForever(serveAddr, gate)
}

// handle client connection to gate
func (gate *GSGate) ServeTCPConnection(conn net.Conn) {
	// called from TCP Service
	vlog.Debug("%s: new connection %s ...", gate, conn.RemoteAddr())
	client := newGSClient(gate, conn)
	gate.clientsLock.Lock()
	gate.clients[client.ClientID] = client // add client to gate clients-map
	gate.clientsLock.Unlock()

	bootEntityKindName := gameserverConfig.BootEntityKind
	entityID := nilSpace.CreateEntity(bootEntityKindName, Vec3{})
	// set entity client

	client.setOwner(entityID)
	client.clientCreateEntity(bootEntityKindName, entityID) // create entity on client side
	entityID.notifyGetClient(gate.ID, client.ClientID)

	go client.serve()
}

func (gate *GSGate) CallClient(clientID GSClientID, entityID GSEntityID, methodName string, args []interface{}) {
	// Send RPC call to the client, called from GSGate.Entity routine
	gate.clientsLock.RLock()
	client, ok := gate.clients[clientID]
	gate.clientsLock.RUnlock()
	if !ok {
		log.Panicf("%s.CallClient: %s: Client %s not found", gate, methodName, clientID)
	}

	client.clientCallEntityMethod(entityID, methodName, args)
}

// notify the client to change owner
func (gate *GSGate) NotifyClientChangeOwner(clientID GSClientID, ownerID GSEntityID, otherID GSEntityID) {
	gate.clientsLock.RLock()
	client, ok := gate.clients[clientID]
	gate.clientsLock.RUnlock()
	if !ok {
		log.Panicf("%s.NotifyClientChangeOwner: client %s not found", gate, clientID)
		return
	}

	client.notifyChangeOwner(ownerID, otherID)
	// tell other entity to get the client
	otherID.notifyGetClient(gate.ID, client.ClientID)
}

func (gate *GSGate) onClientDisconnect(client *GSClient) {
	// called from GSClient serve routine
	gate.clientsLock.Lock()
	delete(gate.clients, client.ClientID)
	gate.clientsLock.Unlock()
}
<|endoftext|>


<commit_before>package sampler

import (
	"fmt"
	"net"
	"strconv"

	"github.com/garyburd/redigo/redis"
)

// Options is a configuration struct that instructs the sampler pkg to sample
// the redis instance listening on a particular host/port with a specified
// number of random keys.
type Options struct {
	Host    string
	Port    int
	NumKeys int
}

// A ValueType represents the various data types that redis can store. The
// string representation of a ValueType matches what is returned from redis'
// `TYPE` command.
type ValueType string

var (
	// TypeString represents a redis string value
	TypeString ValueType = "string"
	// TypeSortedSet represents a redis sorted set value
	TypeSortedSet ValueType = "zset"
	// TypeSet represents a redis set value
	TypeSet ValueType = "set"
	// TypeHash represents a redis hash value
	TypeHash ValueType = "hash"
	// TypeList represents a redis list value
	TypeList ValueType = "list"
	// TypeUnknown means that the redis value type is undefined, and indicates an error
	TypeUnknown ValueType = "unknown"
)

// An Aggregator returns 0 or more arbitrary strings, to be used during a
// sampling operation as aggregation groups or "buckets". For example, an
// Aggregator that takes the first letter of the key would cause a Sampler to
// aggregate stats by each letter of the alphabet
type Aggregator interface {
	Groups(key string, valueType ValueType) []string
}

// The AggregatorFunc type is an adapter to allow the use of
// ordinary functions as Aggregators.  If f is a function
// with the appropriate signature, AggregatorFunc(f) is an
// Aggregator object that calls f.
type AggregatorFunc func(key string, valueType ValueType) []string

// Groups provides 0 or more groups to aggregate `key` to, when sampling redis keys.
func (f AggregatorFunc) Groups(key string, valueType ValueType) []string {
	return f(key, valueType)
}

// flush is a convenience func for flushing a redis pipeline, receiving the
// replies, and returning them, along with any error
func flush(conn redis.Conn) ([]interface{}, error) {
	return redis.Values(conn.Do(""))
}

// ensureEntry is a convenience func for obtaining the Stats instance for the
// specified `group`, creating a new one if no such entry already exists
func ensureEntry(m map[string]*Results, group string, init func() *Results) *Results {
	var stats *Results
	var ok bool
	if stats, ok = m[group]; !ok {
		stats = init()
		m[group] = stats
	}
	return stats
}

// randomKey obtains a random redis key and its ValueType from the supplied redis connection
func randomKey(conn redis.Conn) (key string, vt ValueType, err error) {
	key, err = redis.String(conn.Do("RANDOMKEY"))
	if err != nil {
		return key, TypeUnknown, err
	}

	typeStr, err := redis.String(conn.Do("TYPE", key))
	if err != nil {
		return key, TypeUnknown, err
	}

	return key, ValueType(typeStr), nil
}

func sampleString(key string, conn redis.Conn, aggregator Aggregator, stats map[string]*Results) error {
	val, err := redis.String(conn.Do("GET", key))
	if err != nil {
		return err
	}

	for _, agg := range aggregator.Groups(key, TypeString) {
		s := ensureEntry(stats, agg, NewResults)
		s.observeString(key, val)
	}
	return nil
}

func sampleList(key string, conn redis.Conn, aggregator Aggregator, stats map[string]*Results) error {
	// TODO: Let's not always get the first element, like the orig. sampler
	conn.Send("LLEN", key)
	conn.Send("LRANGE", key, 0, 0)
	replies, err := flush(conn)
	if err != nil {
		return err
	}

	if len(replies) >= 2 {
		l, err := redis.Int(replies[0], err)
		if err != nil {
			return err
		}
		ms, err := redis.Strings(replies[1], err)
		if err != nil {
			return err
		}

		for _, g := range aggregator.Groups(key, TypeList) {
			s := ensureEntry(stats, g, NewResults)
			s.observeList(key, l, ms[0])
		}
	}
	return nil
}

func sampleSet(key string, conn redis.Conn, aggregator Aggregator, stats map[string]*Results) error {
	conn.Send("SCARD", key)
	conn.Send("SRANDMEMBER", key)
	replies, err := flush(conn)
	if err != nil {
		return err
	}

	if len(replies) >= 2 {
		l, err := redis.Int(replies[0], err)
		if err != nil {
			return err
		}
		m, err := redis.String(replies[1], err)
		if err != nil {
			return err
		}

		for _, g := range aggregator.Groups(key, TypeSet) {
			s := ensureEntry(stats, g, NewResults)
			s.observeSet(key, l, m)
		}
	}
	return nil
}

func sampleSortedSet(key string, conn redis.Conn, aggregator Aggregator, stats map[string]*Results) error {
	conn.Send("ZCARD", key)
	// TODO: Let's not always get the first element, like the orig. sampler
	conn.Send("ZRANGE", key, 0, 0)
	replies, err := flush(conn)
	if err != nil {
		return err
	}

	if len(replies) >= 2 {
		l, err := redis.Int(replies[0], err)
		if err != nil {
			return err
		}
		ms, err := redis.Strings(replies[1], err)
		if err != nil {
			return err
		}

		for _, g := range aggregator.Groups(key, TypeSortedSet) {
			s := ensureEntry(stats, g, NewResults)
			s.observeSortedSet(key, l, ms[0])
		}
	}
	return nil
}

func sampleHash(key string, conn redis.Conn, aggregator Aggregator, stats map[string]*Results) error {
	conn.Send("HLEN", key)
	conn.Send("HKEYS", key)
	replies, err := flush(conn)
	if err != nil {
		return err
	}

	if len(replies) >= 2 {
		for _, g := range aggregator.Groups(key, TypeHash) {

			// TODO: Let's not always get the first hash field, like the orig. sampler
			l, err := redis.Int(replies[0], err)
			if err != nil {
				return err
			}
			fields, err := redis.Strings(replies[1], err)
			if err != nil {
				return err
			}
			val, err := redis.String(conn.Do("HGET", key, fields[0]))
			if err != nil {
				return err
			}
			s := ensureEntry(stats, g, NewResults)
			s.observeHash(key, l, fields[0], val)
		}
	}
	return nil
}

// Run performs the configured sampling operation against the redis instance,
// aggregating statistics using the provided Aggregator.  If any errors occurr,
// the sampling is short-circuited, and the error is returned.  In such a case,
// the results should be considered invalid.
func Run(opts Options, aggregator Aggregator) (map[string]*Results, error) {

	stats := make(map[string]*Results)
	var err error

	conn, err := redis.Dial("tcp", net.JoinHostPort(opts.Host, strconv.Itoa(opts.Port)))
	if err != nil {
		return stats, err
	}

	interval := opts.NumKeys / 100
	if interval == 0 {
		interval = 100
	}
	lastInterval := 0

	for i := 0; i < opts.NumKeys; i++ {
		key, vt, err := randomKey(conn)
		if err != nil {
			return stats, err
		}

		if i/interval != lastInterval {
			fmt.Printf("sampled %d keys from redis at: %s:%d...\n", i, opts.Host, opts.Port)
			lastInterval = i / interval
		}

		switch ValueType(vt) {
		case TypeString:
			err = sampleString(key, conn, aggregator, stats)
			if err != nil {
				return stats, err
			}
		case TypeList:
			err = sampleList(key, conn, aggregator, stats)
			if err != nil {
				return stats, err
			}
		case TypeSet:
			err = sampleSet(key, conn, aggregator, stats)
			if err != nil {
				return stats, err
			}
		case TypeSortedSet:
			err = sampleSortedSet(key, conn, aggregator, stats)
			if err != nil {
				return stats, err
			}
		case TypeHash:
			err = sampleHash(key, conn, aggregator, stats)
			if err != nil {
				return stats, err
			}
		default:
			return stats, fmt.Errorf("unknown type for redis key: %s", key)
		}
	}
	return stats, nil
}
<commit_msg>Removes some extraneous error handling code<commit_after>package sampler

import (
	"fmt"
	"net"
	"strconv"

	"github.com/garyburd/redigo/redis"
)

// Options is a configuration struct that instructs the sampler pkg to sample
// the redis instance listening on a particular host/port with a specified
// number of random keys.
type Options struct {
	Host    string
	Port    int
	NumKeys int
}

// A ValueType represents the various data types that redis can store. The
// string representation of a ValueType matches what is returned from redis'
// `TYPE` command.
type ValueType string

var (
	// TypeString represents a redis string value
	TypeString ValueType = "string"
	// TypeSortedSet represents a redis sorted set value
	TypeSortedSet ValueType = "zset"
	// TypeSet represents a redis set value
	TypeSet ValueType = "set"
	// TypeHash represents a redis hash value
	TypeHash ValueType = "hash"
	// TypeList represents a redis list value
	TypeList ValueType = "list"
	// TypeUnknown means that the redis value type is undefined, and indicates an error
	TypeUnknown ValueType = "unknown"
)

// An Aggregator returns 0 or more arbitrary strings, to be used during a
// sampling operation as aggregation groups or "buckets". For example, an
// Aggregator that takes the first letter of the key would cause a Sampler to
// aggregate stats by each letter of the alphabet
type Aggregator interface {
	Groups(key string, valueType ValueType) []string
}

// The AggregatorFunc type is an adapter to allow the use of
// ordinary functions as Aggregators.  If f is a function
// with the appropriate signature, AggregatorFunc(f) is an
// Aggregator object that calls f.
type AggregatorFunc func(key string, valueType ValueType) []string

// Groups provides 0 or more groups to aggregate `key` to, when sampling redis keys.
func (f AggregatorFunc) Groups(key string, valueType ValueType) []string {
	return f(key, valueType)
}

// flush is a convenience func for flushing a redis pipeline, receiving the
// replies, and returning them, along with any error
func flush(conn redis.Conn) ([]interface{}, error) {
	return redis.Values(conn.Do(""))
}

// ensureEntry is a convenience func for obtaining the Stats instance for the
// specified `group`, creating a new one if no such entry already exists
func ensureEntry(m map[string]*Results, group string, init func() *Results) *Results {
	var stats *Results
	var ok bool
	if stats, ok = m[group]; !ok {
		stats = init()
		m[group] = stats
	}
	return stats
}

// randomKey obtains a random redis key and its ValueType from the supplied redis connection
func randomKey(conn redis.Conn) (key string, vt ValueType, err error) {
	key, err = redis.String(conn.Do("RANDOMKEY"))
	if err != nil {
		return key, TypeUnknown, err
	}

	typeStr, err := redis.String(conn.Do("TYPE", key))
	if err != nil {
		return key, TypeUnknown, err
	}

	return key, ValueType(typeStr), nil
}

func sampleString(key string, conn redis.Conn, aggregator Aggregator, stats map[string]*Results) error {
	val, err := redis.String(conn.Do("GET", key))
	if err != nil {
		return err
	}

	for _, agg := range aggregator.Groups(key, TypeString) {
		s := ensureEntry(stats, agg, NewResults)
		s.observeString(key, val)
	}
	return nil
}

func sampleList(key string, conn redis.Conn, aggregator Aggregator, stats map[string]*Results) error {
	// TODO: Let's not always get the first element, like the orig. sampler
	conn.Send("LLEN", key)
	conn.Send("LRANGE", key, 0, 0)
	replies, err := flush(conn)
	if err != nil {
		return err
	}

	if len(replies) >= 2 {
		l, err := redis.Int(replies[0], nil)
		ms, err := redis.Strings(replies[1], err)
		if err != nil {
			return err
		}

		for _, g := range aggregator.Groups(key, TypeList) {
			s := ensureEntry(stats, g, NewResults)
			s.observeList(key, l, ms[0])
		}
	}
	return nil
}

func sampleSet(key string, conn redis.Conn, aggregator Aggregator, stats map[string]*Results) error {
	conn.Send("SCARD", key)
	conn.Send("SRANDMEMBER", key)
	replies, err := flush(conn)
	if err != nil {
		return err
	}

	if len(replies) >= 2 {
		l, err := redis.Int(replies[0], nil)
		m, err := redis.String(replies[1], err)
		if err != nil {
			return err
		}

		for _, g := range aggregator.Groups(key, TypeSet) {
			s := ensureEntry(stats, g, NewResults)
			s.observeSet(key, l, m)
		}
	}
	return nil
}

func sampleSortedSet(key string, conn redis.Conn, aggregator Aggregator, stats map[string]*Results) error {
	conn.Send("ZCARD", key)
	// TODO: Let's not always get the first element, like the orig. sampler
	conn.Send("ZRANGE", key, 0, 0)
	replies, err := flush(conn)
	if err != nil {
		return err
	}

	if len(replies) >= 2 {
		l, err := redis.Int(replies[0], nil)
		ms, err := redis.Strings(replies[1], err)
		if err != nil {
			return err
		}

		for _, g := range aggregator.Groups(key, TypeSortedSet) {
			s := ensureEntry(stats, g, NewResults)
			s.observeSortedSet(key, l, ms[0])
		}
	}
	return nil
}

func sampleHash(key string, conn redis.Conn, aggregator Aggregator, stats map[string]*Results) error {
	conn.Send("HLEN", key)
	conn.Send("HKEYS", key)
	replies, err := flush(conn)
	if err != nil {
		return err
	}

	if len(replies) >= 2 {
		for _, g := range aggregator.Groups(key, TypeHash) {

			// TODO: Let's not always get the first hash field, like the orig. sampler
			l, err := redis.Int(replies[0], nil)
			fields, err := redis.Strings(replies[1], err)
			if err != nil {
				return err
			}
			val, err := redis.String(conn.Do("HGET", key, fields[0]))
			if err != nil {
				return err
			}
			s := ensureEntry(stats, g, NewResults)
			s.observeHash(key, l, fields[0], val)
		}
	}
	return nil
}

// Run performs the configured sampling operation against the redis instance,
// aggregating statistics using the provided Aggregator.  If any errors occurr,
// the sampling is short-circuited, and the error is returned.  In such a case,
// the results should be considered invalid.
func Run(opts Options, aggregator Aggregator) (map[string]*Results, error) {

	stats := make(map[string]*Results)
	var err error

	conn, err := redis.Dial("tcp", net.JoinHostPort(opts.Host, strconv.Itoa(opts.Port)))
	if err != nil {
		return stats, err
	}

	interval := opts.NumKeys / 100
	if interval == 0 {
		interval = 100
	}
	lastInterval := 0

	for i := 0; i < opts.NumKeys; i++ {
		key, vt, err := randomKey(conn)
		if err != nil {
			return stats, err
		}

		if i/interval != lastInterval {
			fmt.Printf("sampled %d keys from redis at: %s:%d...\n", i, opts.Host, opts.Port)
			lastInterval = i / interval
		}

		switch ValueType(vt) {
		case TypeString:
			err = sampleString(key, conn, aggregator, stats)
			if err != nil {
				return stats, err
			}
		case TypeList:
			err = sampleList(key, conn, aggregator, stats)
			if err != nil {
				return stats, err
			}
		case TypeSet:
			err = sampleSet(key, conn, aggregator, stats)
			if err != nil {
				return stats, err
			}
		case TypeSortedSet:
			err = sampleSortedSet(key, conn, aggregator, stats)
			if err != nil {
				return stats, err
			}
		case TypeHash:
			err = sampleHash(key, conn, aggregator, stats)
			if err != nil {
				return stats, err
			}
		default:
			return stats, fmt.Errorf("unknown type for redis key: %s", key)
		}
	}
	return stats, nil
}
<|endoftext|>


<commit_before>package git

import (
	"context"
	"fmt"
	"net/url"

	. "github.com/polydawn/go-errcat"

	"go.polydawn.net/go-timeless-api"
	"go.polydawn.net/go-timeless-api/rio"
	"go.polydawn.net/rio/fs"
	"go.polydawn.net/rio/transmat/mixins/log"
	gitWarehouse "go.polydawn.net/rio/warehouse/impl/git"
)

// Pick a warehouse.
func pick(
	ctx context.Context,
	wareID api.WareID,
	warehouses []api.WarehouseAddr,
	objcacheWorkdir fs.FS,
	mon rio.Monitor,
) (whCtrl *gitWarehouse.Controller, err error) {
	defer RequireErrorHasCategory(&err, rio.ErrorCategory(""))

	var anyWarehouses bool // for clarity in final error messages
	for _, addr := range warehouses {
		u, err := url.Parse(string(addr))
		if err != nil {
			return nil, Errorf(rio.ErrUsage, "failed to parse URI: %s", err)
		}
		switch u.Scheme {
		case "git":
			fallthrough
		case "ssh":
			fallthrough
		case "http", "https":
			fallthrough
		case "file":
			whCtrl, err = gitWarehouse.NewController(objcacheWorkdir, addr)
			if err == nil {
				err = whCtrl.Clone(ctx)
			}
		default:
			return nil, Errorf(rio.ErrUsage, "this fetch operation doesn't support %q scheme (valid options are 'git', 'ssh', 'http', 'https', or 'file')", u.Scheme)
		}
		switch Category(err) {
		case nil:
			anyWarehouses = true
			// pass
		case rio.ErrWarehouseUnavailable:
			log.WarehouseUnavailable(mon, err, addr, wareID, "read")
			continue // okay!  skip to the next one.
		default:
			return nil, err
		}
		if whCtrl.Contains(wareID.Hash) {
			log.WareReaderOpened(mon, addr, wareID)
			return whCtrl, nil // happy path return!
		} else {
			log.WareNotFound(mon, fmt.Errorf("not in this repo"), addr, wareID)
			continue // okay!  skip to the next one.
		}
	}
	if !anyWarehouses {
		return nil, Errorf(rio.ErrWarehouseUnavailable, "no warehouses were available!")
	}
	return nil, Errorf(rio.ErrWareNotFound, "none of the available warehouses have ware %q!", wareID)
}
<commit_msg>git: comment on side effects of 'pick' function.<commit_after>package git

import (
	"context"
	"fmt"
	"net/url"

	. "github.com/polydawn/go-errcat"

	"go.polydawn.net/go-timeless-api"
	"go.polydawn.net/go-timeless-api/rio"
	"go.polydawn.net/rio/fs"
	"go.polydawn.net/rio/transmat/mixins/log"
	gitWarehouse "go.polydawn.net/rio/warehouse/impl/git"
)

// Pick a warehouse.
//
// This function is not cheap; it actually does a ton of work,
// including fetching full content and leaving a lot of it on disk.
// We're not thrilled with this, but it's the way git works.
func pick(
	ctx context.Context,
	wareID api.WareID,
	warehouses []api.WarehouseAddr,
	objcacheWorkdir fs.FS,
	mon rio.Monitor,
) (whCtrl *gitWarehouse.Controller, err error) {
	defer RequireErrorHasCategory(&err, rio.ErrorCategory(""))

	var anyWarehouses bool // for clarity in final error messages
	for _, addr := range warehouses {
		u, err := url.Parse(string(addr))
		if err != nil {
			return nil, Errorf(rio.ErrUsage, "failed to parse URI: %s", err)
		}
		switch u.Scheme {
		case "git":
			fallthrough
		case "ssh":
			fallthrough
		case "http", "https":
			fallthrough
		case "file":
			whCtrl, err = gitWarehouse.NewController(objcacheWorkdir, addr)
			if err == nil {
				err = whCtrl.Clone(ctx)
			}
		default:
			return nil, Errorf(rio.ErrUsage, "this fetch operation doesn't support %q scheme (valid options are 'git', 'ssh', 'http', 'https', or 'file')", u.Scheme)
		}
		switch Category(err) {
		case nil:
			anyWarehouses = true
			// pass
		case rio.ErrWarehouseUnavailable:
			log.WarehouseUnavailable(mon, err, addr, wareID, "read")
			continue // okay!  skip to the next one.
		default:
			return nil, err
		}
		if whCtrl.Contains(wareID.Hash) {
			log.WareReaderOpened(mon, addr, wareID)
			return whCtrl, nil // happy path return!
		} else {
			log.WareNotFound(mon, fmt.Errorf("not in this repo"), addr, wareID)
			continue // okay!  skip to the next one.
		}
	}
	if !anyWarehouses {
		return nil, Errorf(rio.ErrWarehouseUnavailable, "no warehouses were available!")
	}
	return nil, Errorf(rio.ErrWareNotFound, "none of the available warehouses have ware %q!", wareID)
}
<|endoftext|>


<commit_before>package handlers

import (
	"appengine"
	"github.com/gorilla/feeds"
	"github.com/icco/natnatnat/models"
	"github.com/pilu/traffic"
	"net/http"
	"time"
)

func buildFeed(c appengine.Context, entries *[]models.Entry) *feeds.Feed {
	now := time.Now()
	me := &feeds.Author{"Nat Welch", "nat@natwelch.com"}
	feed := &feeds.Feed{
		Title:       "Nat? Nat. Nat!",
		Link:        &feeds.Link{Href: "https://writing.natwelch.com"},
		Description: "Thoughts from Nat about stuff",
		Author:      me,
		Created:     now,
	}

	feed.Items = []*feeds.Item{}
	for _, v := range *entries {
		if v.Public {
			feed.Items = append(feed.Items, &feeds.Item{
				Title:       v.Title,
				Link:        &feeds.Link{Href: v.Url()},
				Description: string(v.Html()),
				Author:      me,
				Created:     v.Datetime,
				Updated:     v.Modified,
			})
		}
	}

	return feed
}

func FeedAtomHandler(w traffic.ResponseWriter, r *traffic.Request) {
	c := appengine.NewContext(r.Request)
	entries, err := models.AllPosts(c)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	feed := buildFeed(c, entries)
	atom, err := feed.ToAtom()
}

func FeedRssHandler(w traffic.ResponseWriter, r *traffic.Request) {
	c := appengine.NewContext(r.Request)
	entries, err := models.AllPosts(c)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	feed := buildFeed(c, entries)
	rss, err := feed.ToRss()
}
<commit_msg>actually return data<commit_after>package handlers

import (
	"appengine"
	"github.com/gorilla/feeds"
	"github.com/icco/natnatnat/models"
	"github.com/pilu/traffic"
	"net/http"
	"time"
)

func buildFeed(c appengine.Context, entries *[]models.Entry) *feeds.Feed {
	now := time.Now()
	me := &feeds.Author{"Nat Welch", "nat@natwelch.com"}
	feed := &feeds.Feed{
		Title:       "Nat? Nat. Nat!",
		Link:        &feeds.Link{Href: "https://writing.natwelch.com"},
		Description: "Thoughts from Nat about stuff",
		Author:      me,
		Created:     now,
	}

	feed.Items = []*feeds.Item{}
	for _, v := range *entries {
		if v.Public {
			feed.Items = append(feed.Items, &feeds.Item{
				Title:       v.Title,
				Link:        &feeds.Link{Href: v.Url()},
				Description: string(v.Html()),
				Author:      me,
				Created:     v.Datetime,
				Updated:     v.Modified,
			})
		}
	}

	return feed
}

func FeedAtomHandler(w traffic.ResponseWriter, r *traffic.Request) {
	c := appengine.NewContext(r.Request)
	entries, err := models.AllPosts(c)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	feed := buildFeed(c, entries)
	atom, err := feed.ToAtom()
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	w.WriteXML(atom)
}

func FeedRssHandler(w traffic.ResponseWriter, r *traffic.Request) {
	c := appengine.NewContext(r.Request)
	entries, err := models.AllPosts(c)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	feed := buildFeed(c, entries)
	rss, err := feed.ToRss()
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	w.WriteXML(rss)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/hex"
	"encoding/json"
	"flag"
	"fmt"
	"github.com/tatsuhiro-t/go-http2-hpack"
	"os"
	"reflect"
)

type hpackTest struct {
	Draft       int    `json:"draft,omitempty"`
	Description string `json:"description,omitempty"`
	Cases       []map[string]interface{}
}

func runTest(test *hpackTest) error {
	decoder := hpack.NewDecoder()

	for seqno, testCase := range test.Cases {
		wire := testCase["wire"].(string)
		expectedHeaders := testCase["headers"].([]interface{})

		if val, ok := testCase["header_table_size"]; ok {
			headerTableSize := val.(uint)
			decoder.ChangeTableSize(headerTableSize)
		}

		input, err := hex.DecodeString(wire)

		if err != nil {
			return fmt.Errorf(
				"seqno %d: wire is not encoded as hex string",
				seqno)
		}

		headers := []map[string]string{}

		for cur := 0; cur < len(input); {
			header, nread, err := decoder.Decode(input[cur:cur+1],
				cur+1 == len(input))

			if err != nil {
				return fmt.Errorf("seqno %d: decode failed %s",
					seqno, err)
			}

			if header != nil {
				headers = append(headers,
					map[string]string{
						header.Name: header.Value,
					})
			}

			cur += nread
		}

		if reflect.DeepEqual(expectedHeaders, headers) {
			fmt.Errorf("seqno %d: decoded = %v, want %v",
				seqno, headers, expectedHeaders)
		}
	}

	return nil
}

func main() {
	flag.Parse()

	for _, inpath := range flag.Args() {
		file, err := os.Open(inpath)

		if err != nil {
			panic(err)
		}

		dec := json.NewDecoder(file)

		var test hpackTest

		err = dec.Decode(&test)

		if err != nil {
			panic(err)
		}

		err = runTest(&test)

		if err != nil {
			fmt.Printf("%s: FAIL\n%s\n", inpath, err)
		} else {
			fmt.Printf("%s: SUCCESS\n", inpath)
		}
	}
}
<commit_msg>Update doc<commit_after>package main

import (
	"encoding/hex"
	"encoding/json"
	"flag"
	"fmt"
	"github.com/tatsuhiro-t/go-http2-hpack"
	"os"
	"reflect"
)

type hpackTest struct {
	Draft       int    `json:"draft,omitempty"`
	Description string `json:"description,omitempty"`
	Cases       []map[string]interface{}
}

func runTest(test *hpackTest) error {
	decoder := hpack.NewDecoder()

	for seqno, testCase := range test.Cases {
		wire := testCase["wire"].(string)
		expectedHeaders := testCase["headers"].([]interface{})

		if val, ok := testCase["header_table_size"]; ok {
			headerTableSize := val.(uint)
			decoder.ChangeTableSize(headerTableSize)
		}

		input, err := hex.DecodeString(wire)

		if err != nil {
			return fmt.Errorf(
				"seqno %d: wire is not encoded as hex string",
				seqno)
		}

		headers := []map[string]string{}

		for cur := 0; cur < len(input); {
			// Decode 1 byte at a time to check streaming
			// decoder.
			header, nread, err := decoder.Decode(input[cur:cur+1],
				cur+1 == len(input))

			if err != nil {
				return fmt.Errorf("seqno %d: decode failed %s",
					seqno, err)
			}

			if header != nil {
				headers = append(headers,
					map[string]string{
						header.Name: header.Value,
					})
			}

			cur += nread
		}

		if reflect.DeepEqual(expectedHeaders, headers) {
			fmt.Errorf("seqno %d: decoded = %v, want %v",
				seqno, headers, expectedHeaders)
		}
	}

	return nil
}

func main() {
	flag.Parse()

	for _, inpath := range flag.Args() {
		file, err := os.Open(inpath)

		if err != nil {
			panic(err)
		}

		dec := json.NewDecoder(file)

		var test hpackTest

		err = dec.Decode(&test)

		if err != nil {
			panic(err)
		}

		err = runTest(&test)

		if err != nil {
			fmt.Printf("%s: FAIL\n%s\n", inpath, err)
		} else {
			fmt.Printf("%s: SUCCESS\n", inpath)
		}
	}
}
<|endoftext|>


<commit_before>// Copyright ©2013 The bíogo.entrez Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package entrez

import (
	"encoding/xml"
	"fmt"
	"io"
	"strconv"
)

// <!--
//                 This is the Current DTD for Entrez eSearch
// $Id: eSearch_020511.dtd 85163 2006-06-28 17:35:21Z olegh $
// -->
// <!-- ================================================================= -->
//
// <!ELEMENT       Count           (#PCDATA)>	<!-- \d+ -->
// <!ELEMENT       RetMax          (#PCDATA)>	<!-- \d+ -->
// <!ELEMENT       RetStart        (#PCDATA)>	<!-- \d+ -->
// <!ELEMENT       Id              (#PCDATA)>	<!-- \d+ -->
//
// <!ELEMENT       From            (#PCDATA)>	<!-- .+ -->
// <!ELEMENT       To              (#PCDATA)>	<!-- .+ -->
// <!ELEMENT       Term            (#PCDATA)>	<!-- .+ -->
//
// <!ELEMENT       Field           (#PCDATA)>	<!-- .+ -->
//
// <!ELEMENT        QueryKey       (#PCDATA)>	<!-- \d+ -->
// <!ELEMENT        WebEnv         (#PCDATA)>	<!-- \S+ -->
//
// <!ELEMENT       Explode         (#PCDATA)>	<!-- (Y|N) -->
// <!ELEMENT       OP              (#PCDATA)>	<!-- (AND|OR|NOT|RANGE|GROUP) -->
// <!ELEMENT       IdList          (Id*)>
//
// <!ELEMENT       Translation     (From, To)>
// <!ELEMENT       TranslationSet  (Translation*)>
//
// <!ELEMENT       TermSet (Term, Field, Count, Explode)>
// <!ELEMENT       TranslationStack        ((TermSet|OP)*)>
//
// <!-- Error message tags  -->
//
// <!ELEMENT        ERROR                  (#PCDATA)>	<!-- .+ -->
//
// <!ELEMENT        OutputMessage		    (#PCDATA)>	<!-- .+ -->
// <!ELEMENT        QuotedPhraseNotFound   (#PCDATA)>	<!-- .+ -->
// <!ELEMENT        PhraseIgnored          (#PCDATA)>	<!-- .+ -->
// <!ELEMENT        FieldNotFound          (#PCDATA)>	<!-- .+ -->
// <!ELEMENT        PhraseNotFound         (#PCDATA)>	<!-- .+ -->
// <!ELEMENT        QueryTranslation       (#PCDATA)>	<!-- .+ -->
//
// <!ELEMENT        ErrorList      (PhraseNotFound*,FieldNotFound*)>
// <!ELEMENT        WarningList   	(PhraseIgnored*,
// 				QuotedPhraseNotFound*,
// 				OutputMessage*)>
// <!-- Response tags -->
//
//
// <!ELEMENT       eSearchResult   (((
//                                 Count,
//                                     (RetMax,
//                                     RetStart,
//                                     QueryKey?,
//                                     WebEnv?,
//                                     IdList,
//                                     TranslationSet,
//                                     TranslationStack?,
//                                     QueryTranslation
//                                     )?
//                                 ) | ERROR),
// 				ErrorList?,
// 				WarningList?
// 				)>

const (
	phraseNotFound = "phrase not found"
	fieldNotFound  = "field not found"
)

type NotFoundError struct {
	Type  string
	Value string
}

func (e NotFoundError) Error() string { return fmt.Sprintf("entrez: %s: %q", e.Type, e.Value) }

const (
	phraseIgnored        = "phrase ignored"
	quotedPhraseNotFound = "quoted phrase not found"
	outputMessage        = "output message"
)

type Warning struct {
	Type  string
	Value string
}

func (w Warning) Error() string { return fmt.Sprintf("entrez: warning: %s: %q", w.Type, w.Value) }

type Op string

func (o Op) Consume(s []Node) Node {
	// TODO Flesh out Op to be a struct:
	//
	//  type Op struct {
	//  	Operation string
	//  	Operands  []Node
	//  }
	//
	// Then we can build an AST for the search. To do this we need to understand what
	// RANGE and GROUP actually do - this is not specified.
	switch o {
	case "AND", "OR", "NOT", "RANGE", "GROUP":
		return o
	}
	return nil
}

type Term struct {
	Term    string
	Field   string
	Count   int
	Explode bool
}

func (tm Term) Consume(_ []Node) Node { return tm }

func (tm *Term) unmarshal(dec *xml.Decoder, st stack) error {
	for {
		t, err := dec.Token()
		if err != nil {
			if err == io.EOF {
				return io.ErrUnexpectedEOF
			}
			return err
		}
		switch t := t.(type) {
		case xml.ProcInst:
		case xml.Directive:
		case xml.StartElement:
			st = st.push(t.Name.Local)
		case xml.CharData:
			if st.empty() {
				continue
			}
			switch name := st.peek(0); name {
			case "Term":
				tm.Term = string(t)
			case "Field":
				tm.Field = string(t)
			case "Count":
				c, err := strconv.Atoi(string(t))
				if err != nil {
					return err
				}
				tm.Count = c
			case "Explode":
				switch b := string(t); b {
				case "Y", "N":
					tm.Explode = b == "Y"
				default:
					return fmt.Errorf("entrez: bad boolean: %q", b)
				}
			case "TermSet":
			default:
				return fmt.Errorf("entrez: unknown name: %q", name)
			}
		case xml.EndElement:
			st, err = st.pair(t.Name.Local)
			if err != nil {
				return err
			}
			if st.empty() {
				return nil
			}
		}
	}
	panic("cannot reach")
}

type Translation struct {
	From string
	To   string
}

func (tr *Translation) unmarshal(dec *xml.Decoder, st stack) error {
	for {
		t, err := dec.Token()
		if err != nil {
			if err == io.EOF {
				return io.ErrUnexpectedEOF
			}
			return err
		}
		switch t := t.(type) {
		case xml.ProcInst:
		case xml.Directive:
		case xml.StartElement:
			st = st.push(t.Name.Local)
		case xml.CharData:
			if st.empty() {
				continue
			}
			switch name := st.peek(0); name {
			case "From":
				tr.From = string(t)
			case "To":
				tr.To = string(t)
			case "TranslationSet", "Translation":
			default:
				return fmt.Errorf("entrez: unknown name: %q", name)
			}
		case xml.EndElement:
			st, err = st.pair(t.Name.Local)
			if err != nil {
				return err
			}
			if st.empty() {
				return nil
			}
		}
	}
	panic("cannot reach")
}

type Node interface {
	Consume([]Node) Node
}

// A Search holds the deserialised results of an ESearch request.
type Search struct {
	Database         string
	Count            int
	RetMax           int
	RetStart         int
	History          *History
	IdList           []int
	Translations     []Translation
	TranslationStack []Node
	QueryTranslation *string
	Err              error
	Errors           []error
	Warnings         []error
}

// Unmarshal fills the fields of a Search from an XML stream read from r.
func (s *Search) Unmarshal(r io.Reader) error {
	dec := xml.NewDecoder(r)
	var st stack
	for {
		t, err := dec.Token()
		if err != nil {
			if err != io.EOF {
				return err
			}
			if !st.empty() {
				return io.ErrUnexpectedEOF
			}
			break
		}
		switch t := t.(type) {
		case xml.ProcInst:
		case xml.Directive:
		case xml.StartElement:
			st = st.push(t.Name.Local)
			switch t.Name.Local {
			case "Translation":
				var tr Translation
				err := tr.unmarshal(dec, st[len(st)-1:])
				if (tr != Translation{}) {
					s.Translations = append(s.Translations, tr)
				}
				if err != nil {
					return err
				}
				st = st.drop()
			case "TermSet":
				var tm Term
				err := tm.unmarshal(dec, st[len(st)-1:])
				if (tm != Term{}) {
					s.TranslationStack = append(s.TranslationStack, tm)
				}
				if err != nil {
					return err
				}
				st = st.drop()
			}
		case xml.CharData:
			if st.empty() {
				continue
			}
			switch name := st.peek(0); name {
			case "Count":
				c, err := strconv.Atoi(string(t))
				if err != nil {
					return err
				}
				s.Count = c
			case "RetMax":
				m, err := strconv.Atoi(string(t))
				if err != nil {
					return err
				}
				s.RetMax = m
			case "RetStart":
				st, err := strconv.Atoi(string(t))
				if err != nil {
					return err
				}
				s.RetStart = st
			case "QueryKey":
				if s.History == nil {
					s.History = &History{}
				}
				k, err := strconv.Atoi(string(t))
				if err != nil {
					return err
				}
				s.History.QueryKey = k
			case "WebEnv":
				if s.History == nil {
					s.History = &History{}
				}
				s.History.WebEnv = string(t)
			case "Id":
				id, err := strconv.Atoi(string(t))
				if err != nil {
					return err
				}
				s.IdList = append(s.IdList, id)
			case "OP":
				o := Op(string(t))
				if o.Consume(s.TranslationStack) == nil {
					return fmt.Errorf("entrez: illegal operator: %q", o)
				}
				s.TranslationStack = append(s.TranslationStack, o)
			case "QueryTranslation":
				st := string(t)
				s.QueryTranslation = &st
			case "ERROR":
				s.Err = Error(string(t))
			case "PhraseNotFound", "FieldNotFound":
				if st.peek(1) != "ErrorList" {
					return fmt.Errorf("entrez: unexpected tag: %q", name)
				}
				switch name {
				case "PhraseNotFound":
					s.Errors = append(s.Errors, NotFoundError{Type: phraseNotFound, Value: string(t)})
				case "FieldNotFound":
					s.Errors = append(s.Errors, NotFoundError{Type: fieldNotFound, Value: string(t)})
				}
			case "PhraseIgnored", "QuotedPhraseNotFound", "OutputMessage":
				if st.peek(1) != "WarningList" {
					return fmt.Errorf("entrez: unexpected tag: %q", name)
				}
				switch name {
				case "PhraseIgnored":
					s.Warnings = append(s.Warnings, Warning{Type: phraseIgnored, Value: string(t)})
				case "QuotedPhraseNotFound":
					s.Warnings = append(s.Warnings, Warning{Type: quotedPhraseNotFound, Value: string(t)})
				case "OutputMessage":
					s.Warnings = append(s.Warnings, Warning{Type: outputMessage, Value: string(t)})
				}
			case "eSearchResult", "IdList", "TranslationSet", "TranslationStack", "ErrorList", "WarningList":
			default:
				return fmt.Errorf("entrez: unknown name: %q", name)
			}
		case xml.EndElement:
			st, err = st.pair(t.Name.Local)
			if err != nil {
				return err
			}
		}
	}
	return nil
}
<commit_msg>Explain Node interface<commit_after>// Copyright ©2013 The bíogo.entrez Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package entrez

import (
	"encoding/xml"
	"fmt"
	"io"
	"strconv"
)

// <!--
//                 This is the Current DTD for Entrez eSearch
// $Id: eSearch_020511.dtd 85163 2006-06-28 17:35:21Z olegh $
// -->
// <!-- ================================================================= -->
//
// <!ELEMENT       Count           (#PCDATA)>	<!-- \d+ -->
// <!ELEMENT       RetMax          (#PCDATA)>	<!-- \d+ -->
// <!ELEMENT       RetStart        (#PCDATA)>	<!-- \d+ -->
// <!ELEMENT       Id              (#PCDATA)>	<!-- \d+ -->
//
// <!ELEMENT       From            (#PCDATA)>	<!-- .+ -->
// <!ELEMENT       To              (#PCDATA)>	<!-- .+ -->
// <!ELEMENT       Term            (#PCDATA)>	<!-- .+ -->
//
// <!ELEMENT       Field           (#PCDATA)>	<!-- .+ -->
//
// <!ELEMENT        QueryKey       (#PCDATA)>	<!-- \d+ -->
// <!ELEMENT        WebEnv         (#PCDATA)>	<!-- \S+ -->
//
// <!ELEMENT       Explode         (#PCDATA)>	<!-- (Y|N) -->
// <!ELEMENT       OP              (#PCDATA)>	<!-- (AND|OR|NOT|RANGE|GROUP) -->
// <!ELEMENT       IdList          (Id*)>
//
// <!ELEMENT       Translation     (From, To)>
// <!ELEMENT       TranslationSet  (Translation*)>
//
// <!ELEMENT       TermSet (Term, Field, Count, Explode)>
// <!ELEMENT       TranslationStack        ((TermSet|OP)*)>
//
// <!-- Error message tags  -->
//
// <!ELEMENT        ERROR                  (#PCDATA)>	<!-- .+ -->
//
// <!ELEMENT        OutputMessage		    (#PCDATA)>	<!-- .+ -->
// <!ELEMENT        QuotedPhraseNotFound   (#PCDATA)>	<!-- .+ -->
// <!ELEMENT        PhraseIgnored          (#PCDATA)>	<!-- .+ -->
// <!ELEMENT        FieldNotFound          (#PCDATA)>	<!-- .+ -->
// <!ELEMENT        PhraseNotFound         (#PCDATA)>	<!-- .+ -->
// <!ELEMENT        QueryTranslation       (#PCDATA)>	<!-- .+ -->
//
// <!ELEMENT        ErrorList      (PhraseNotFound*,FieldNotFound*)>
// <!ELEMENT        WarningList   	(PhraseIgnored*,
// 				QuotedPhraseNotFound*,
// 				OutputMessage*)>
// <!-- Response tags -->
//
//
// <!ELEMENT       eSearchResult   (((
//                                 Count,
//                                     (RetMax,
//                                     RetStart,
//                                     QueryKey?,
//                                     WebEnv?,
//                                     IdList,
//                                     TranslationSet,
//                                     TranslationStack?,
//                                     QueryTranslation
//                                     )?
//                                 ) | ERROR),
// 				ErrorList?,
// 				WarningList?
// 				)>

const (
	phraseNotFound = "phrase not found"
	fieldNotFound  = "field not found"
)

type NotFoundError struct {
	Type  string
	Value string
}

func (e NotFoundError) Error() string { return fmt.Sprintf("entrez: %s: %q", e.Type, e.Value) }

const (
	phraseIgnored        = "phrase ignored"
	quotedPhraseNotFound = "quoted phrase not found"
	outputMessage        = "output message"
)

type Warning struct {
	Type  string
	Value string
}

func (w Warning) Error() string { return fmt.Sprintf("entrez: warning: %s: %q", w.Type, w.Value) }

type Op string

func (o Op) Consume(s []Node) Node {
	// TODO Flesh out Op to be a struct:
	//
	//  type Op struct {
	//  	Operation string
	//  	Operands  []Node
	//  }
	//
	// Then we can build an AST for the search. To do this we need to understand what
	// RANGE and GROUP actually do - this is not specified.
	switch o {
	case "AND", "OR", "NOT", "RANGE", "GROUP":
		return o
	}
	return nil
}

type Term struct {
	Term    string
	Field   string
	Count   int
	Explode bool
}

func (tm Term) Consume(_ []Node) Node { return tm }

func (tm *Term) unmarshal(dec *xml.Decoder, st stack) error {
	for {
		t, err := dec.Token()
		if err != nil {
			if err == io.EOF {
				return io.ErrUnexpectedEOF
			}
			return err
		}
		switch t := t.(type) {
		case xml.ProcInst:
		case xml.Directive:
		case xml.StartElement:
			st = st.push(t.Name.Local)
		case xml.CharData:
			if st.empty() {
				continue
			}
			switch name := st.peek(0); name {
			case "Term":
				tm.Term = string(t)
			case "Field":
				tm.Field = string(t)
			case "Count":
				c, err := strconv.Atoi(string(t))
				if err != nil {
					return err
				}
				tm.Count = c
			case "Explode":
				switch b := string(t); b {
				case "Y", "N":
					tm.Explode = b == "Y"
				default:
					return fmt.Errorf("entrez: bad boolean: %q", b)
				}
			case "TermSet":
			default:
				return fmt.Errorf("entrez: unknown name: %q", name)
			}
		case xml.EndElement:
			st, err = st.pair(t.Name.Local)
			if err != nil {
				return err
			}
			if st.empty() {
				return nil
			}
		}
	}
	panic("cannot reach")
}

type Translation struct {
	From string
	To   string
}

func (tr *Translation) unmarshal(dec *xml.Decoder, st stack) error {
	for {
		t, err := dec.Token()
		if err != nil {
			if err == io.EOF {
				return io.ErrUnexpectedEOF
			}
			return err
		}
		switch t := t.(type) {
		case xml.ProcInst:
		case xml.Directive:
		case xml.StartElement:
			st = st.push(t.Name.Local)
		case xml.CharData:
			if st.empty() {
				continue
			}
			switch name := st.peek(0); name {
			case "From":
				tr.From = string(t)
			case "To":
				tr.To = string(t)
			case "TranslationSet", "Translation":
			default:
				return fmt.Errorf("entrez: unknown name: %q", name)
			}
		case xml.EndElement:
			st, err = st.pair(t.Name.Local)
			if err != nil {
				return err
			}
			if st.empty() {
				return nil
			}
		}
	}
	panic("cannot reach")
}

// A Node is an element of the ESearch translation stack.
type Node interface {
	Consume([]Node) Node
}

// A Search holds the deserialised results of an ESearch request.
type Search struct {
	Database         string
	Count            int
	RetMax           int
	RetStart         int
	History          *History
	IdList           []int
	Translations     []Translation
	TranslationStack []Node
	QueryTranslation *string
	Err              error
	Errors           []error
	Warnings         []error
}

// Unmarshal fills the fields of a Search from an XML stream read from r.
func (s *Search) Unmarshal(r io.Reader) error {
	dec := xml.NewDecoder(r)
	var st stack
	for {
		t, err := dec.Token()
		if err != nil {
			if err != io.EOF {
				return err
			}
			if !st.empty() {
				return io.ErrUnexpectedEOF
			}
			break
		}
		switch t := t.(type) {
		case xml.ProcInst:
		case xml.Directive:
		case xml.StartElement:
			st = st.push(t.Name.Local)
			switch t.Name.Local {
			case "Translation":
				var tr Translation
				err := tr.unmarshal(dec, st[len(st)-1:])
				if (tr != Translation{}) {
					s.Translations = append(s.Translations, tr)
				}
				if err != nil {
					return err
				}
				st = st.drop()
			case "TermSet":
				var tm Term
				err := tm.unmarshal(dec, st[len(st)-1:])
				if (tm != Term{}) {
					s.TranslationStack = append(s.TranslationStack, tm)
				}
				if err != nil {
					return err
				}
				st = st.drop()
			}
		case xml.CharData:
			if st.empty() {
				continue
			}
			switch name := st.peek(0); name {
			case "Count":
				c, err := strconv.Atoi(string(t))
				if err != nil {
					return err
				}
				s.Count = c
			case "RetMax":
				m, err := strconv.Atoi(string(t))
				if err != nil {
					return err
				}
				s.RetMax = m
			case "RetStart":
				st, err := strconv.Atoi(string(t))
				if err != nil {
					return err
				}
				s.RetStart = st
			case "QueryKey":
				if s.History == nil {
					s.History = &History{}
				}
				k, err := strconv.Atoi(string(t))
				if err != nil {
					return err
				}
				s.History.QueryKey = k
			case "WebEnv":
				if s.History == nil {
					s.History = &History{}
				}
				s.History.WebEnv = string(t)
			case "Id":
				id, err := strconv.Atoi(string(t))
				if err != nil {
					return err
				}
				s.IdList = append(s.IdList, id)
			case "OP":
				o := Op(string(t))
				if o.Consume(s.TranslationStack) == nil {
					return fmt.Errorf("entrez: illegal operator: %q", o)
				}
				s.TranslationStack = append(s.TranslationStack, o)
			case "QueryTranslation":
				st := string(t)
				s.QueryTranslation = &st
			case "ERROR":
				s.Err = Error(string(t))
			case "PhraseNotFound", "FieldNotFound":
				if st.peek(1) != "ErrorList" {
					return fmt.Errorf("entrez: unexpected tag: %q", name)
				}
				switch name {
				case "PhraseNotFound":
					s.Errors = append(s.Errors, NotFoundError{Type: phraseNotFound, Value: string(t)})
				case "FieldNotFound":
					s.Errors = append(s.Errors, NotFoundError{Type: fieldNotFound, Value: string(t)})
				}
			case "PhraseIgnored", "QuotedPhraseNotFound", "OutputMessage":
				if st.peek(1) != "WarningList" {
					return fmt.Errorf("entrez: unexpected tag: %q", name)
				}
				switch name {
				case "PhraseIgnored":
					s.Warnings = append(s.Warnings, Warning{Type: phraseIgnored, Value: string(t)})
				case "QuotedPhraseNotFound":
					s.Warnings = append(s.Warnings, Warning{Type: quotedPhraseNotFound, Value: string(t)})
				case "OutputMessage":
					s.Warnings = append(s.Warnings, Warning{Type: outputMessage, Value: string(t)})
				}
			case "eSearchResult", "IdList", "TranslationSet", "TranslationStack", "ErrorList", "WarningList":
			default:
				return fmt.Errorf("entrez: unknown name: %q", name)
			}
		case xml.EndElement:
			st, err = st.pair(t.Name.Local)
			if err != nil {
				return err
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package store

import "io"

// Searcher is an interface for the different searcher params
type Searcher interface {
	// expr returns a sqlite snippet used in the WHERE clause.
	Expr() string
	// params returns the required parameters for the sqlite snippet.
	Params() []interface{}
}

// between is a searcher which searches for values between (inclusive) the
// given values.
type between struct {
	col      string
	from, to int
}

// Between returns a Searcher that looks for an integer between two values
func Between(column string, from, to int) Searcher {
	return between{column, from, to}
}

func (b between) Expr() string {
	return "[" + b.col + "] BETWEEN ? AND ?"
}

func (b between) Params() []interface{} {
	return []interface{}{b.from, b.to}
}

// like implements a search which uses the LIKE syntax in a WHERE clause.
type like struct {
	col, likeStr string
}

// Like returns a Searcher that looks for similar strings
func Like(column, likeStr string) Searcher {
	return like{column, likeStr}
}

func (l like) Expr() string {
	return "[" + l.col + "] LIKE ?"
}

func (l like) Params() []interface{} {
	return []interface{}{l.likeStr}
}

type notLike struct {
	col, notLikeStr string
}

// NotLike returns a Searcher that excludes results with similar strings
func NotLike(column, notLikeStr string) Searcher {
	return notLike{column, notLikeStr}
}

func (n notLike) Expr() string {
	return "[" + n.col + "] NOT LIKE ?"
}

func (n notLike) Params() []interface{} {
	return []interface{}{n.notLikeStr}
}

type matchString struct {
	col, match string
}

// MatchString searches for an exact (case-insensitive) string match on the given column
func MatchString(column, match string) Searcher {
	return matchString{column, match}
}

func (m matchString) Expr() string {
	return "[" + m.col + "] = ?"
}

func (m matchString) Params() []interface{} {
	return []interface{}{m.match}
}

type matchInt struct {
	col   string
	match int
}

// MatchInt searches for an exact integer match on the given column
func MatchInt(column string, match int) Searcher {
	return matchInt{column, match}
}

func (m matchInt) Expr() string {
	return "[" + m.col + "] = ?"
}

func (m matchInt) Params() []interface{} {
	return []interface{}{m.match}
}

type or []Searcher

// Or returns a Searcher that matches for any of the given searchers
func Or(searchers ...Searcher) Searcher {
	return or(searchers)
}

func (o or) Expr() string {
	expr := ""
	for n, searcher := range o {
		if n > 0 {
			expr += " OR "
		}
		expr += searcher.Expr()
	}
	return expr
}

func (o or) Params() []interface{} {
	p := make([]interface{}, 0, len(o))
	for _, searcher := range o {
		p = append(p, searcher.Params())
	}
	return p
}

// Search is used for a custom (non primary key) search on a table.
//
// Returns the number of items found and an error if any occurred.
func (s *Store) Search(data []Interface, offset int, params ...Searcher) (int, error) {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	if len(data) == 0 {
		return 0, nil
	}
	table := tableName(data[0])
	cols := data[0].Get()
	vars := make([]string, 0, len(cols))
	first := true
	columns := ""
	for col := range cols {
		if first {
			first = false
		} else {
			columns += ", "
		}
		columns += "[" + col + "]"
		vars = append(vars, col)
	}
	var clause string
	paramVars := make([]interface{}, 0, len(params)+2)
	if len(params) > 0 {
		clause = "WHERE "
		first = true
		for _, param := range params {
			if first {
				first = false
			} else {
				clause += " AND "
			}
			clause += param.Expr()
			paramVars = append(paramVars, param.Params()...)
		}
	}
	var sortBy, dir string
	if s, ok := data[0].(sort); ok {
		sortBy = s.sortBy
		if s.asc {
			dir = "ASC"
		} else {
			dir = "DESC"
		}
	} else {
		sortBy = data[0].Key()
		dir = "ASC"
	}
	sql := "SELECT " + columns + " FROM [" + table + "] " + clause + " ORDER BY [" + sortBy + "] " + dir + " LIMIT ? OFFSET ?;"
	stmt, err := s.db.Query(sql, append(paramVars, len(data), offset)...)
	stmtVars := statement{Stmt: stmt, vars: vars}
	pos := 0
	for ; err == nil; err = stmt.Next() {
		if typeName := tableName(data[pos]); typeName != table {
			err = UnmatchedType{table, typeName}
		} else {
			err = stmtVars.Scan(stmtVars.Vars(data[pos].Get())...)
			pos++
		}
	}
	if err != nil && err != io.EOF {
		return pos, err
	}

	return pos, nil
}

// SearchCount performs the search and returns the number of results
func (s *Store) SearchCount(data Interface, params ...Searcher) (int, error) {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	table := tableName(data)
	first := true
	var clause string
	paramVars := make([]interface{}, 0, len(params))
	if len(params) > 0 {
		clause = "WHERE "
		for _, param := range params {
			if first {
				first = false
			} else {
				clause += " AND "
			}
			clause += param.Expr()
			paramVars = append(paramVars, param.Params()...)
		}
	}
	sql := "SELECT count(1) FROM [" + table + "] " + clause + ";"
	stmt, err := s.db.Query(sql, paramVars...)
	if err != nil {
		return 0, err
	}
	count := 0
	err = stmt.Scan(&count)
	if err != nil && err != io.EOF {
		return 0, err
	}
	return count, nil
}

//Errors

// UnknownColumn is an error that occurrs when a search parameter requires a
// column which does not exist for the given type.
type UnknownColumn string

func (u UnknownColumn) Error() string {
	return "unknown column: " + string(u)
}
<commit_msg>Added parenthesis for Or<commit_after>package store

import "io"

// Searcher is an interface for the different searcher params
type Searcher interface {
	// expr returns a sqlite snippet used in the WHERE clause.
	Expr() string
	// params returns the required parameters for the sqlite snippet.
	Params() []interface{}
}

// between is a searcher which searches for values between (inclusive) the
// given values.
type between struct {
	col      string
	from, to int
}

// Between returns a Searcher that looks for an integer between two values
func Between(column string, from, to int) Searcher {
	return between{column, from, to}
}

func (b between) Expr() string {
	return "[" + b.col + "] BETWEEN ? AND ?"
}

func (b between) Params() []interface{} {
	return []interface{}{b.from, b.to}
}

// like implements a search which uses the LIKE syntax in a WHERE clause.
type like struct {
	col, likeStr string
}

// Like returns a Searcher that looks for similar strings
func Like(column, likeStr string) Searcher {
	return like{column, likeStr}
}

func (l like) Expr() string {
	return "[" + l.col + "] LIKE ?"
}

func (l like) Params() []interface{} {
	return []interface{}{l.likeStr}
}

type notLike struct {
	col, notLikeStr string
}

// NotLike returns a Searcher that excludes results with similar strings
func NotLike(column, notLikeStr string) Searcher {
	return notLike{column, notLikeStr}
}

func (n notLike) Expr() string {
	return "[" + n.col + "] NOT LIKE ?"
}

func (n notLike) Params() []interface{} {
	return []interface{}{n.notLikeStr}
}

type matchString struct {
	col, match string
}

// MatchString searches for an exact (case-insensitive) string match on the given column
func MatchString(column, match string) Searcher {
	return matchString{column, match}
}

func (m matchString) Expr() string {
	return "[" + m.col + "] = ?"
}

func (m matchString) Params() []interface{} {
	return []interface{}{m.match}
}

type matchInt struct {
	col   string
	match int
}

// MatchInt searches for an exact integer match on the given column
func MatchInt(column string, match int) Searcher {
	return matchInt{column, match}
}

func (m matchInt) Expr() string {
	return "[" + m.col + "] = ?"
}

func (m matchInt) Params() []interface{} {
	return []interface{}{m.match}
}

type or []Searcher

// Or returns a Searcher that matches for any of the given searchers
func Or(searchers ...Searcher) Searcher {
	return or(searchers)
}

func (o or) Expr() string {
	expr := "( "
	for n, searcher := range o {
		if n > 0 {
			expr += " OR "
		}
		expr += searcher.Expr()
	}
	expr = +" )"
	return expr
}

func (o or) Params() []interface{} {
	p := make([]interface{}, 0, len(o))
	for _, searcher := range o {
		p = append(p, searcher.Params())
	}
	return p
}

// Search is used for a custom (non primary key) search on a table.
//
// Returns the number of items found and an error if any occurred.
func (s *Store) Search(data []Interface, offset int, params ...Searcher) (int, error) {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	if len(data) == 0 {
		return 0, nil
	}
	table := tableName(data[0])
	cols := data[0].Get()
	vars := make([]string, 0, len(cols))
	first := true
	columns := ""
	for col := range cols {
		if first {
			first = false
		} else {
			columns += ", "
		}
		columns += "[" + col + "]"
		vars = append(vars, col)
	}
	var clause string
	paramVars := make([]interface{}, 0, len(params)+2)
	if len(params) > 0 {
		clause = "WHERE "
		first = true
		for _, param := range params {
			if first {
				first = false
			} else {
				clause += " AND "
			}
			clause += param.Expr()
			paramVars = append(paramVars, param.Params()...)
		}
	}
	var sortBy, dir string
	if s, ok := data[0].(sort); ok {
		sortBy = s.sortBy
		if s.asc {
			dir = "ASC"
		} else {
			dir = "DESC"
		}
	} else {
		sortBy = data[0].Key()
		dir = "ASC"
	}
	sql := "SELECT " + columns + " FROM [" + table + "] " + clause + " ORDER BY [" + sortBy + "] " + dir + " LIMIT ? OFFSET ?;"
	stmt, err := s.db.Query(sql, append(paramVars, len(data), offset)...)
	stmtVars := statement{Stmt: stmt, vars: vars}
	pos := 0
	for ; err == nil; err = stmt.Next() {
		if typeName := tableName(data[pos]); typeName != table {
			err = UnmatchedType{table, typeName}
		} else {
			err = stmtVars.Scan(stmtVars.Vars(data[pos].Get())...)
			pos++
		}
	}
	if err != nil && err != io.EOF {
		return pos, err
	}

	return pos, nil
}

// SearchCount performs the search and returns the number of results
func (s *Store) SearchCount(data Interface, params ...Searcher) (int, error) {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	table := tableName(data)
	first := true
	var clause string
	paramVars := make([]interface{}, 0, len(params))
	if len(params) > 0 {
		clause = "WHERE "
		for _, param := range params {
			if first {
				first = false
			} else {
				clause += " AND "
			}
			clause += param.Expr()
			paramVars = append(paramVars, param.Params()...)
		}
	}
	sql := "SELECT count(1) FROM [" + table + "] " + clause + ";"
	stmt, err := s.db.Query(sql, paramVars...)
	if err != nil {
		return 0, err
	}
	count := 0
	err = stmt.Scan(&count)
	if err != nil && err != io.EOF {
		return 0, err
	}
	return count, nil
}

//Errors

// UnknownColumn is an error that occurrs when a search parameter requires a
// column which does not exist for the given type.
type UnknownColumn string

func (u UnknownColumn) Error() string {
	return "unknown column: " + string(u)
}
<|endoftext|>