diff --git a/sonic/control.go b/sonic/control.go new file mode 100644 index 0000000..0d4155f --- /dev/null +++ b/sonic/control.go @@ -0,0 +1,58 @@ +package sonic + +import ( + "errors" + "fmt" +) + +var InvalidActionName = errors.New("invalid action name") + +// Controllable is used for administration purposes. +type Controllable interface { + // Trigger an action. + // Command syntax TRIGGER []?. + Trigger(action Action) (err error) + + // Quit refer to the Base interface + Quit() (err error) + + // Quit refer to the Base interface + Ping() (err error) +} + +type ControlChannel struct { + *Driver +} + +func NewControl(host string, port int, password string) (Controllable, error) { + driver := &Driver{ + Host: host, + Port: port, + Password: password, + channel: Control, + } + err := driver.Connect() + if err != nil { + return nil, err + } + return ControlChannel{ + Driver: driver, + }, nil +} + +func (c ControlChannel) Trigger(action Action) (err error) { + if IsActionValid(action) { + return InvalidActionName + } + err = c.write(fmt.Sprintf("TRIGGER %s", action)) + if err != nil { + return err + } + + // should get OK + _, err = c.read() + if err != nil { + return err + } + return nil +} diff --git a/sonic/doc.go b/sonic/doc.go new file mode 100644 index 0000000..f9c31c1 --- /dev/null +++ b/sonic/doc.go @@ -0,0 +1,25 @@ +// Package sonic implements all methods to communicate with it and execute commands. +// +// Syntax terminology (from https://github.com/valeriansaliou/sonic/blob/master/PROTOCOL.md): +// +// - collection: index collection (ie. what you search in, eg. messages, products, etc.); +// +// - bucket: index bucket name (ie. user-specific search classifier in the collection if you have any eg. user-1, +//user-2, .., otherwise use a common bucket name eg. generic, default, common, ..); +// +// - terms: text for search terms (between quotes); +// +// - count: a positive integer number; set within allowed maximum & minimum limits; +// +// - object: object identifier that refers to an entity in an external database, where the searched object is stored +// (eg. you use Sonic to index CRM contacts by name; full CRM contact data is stored in a MySQL database; +// in this case the object identifier in Sonic will be the MySQL primary key for the CRM contact); +// +// action: action to be triggered (available actions: consolidate); +// +// +// Notice: the bucket terminology may confuse some Sonic users. As we are well-aware Sonic may be used in an environment +// where end-users may each hold their own search index in a given collection, we made it possible to manage per-end-user +// search indexes with bucket. If you only have a single index per collection (most Sonic users will), we advise you use +// a static generic name for your bucket, for instance: default. +package sonic diff --git a/sonic/driver.go b/sonic/driver.go index 8e183ea..637501d 100644 --- a/sonic/driver.go +++ b/sonic/driver.go @@ -11,38 +11,41 @@ import ( ) var ( - ClosedError = errors.New("sonic connection is closed") - InvalidChanName = errors.New("invalid channel name") - InvalidActionName = errors.New("invalid action name") + ClosedError = errors.New("sonic connection is closed") + InvalidChanName = errors.New("invalid channel name") ) +// Base contains commons commands to all channels. +type Base interface { + // Quit stop connection, you can't execute anything after calling this method. + // Syntax command QUIT + Quit() error + + // Ping ping the sonic server. + // Return an error is there is something wrong. + // If an error occur, the sonic server is maybe down. + // Syntax command PING + Ping() error +} + type Driver struct { Host string Port int Password string - Channel Channel - reader *bufio.Reader - conn net.Conn - closed bool + channel Channel + reader *bufio.Reader + conn net.Conn + closed bool } -func NewControl(host string, port int, password string) (*Driver, error) { - driver := &Driver{ - Host: host, - Port: port, - Password: password, - Channel: Ingest, - } - - return driver, driver.connect() -} - -func (c *Driver) connect() error { - if !IsChannelValid(c.Channel) { +// Connect open a connection via TCP with the sonic server. +func (c *Driver) Connect() error { + if !IsChannelValid(c.channel) { return InvalidChanName } + c.clean() conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", c.Host, c.Port)) if err != nil { return err @@ -50,7 +53,7 @@ func (c *Driver) connect() error { c.conn = conn c.reader = bufio.NewReader(c.conn) - err := c.write(fmt.Sprintf("START %s %s", c.Channel, c.Password)) + err := c.write(fmt.Sprintf("START %s %s", c.channel, c.Password)) if err != nil { return err } @@ -64,6 +67,32 @@ func (c *Driver) connect() error { } } +func (c *Driver) Quit() error { + err := c.write("QUIT") + if err != nil { + return err + } + + // should get ENDED + _, err = c.read() + c.clean() + return err +} + +func (c Driver) Ping() error { + err := c.write("PING") + if err != nil { + return err + } + + // should get PONG + _, err = c.read() + if err != nil { + return err + } + return nil +} + func (c *Driver) read() (string, error) { if c.closed { return "", ClosedError @@ -98,49 +127,6 @@ func (c Driver) write(str string) error { return err } -func (c *Driver) Quit() error { - err := c.write("QUIT") - if err != nil { - return err - } - - // should get ENDED - _, err = c.read() - c.clean() - return err -} - -func (c Driver) Ping() error { - err := c.write("PING") - if err != nil { - return err - } - - // should get PONG - _, err = c.read() - if err != nil { - return err - } - return nil -} - -func (c Driver) Trigger(action Action) error { - if IsActionValid(action) { - return InvalidActionName - } - err := c.write(fmt.Sprintf("TRIGGER %s", action)) - if err != nil { - return err - } - - // should get OK - _, err = c.read() - if err != nil { - return err - } - return nil -} - func (c *Driver) clean() { c.closed = true _ = c.conn.Close() diff --git a/sonic/ingester.go b/sonic/ingester.go index d0288a8..2492d98 100644 --- a/sonic/ingester.go +++ b/sonic/ingester.go @@ -6,14 +6,38 @@ import ( "strings" ) +// Ingestable is used for altering the search index (push, pop and flush). type Ingestable interface { + // Push search data in the index. + // Command syntax PUSH "" Push(collection, bucket, object, text string) (err error) + + // Pop search data from the index. + // Command syntax POP "". Pop(collection, bucket, object, text string) (err error) + + // Count indexed search data. + // bucket and object are optionals, empty string ignore it. + // Command syntax COUNT [ []?]?. Count(collection, bucket, object string) (count int, err error) + // FlushCollection Flush all indexed data from a collection. + // Command syntax FLUSHC . FlushCollection(collection string) (err error) + + // Flush all indexed data from a bucket in a collection. + // Command syntax FLUSHB . FlushBucket(collection, bucket string) (err error) + + // Flush all indexed data from an object in a bucket in collection. + // Command syntax FLUSHO . FlushObject(collection, bucket, object string) (err error) + + // Quit refer to the Base interface + Quit() (err error) + + // Quit refer to the Base interface + Ping() (err error) } type ingesterCommands string @@ -36,9 +60,9 @@ func NewIngester(host string, port int, password string) (Ingestable, error) { Host: host, Port: port, Password: password, - Channel: Ingest, + channel: Ingest, } - err := driver.connect() + err := driver.Connect() if err != nil { return nil, err } diff --git a/sonic/search.go b/sonic/search.go index af3b161..4ad84de 100644 --- a/sonic/search.go +++ b/sonic/search.go @@ -5,9 +5,23 @@ import ( "strings" ) +// Searchable is used for querying the search index. type Searchable interface { - Query(collection, bucket, term string, limit, offset int) (results []string, err error) + + // Query the database, return a list of object, represented as a string. + // Sonic default limit is 10. + // Command syntax QUERY "" [LIMIT()]? [OFFSET()]?. + Query(collection, bucket, terms string, limit, offset int) (results []string, err error) + + // Suggest auto-completes word, return a list of words as a string. + // Command syntax SUGGEST "" [LIMIT()]?. Suggest(collection, bucket, word string, limit int) (results []string, err error) + + // Quit refer to the Base interface + Quit() (err error) + + // Quit refer to the Base interface + Ping() (err error) } type searchCommands string @@ -26,9 +40,9 @@ func NewSearch(host string, port int, password string) (Searchable, error) { Host: host, Port: port, Password: password, - Channel: Search, + channel: Search, } - err := driver.connect() + err := driver.Connect() if err != nil { return nil, err }