gosora/common/search.go
Azareal 2296008655 Basic search now works for the Search & Filter Widget. ElasticSearch has been temporarily delayed, so I can push through this update.
Added the three month time range to the analytics panes.
Began work on adding new graphs to the analytics panes.
Began work on the ElasticSearch adapter for the search system.
Added the currently limited AddKey method to the database adapters.
Expanded upon the column parsing logic in the database adapters to ease the use of InsertSelects.

Added the BulkGet method to TopicCache.
Added the BulkGetMap method to TopicStore.
TopicStore methods should now properly retrieve lastReplyBy.
Added the panel_analytics_script template to de-dupe part of the analytics logic. We plan to tidy this up further, but for now, it'll suffice.
Added plugin_sendmail and plugin_hyperdrive to the continuous integration test list.
Tweaked the width and heights of the textareas for the Widget Editor.
Added the AddKey method to *qgen.builder
Fixed a bug where using the inline forum editor would crash Gosora and wouldn't set the preset permissions for that forum properly.
Added DotBot to the user agent analytics.
Invisibles should be better handled when they're encountered now in user agent strings.
Unknown language ISO Codes in headers now have the requests fully logged for debugging purposes.
Shortened some of the pointer receiver names.
Shortened some variable names.

Added the dotbot phrase.
Added the panel_statistics_time_range_three_months phrase.

Added gopkg.in/olivere/elastic.v6 as a dependency.

You will need to run the patcher or updater for this commit.
2019-02-23 16:29:19 +10:00

134 lines
3.4 KiB
Go

package common
import (
"database/sql"
"errors"
"strconv"
"github.com/Azareal/Gosora/query_gen"
)
var RepliesSearch Searcher
type Searcher interface {
Query(q string, zones []int) ([]int, error)
}
// TODO: Implement this
// Note: This is slow compared to something like ElasticSearch and very limited
type SQLSearcher struct {
queryReplies *sql.Stmt
queryTopics *sql.Stmt
queryZone *sql.Stmt
}
// TODO: Support things other than MySQL
// TODO: Use LIMIT?
func NewSQLSearcher(acc *qgen.Accumulator) (*SQLSearcher, error) {
if acc.GetAdapter().GetName() != "mysql" {
return nil, errors.New("SQLSearcher only supports MySQL at this time")
}
return &SQLSearcher{
queryReplies: acc.RawPrepare("SELECT `tid` FROM `replies` WHERE MATCH(content) AGAINST (? IN NATURAL LANGUAGE MODE);"),
queryTopics: acc.RawPrepare("SELECT `tid` FROM `topics` WHERE MATCH(title) AGAINST (? IN NATURAL LANGUAGE MODE) OR MATCH(content) AGAINST (? IN NATURAL LANGUAGE MODE);"),
queryZone: acc.RawPrepare("SELECT `topics`.`tid` FROM `topics` INNER JOIN `replies` ON `topics`.`tid` = `replies`.`tid` WHERE (MATCH(`topics`.`title`) AGAINST (? IN NATURAL LANGUAGE MODE) OR MATCH(`topics`.`content`) AGAINST (? IN NATURAL LANGUAGE MODE) OR MATCH(`replies`.`content`) AGAINST (? IN NATURAL LANGUAGE MODE)) AND `topics`.`parentID` = ?;"),
}, acc.FirstError()
}
func (search *SQLSearcher) queryAll(q string) ([]int, error) {
var ids []int
var run = func(stmt *sql.Stmt, q ...interface{}) error {
rows, err := stmt.Query(q...)
if err == sql.ErrNoRows {
return nil
} else if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var id int
err := rows.Scan(&id)
if err != nil {
return err
}
ids = append(ids, id)
}
return rows.Err()
}
err := run(search.queryReplies, q)
if err != nil {
return nil, err
}
err = run(search.queryTopics, q, q)
if err != nil {
return nil, err
}
if len(ids) == 0 {
err = sql.ErrNoRows
}
return ids, err
}
func (search *SQLSearcher) Query(q string, zones []int) (ids []int, err error) {
if len(zones) == 0 {
return nil, nil
}
var run = func(rows *sql.Rows, err error) error {
if err == sql.ErrNoRows {
return nil
} else if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var id int
err := rows.Scan(&id)
if err != nil {
return err
}
ids = append(ids, id)
}
return rows.Err()
}
if len(zones) == 1 {
err = run(search.queryZone.Query(q, q, q, zones[0]))
} else {
var zList string
for _, zone := range zones {
zList += strconv.Itoa(zone) + ","
}
zList = zList[:len(zList)-1]
acc := qgen.NewAcc()
stmt := acc.RawPrepare("SELECT `topics`.`tid` FROM `topics` INNER JOIN `replies` ON `topics`.`tid` = `replies`.`tid` WHERE (MATCH(`topics`.`title`) AGAINST (? IN NATURAL LANGUAGE MODE) OR MATCH(`topics`.`content`) AGAINST (? IN NATURAL LANGUAGE MODE) OR MATCH(`replies`.`content`) AGAINST (? IN NATURAL LANGUAGE MODE)) AND `topics`.`parentID` IN(" + zList + ");")
err := acc.FirstError()
if err != nil {
return nil, err
}
err = run(stmt.Query(q, q, q))
}
if err != nil {
return nil, err
}
if len(ids) == 0 {
err = sql.ErrNoRows
}
return ids, err
}
// TODO: Implement this
type ElasticSearchSearcher struct {
}
func NewElasticSearchSearcher() (*ElasticSearchSearcher, error) {
return &ElasticSearchSearcher{}, nil
}
func (search *ElasticSearchSearcher) Query(q string, zones []int) ([]int, error) {
return nil, nil
}