github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/common/search.go (about)

     1  package common
     2  
     3  import (
     4  	"database/sql"
     5  	"errors"
     6  	"strconv"
     7  
     8  	qgen "github.com/Azareal/Gosora/query_gen"
     9  )
    10  
    11  var RepliesSearch Searcher
    12  
    13  type Searcher interface {
    14  	Query(q string, zones []int) ([]int, error)
    15  }
    16  
    17  // TODO: Implement this
    18  // Note: This is slow compared to something like ElasticSearch and very limited
    19  type SQLSearcher struct {
    20  	queryReplies *sql.Stmt
    21  	queryTopics  *sql.Stmt
    22  	queryRepliesZone *sql.Stmt
    23  	queryTopicsZone *sql.Stmt
    24  	//queryZone    *sql.Stmt
    25  	fuzzyZone *sql.Stmt
    26  }
    27  
    28  // TODO: Support things other than MySQL
    29  // TODO: Use LIMIT?
    30  func NewSQLSearcher(acc *qgen.Accumulator) (*SQLSearcher, error) {
    31  	if acc.GetAdapter().GetName() != "mysql" {
    32  		return nil, errors.New("SQLSearcher only supports MySQL at this time")
    33  	}
    34  	return &SQLSearcher{
    35  		queryReplies: acc.RawPrepare("SELECT tid FROM replies WHERE MATCH(content) AGAINST (? IN BOOLEAN MODE)"),
    36  		queryTopics:  acc.RawPrepare("SELECT tid FROM topics WHERE MATCH(title) AGAINST (? IN BOOLEAN MODE) OR MATCH(content) AGAINST (? IN BOOLEAN MODE)"),
    37  		queryRepliesZone: acc.RawPrepare("SELECT tid FROM replies WHERE MATCH(content) AGAINST (? IN BOOLEAN MODE) AND tid=?"),
    38  		queryTopicsZone:  acc.RawPrepare("SELECT tid FROM topics WHERE (MATCH(title) AGAINST (? IN BOOLEAN MODE) OR MATCH(content) AGAINST (? IN BOOLEAN MODE)) AND parentID=?"),
    39  		//queryZone:    acc.RawPrepare("SELECT topics.tid FROM topics INNER JOIN replies ON topics.tid = replies.tid WHERE (topics.title=? OR (MATCH(topics.title) AGAINST (? IN BOOLEAN MODE) OR MATCH(topics.content) AGAINST (? IN BOOLEAN MODE) OR MATCH(replies.content) AGAINST (? IN BOOLEAN MODE)) OR topics.content=? OR replies.content=?) AND topics.parentID=?"),
    40  		fuzzyZone:    acc.RawPrepare("SELECT topics.tid FROM topics INNER JOIN replies ON topics.tid = replies.tid WHERE (topics.title LIKE ? OR topics.content LIKE ? OR replies.content LIKE ?) AND topics.parentID=?"),
    41  	}, acc.FirstError()
    42  }
    43  
    44  func (s *SQLSearcher) queryAll(q string) ([]int, error) {
    45  	var ids []int
    46  	run := func(stmt *sql.Stmt, q ...interface{}) error {
    47  		rows, e := stmt.Query(q...)
    48  		if e == sql.ErrNoRows {
    49  			return nil
    50  		} else if e != nil {
    51  			return e
    52  		}
    53  		defer rows.Close()
    54  
    55  		for rows.Next() {
    56  			var id int
    57  			if e := rows.Scan(&id); e != nil {
    58  				return e
    59  			}
    60  			ids = append(ids, id)
    61  		}
    62  		return rows.Err()
    63  	}
    64  
    65  	err := run(s.queryReplies, q)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  	err = run(s.queryTopics, q, q)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  	if len(ids) == 0 {
    74  		err = sql.ErrNoRows
    75  	}
    76  	return ids, err
    77  }
    78  
    79  func (s *SQLSearcher) Query(q string, zones []int) (ids []int, err error) {
    80  	if len(zones) == 0 {
    81  		return nil, nil
    82  	}
    83  	run := func(rows *sql.Rows, e error) error {
    84  		/*if e == sql.ErrNoRows {
    85  			return nil
    86  		} else */if e != nil {
    87  			return e
    88  		}
    89  		defer rows.Close()
    90  
    91  		for rows.Next() {
    92  			var id int
    93  			if e := rows.Scan(&id); e != nil {
    94  				return e
    95  			}
    96  			ids = append(ids, id)
    97  		}
    98  		return rows.Err()
    99  	}
   100  
   101  	if len(zones) == 1 {
   102  		//err = run(s.queryZone.Query(q, q, q, q, q,q, zones[0]))
   103  		err = run(s.queryRepliesZone.Query(q, zones[0]))
   104  		if err != nil {
   105  			return nil, err
   106  		}
   107  		err = run(s.queryTopicsZone.Query(q, q,zones[0]))
   108  	} else {
   109  		var zList string
   110  		for _, zone := range zones {
   111  			zList += strconv.Itoa(zone) + ","
   112  		}
   113  		zList = zList[:len(zList)-1]
   114  
   115  		acc := qgen.NewAcc()
   116  		/*stmt := acc.RawPrepare("SELECT topics.tid FROM topics INNER JOIN replies ON topics.tid = replies.tid WHERE (MATCH(topics.title) AGAINST (? IN BOOLEAN MODE) OR MATCH(topics.content) AGAINST (? IN BOOLEAN MODE) OR MATCH(replies.content) AGAINST (? IN BOOLEAN MODE) OR topics.title=? OR topics.content=? OR replies.content=?) AND topics.parentID IN(" + zList + ")")
   117  		if err = acc.FirstError(); err != nil {
   118  			return nil, err
   119  		}*/
   120  		// TODO: Cache common IN counts
   121  		stmt := acc.RawPrepare("SELECT tid FROM topics WHERE (MATCH(topics.title) AGAINST (? IN BOOLEAN MODE) OR MATCH(topics.content) AGAINST (? IN BOOLEAN MODE)) AND parentID IN(" + zList + ")")
   122  		if err = acc.FirstError(); err != nil {
   123  			return nil, err
   124  		}
   125  		err = run(stmt.Query(q, q))
   126  		if err != nil {
   127  			return nil, err
   128  		}
   129  		stmt = acc.RawPrepare("SELECT tid FROM replies WHERE MATCH(replies.content) AGAINST (? IN BOOLEAN MODE) AND tid IN(" + zList + ")")
   130  		if err = acc.FirstError(); err != nil {
   131  			return nil, err
   132  		}
   133  		err = run(stmt.Query(q))
   134  		//err = run(stmt.Query(q, q, q, q, q, q))
   135  	}
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  	if len(ids) == 0 {
   140  		err = sql.ErrNoRows
   141  	}
   142  	return ids, err
   143  }
   144  
   145  // TODO: Implement this
   146  type ElasticSearchSearcher struct {
   147  }
   148  
   149  func NewElasticSearchSearcher() (*ElasticSearchSearcher, error) {
   150  	return &ElasticSearchSearcher{}, nil
   151  }
   152  
   153  func (s *ElasticSearchSearcher) Query(q string, zones []int) ([]int, error) {
   154  	return nil, nil
   155  }