1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-30 02:29:43 +02:00

Enable Full-text Search when using SQL Server 2016+

This commit is contained in:
sauls8t 2019-09-11 19:08:53 +01:00
parent 0524a0c74c
commit 5004e5a85e
10 changed files with 1360 additions and 1199 deletions

View file

@ -13,10 +13,8 @@ package search
import (
"database/sql"
"fmt"
"strings"
"github.com/documize/community/core/stringutil"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/attachment"
@ -35,67 +33,67 @@ type StoreSQLServer struct {
// IndexDocument adds search index entries for document inserting title, tags and attachments as
// searchable items. Any existing document entries are removed.
func (s StoreSQLServer) IndexDocument(ctx domain.RequestContext, doc doc.Document, a []attachment.Attachment) (err error) {
method := "search.IndexDocument"
// method := "search.IndexDocument"
// remove previous search entries
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_search WHERE c_orgid=? AND c_docid=? AND (c_itemtype='doc' OR c_itemtype='file' OR c_itemtype='tag')"),
ctx.OrgID, doc.RefID)
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "execute delete document index entries")
s.Runtime.Log.Error(method, err)
return
}
// // remove previous search entries
// _, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_search WHERE c_orgid=? AND c_docid=? AND (c_itemtype='doc' OR c_itemtype='file' OR c_itemtype='tag')"),
// ctx.OrgID, doc.RefID)
// if err != nil && err != sql.ErrNoRows {
// err = errors.Wrap(err, "execute delete document index entries")
// s.Runtime.Log.Error(method, err)
// return
// }
// insert doc title
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_search (c_orgid, c_docid, c_itemid, c_itemtype, c_content) VALUES (?, ?, ?, ?, ?)"),
ctx.OrgID, doc.RefID, "", "doc", doc.Name)
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "execute insert document title entry")
s.Runtime.Log.Error(method, err)
return
}
// // insert doc title
// _, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_search (c_orgid, c_docid, c_itemid, c_itemtype, c_content) VALUES (?, ?, ?, ?, ?)"),
// ctx.OrgID, doc.RefID, "", "doc", doc.Name)
// if err != nil && err != sql.ErrNoRows {
// err = errors.Wrap(err, "execute insert document title entry")
// s.Runtime.Log.Error(method, err)
// return
// }
// insert doc tags
tags := strings.Split(doc.Tags, "#")
for _, t := range tags {
t = strings.TrimSpace(t)
if len(t) == 0 {
continue
}
// // insert doc tags
// tags := strings.Split(doc.Tags, "#")
// for _, t := range tags {
// t = strings.TrimSpace(t)
// if len(t) == 0 {
// continue
// }
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_search (c_orgid, c_docid, c_itemid, c_itemtype, c_content) VALUES (?, ?, ?, ?, ?)"),
ctx.OrgID, doc.RefID, "", "tag", t)
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "execute insert document tag entry")
s.Runtime.Log.Error(method, err)
return
}
}
// _, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_search (c_orgid, c_docid, c_itemid, c_itemtype, c_content) VALUES (?, ?, ?, ?, ?)"),
// ctx.OrgID, doc.RefID, "", "tag", t)
// if err != nil && err != sql.ErrNoRows {
// err = errors.Wrap(err, "execute insert document tag entry")
// s.Runtime.Log.Error(method, err)
// return
// }
// }
for _, file := range a {
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_search (c_orgid, c_docid, c_itemid, c_itemtype, c_content) VALUES (?, ?, ?, ?, ?)"),
ctx.OrgID, doc.RefID, file.RefID, "file", file.Filename)
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "execute insert document file entry")
s.Runtime.Log.Error(method, err)
return
}
}
// for _, file := range a {
// _, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_search (c_orgid, c_docid, c_itemid, c_itemtype, c_content) VALUES (?, ?, ?, ?, ?)"),
// ctx.OrgID, doc.RefID, file.RefID, "file", file.Filename)
// if err != nil && err != sql.ErrNoRows {
// err = errors.Wrap(err, "execute insert document file entry")
// s.Runtime.Log.Error(method, err)
// return
// }
// }
return nil
}
// DeleteDocument removes all search entries for document.
func (s StoreSQLServer) DeleteDocument(ctx domain.RequestContext, ID string) (err error) {
method := "search.DeleteDocument"
// method := "search.DeleteDocument"
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_search WHERE c_orgid=? AND c_docid=?"),
ctx.OrgID, ID)
// _, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_search WHERE c_orgid=? AND c_docid=?"),
// ctx.OrgID, ID)
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "execute delete document entries")
s.Runtime.Log.Error(method, err)
}
// if err != nil && err != sql.ErrNoRows {
// err = errors.Wrap(err, "execute delete document entries")
// s.Runtime.Log.Error(method, err)
// }
return
}
@ -103,61 +101,61 @@ func (s StoreSQLServer) DeleteDocument(ctx domain.RequestContext, ID string) (er
// IndexContent adds search index entry for document context.
// Any existing document entries are removed.
func (s StoreSQLServer) IndexContent(ctx domain.RequestContext, p page.Page) (err error) {
method := "search.IndexContent"
// method := "search.IndexContent"
// remove previous search entries
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_search WHERE c_orgid=? AND c_docid=? AND c_itemid=? AND c_itemtype='page'"),
ctx.OrgID, p.DocumentID, p.RefID)
// // remove previous search entries
// _, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_search WHERE c_orgid=? AND c_docid=? AND c_itemid=? AND c_itemtype='page'"),
// ctx.OrgID, p.DocumentID, p.RefID)
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "execute delete document content entry")
s.Runtime.Log.Error(method, err)
return
}
err = nil
// if err != nil && err != sql.ErrNoRows {
// err = errors.Wrap(err, "execute delete document content entry")
// s.Runtime.Log.Error(method, err)
// return
// }
// err = nil
// prepare content
content, err := stringutil.HTML(p.Body).Text(false)
if err != nil {
err = errors.Wrap(err, "search strip HTML failed")
s.Runtime.Log.Error(method, err)
return
}
content = strings.TrimSpace(content)
// // prepare content
// content, err := stringutil.HTML(p.Body).Text(false)
// if err != nil {
// err = errors.Wrap(err, "search strip HTML failed")
// s.Runtime.Log.Error(method, err)
// return
// }
// content = strings.TrimSpace(content)
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_search (c_orgid, c_docid, c_itemid, c_itemtype, c_content) VALUES (?, ?, ?, ?, ?)"),
ctx.OrgID, p.DocumentID, p.RefID, "page", content)
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "execute insert section content entry")
s.Runtime.Log.Error(method, err)
return
}
err = nil
// _, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_search (c_orgid, c_docid, c_itemid, c_itemtype, c_content) VALUES (?, ?, ?, ?, ?)"),
// ctx.OrgID, p.DocumentID, p.RefID, "page", content)
// if err != nil && err != sql.ErrNoRows {
// err = errors.Wrap(err, "execute insert section content entry")
// s.Runtime.Log.Error(method, err)
// return
// }
// err = nil
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_search (c_orgid, c_docid, c_itemid, c_itemtype, c_content) VALUES (?, ?, ?, ?, ?)"),
ctx.OrgID, p.DocumentID, p.RefID, "page", p.Name)
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "execute insert section title entry")
s.Runtime.Log.Error(method, err)
return
}
// _, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_search (c_orgid, c_docid, c_itemid, c_itemtype, c_content) VALUES (?, ?, ?, ?, ?)"),
// ctx.OrgID, p.DocumentID, p.RefID, "page", p.Name)
// if err != nil && err != sql.ErrNoRows {
// err = errors.Wrap(err, "execute insert section title entry")
// s.Runtime.Log.Error(method, err)
// return
// }
return nil
}
// DeleteContent removes all search entries for specific document content.
func (s StoreSQLServer) DeleteContent(ctx domain.RequestContext, pageID string) (err error) {
method := "search.DeleteContent"
// method := "search.DeleteContent"
// remove all search entries
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_search WHERE c_orgid=? AND c_itemid=? AND c_itemtype=?"),
ctx.OrgID, pageID, "page")
// // remove all search entries
// _, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_search WHERE c_orgid=? AND c_itemid=? AND c_itemtype=?"),
// ctx.OrgID, pageID, "page")
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, "execute delete document content entry")
s.Runtime.Log.Error(method, err)
return
}
// if err != nil && err != sql.ErrNoRows {
// err = errors.Wrap(err, "execute delete document content entry")
// s.Runtime.Log.Error(method, err)
// return
// }
return nil
}
@ -174,7 +172,7 @@ func (s StoreSQLServer) Documents(ctx domain.RequestContext, q search.QueryOptio
// Match doc names
if q.Doc {
r1, err1 := s.match(ctx, q.Keywords, "doc")
r1, err1 := s.matchDoc(ctx, q.Keywords)
if err1 != nil {
err = errors.Wrap(err1, "search document names")
return
@ -185,7 +183,7 @@ func (s StoreSQLServer) Documents(ctx domain.RequestContext, q search.QueryOptio
// Match doc content
if q.Content {
r2, err2 := s.match(ctx, q.Keywords, "page")
r2, err2 := s.matchSection(ctx, q.Keywords)
if err2 != nil {
err = errors.Wrap(err2, "search document content")
return
@ -194,28 +192,6 @@ func (s StoreSQLServer) Documents(ctx domain.RequestContext, q search.QueryOptio
results = append(results, r2...)
}
// Match doc tags
if q.Tag {
r3, err3 := s.match(ctx, q.Keywords, "tag")
if err3 != nil {
err = errors.Wrap(err3, "search document tag")
return
}
results = append(results, r3...)
}
// Match doc attachments
if q.Attachment {
r4, err4 := s.match(ctx, q.Keywords, "file")
if err4 != nil {
err = errors.Wrap(err4, "search document attachments")
return
}
results = append(results, r4...)
}
if len(results) == 0 {
results = []search.QueryResult{}
}
@ -223,6 +199,110 @@ func (s StoreSQLServer) Documents(ctx domain.RequestContext, q search.QueryOptio
return
}
// Match published documents.
func (s StoreSQLServer) matchDoc(ctx domain.RequestContext, keywords string) (r []search.QueryResult, err error) {
keywords = strings.ToLower(keywords)
sql1 := s.Bind(`SELECT
d.id, d.c_orgid AS orgid, d.c_refid AS documentid, d.c_refid AS itemid, 'doc' AS itemtype,
d.c_spaceid as spaceid, COALESCE(d.c_name,'Unknown') AS document, d.c_tags AS tags,
d.c_desc AS excerpt, d.c_template AS template, d.c_versionid AS versionid,
COALESCE(l.c_name,'Unknown') AS space, d.c_created AS created, d.c_revised AS revised
FROM
dmz_doc d
LEFT JOIN
dmz_space l ON l.c_orgid=d.c_orgid AND l.c_refid = d.c_spaceid
WHERE
d.c_orgid = ?
AND d.c_lifecycle = 1
AND d.c_spaceid IN
(
SELECT c_refid FROM dmz_space WHERE c_orgid=? AND c_refid IN
(
SELECT c_refid from dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space'
UNION ALL
SELECT p.c_refid from dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid WHERE p.c_orgid=? AND p.c_who='role'
AND p.c_location='space' AND (r.c_userid=? OR r.c_userid='0')
)
)
AND (CONTAINS(d.c_name, ?) OR CONTAINS(d.c_desc, ?))`)
err = s.Runtime.Db.Select(&r,
sql1,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.UserID,
ctx.OrgID,
ctx.UserID,
keywords, keywords)
if err == sql.ErrNoRows {
err = nil
r = []search.QueryResult{}
}
return
}
// Match approved section contents.
func (s StoreSQLServer) matchSection(ctx domain.RequestContext, keywords string) (r []search.QueryResult, err error) {
keywords = strings.ToLower(keywords)
sql1 := s.Bind(`SELECT
d.id, d.c_orgid AS orgid, d.c_refid AS documentid, s.c_refid AS itemid, 'page' AS itemtype,
d.c_spaceid as spaceid, COALESCE(d.c_name,'Unknown') AS document, d.c_tags AS tags,
d.c_desc AS excerpt, d.c_template AS template, d.c_versionid AS versionid,
COALESCE(l.c_name,'Unknown') AS space, d.c_created AS created, d.c_revised AS revised
FROM
dmz_doc d
INNER JOIN
dmz_section s ON s.c_docid = d.c_refid
INNER JOIN
dmz_space l ON l.c_orgid=d.c_orgid AND l.c_refid = d.c_spaceid
WHERE
d.c_refid = s.c_docid
AND d.c_orgid = ?
AND d.c_lifecycle = 1
AND s.c_status = 0
AND d.c_spaceid IN
(
SELECT c_refid FROM dmz_space WHERE c_orgid=? AND c_refid IN
(
SELECT c_refid from dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space'
UNION ALL
SELECT p.c_refid from dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid WHERE p.c_orgid=? AND p.c_who='role'
AND p.c_location='space' AND (r.c_userid=? OR r.c_userid='0')
)
)
AND (CONTAINS(s.c_name, ?) OR CONTAINS(s.c_body, ?))`)
err = s.Runtime.Db.Select(&r,
sql1,
ctx.OrgID,
ctx.OrgID,
ctx.OrgID,
ctx.UserID,
ctx.OrgID,
ctx.UserID,
keywords, keywords)
if err == sql.ErrNoRows {
err = nil
r = []search.QueryResult{}
}
return
}
/*
https://docs.microsoft.com/en-us/sql/relational-databases/search/get-started-with-full-text-search?view=sql-server-2017#options
SELECT * FROM dmz_doc WHERE CONTAINS(c_name, 'release AND v2.0*');
SELECT * FROM dmz_doc WHERE CONTAINS(c_name, 'release AND NOT v2.0*');
SELECT * FROM dmz_section WHERE CONTAINS(c_name, 'User');
SELECT * FROM dmz_section WHERE CONTAINS(c_body, 'authenticate AND NOT user');
func (s StoreSQLServer) match(ctx domain.RequestContext, keywords, itemType string) (r []search.QueryResult, err error) {
// LIKE clause does not like quotes!
keywords = strings.Replace(keywords, "'", "", -1)
@ -279,3 +359,4 @@ func (s StoreSQLServer) match(ctx domain.RequestContext, keywords, itemType stri
return
}
*/