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 }