mirror of
https://github.com/documize/community.git
synced 2025-07-19 13:19:43 +02:00
Enable Full-text Search when using SQL Server 2016+
This commit is contained in:
parent
0524a0c74c
commit
5004e5a85e
10 changed files with 1360 additions and 1199 deletions
10
README.md
10
README.md
|
@ -30,8 +30,10 @@ All you need to provide is PostgreSQL, Microsoft SQL Server or any MySQL variant
|
||||||
|
|
||||||
## Database Support
|
## Database Support
|
||||||
|
|
||||||
|
For all database types, Full-Text Search support (FTS) is mandatory.
|
||||||
|
|
||||||
- PostgreSQL (v9.6+)
|
- PostgreSQL (v9.6+)
|
||||||
- Microsoft SQL Server (2016+)
|
- Microsoft SQL Server (2016+ with FTS)
|
||||||
- MySQL (v5.7.10+ and v8.0.12+)
|
- MySQL (v5.7.10+ and v8.0.12+)
|
||||||
- Percona (v5.7.16-10+)
|
- Percona (v5.7.16-10+)
|
||||||
- MariaDB (10.3.0+)
|
- MariaDB (10.3.0+)
|
||||||
|
@ -48,8 +50,8 @@ All you need to provide is PostgreSQL, Microsoft SQL Server or any MySQL variant
|
||||||
|
|
||||||
## Technology Stack
|
## Technology Stack
|
||||||
|
|
||||||
- Go (v1.12.9)
|
- Go (v1.13.0)
|
||||||
- Ember JS (v3.12.0)
|
- EmberJS (v3.12.0)
|
||||||
|
|
||||||
## Authentication Options
|
## Authentication Options
|
||||||
|
|
||||||
|
@ -60,7 +62,7 @@ Besides email/password login, you can also authenticate via:
|
||||||
* Red Hat Keycloak
|
* Red Hat Keycloak
|
||||||
* Central Authentication Service (CAS)
|
* Central Authentication Service (CAS)
|
||||||
|
|
||||||
When using LDAP/Active Directory, you can optionally also authenticate with email/password.
|
When using LDAP/Active Directory, you can enable dual-authentication with email/password.
|
||||||
|
|
||||||
## The Legal Bit
|
## The Legal Bit
|
||||||
|
|
||||||
|
|
|
@ -77,35 +77,42 @@ func InstallUpgrade(runtime *env.Runtime, existingDB bool) (err error) {
|
||||||
runtime.Log.Info(fmt.Sprintf("Database: legacy schema has %d scripts to process", len(toProcess)))
|
runtime.Log.Info(fmt.Sprintf("Database: legacy schema has %d scripts to process", len(toProcess)))
|
||||||
}
|
}
|
||||||
|
|
||||||
tx, err := runtime.Db.Beginx()
|
err = runScripts(runtime, toProcess)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = runScripts(runtime, tx, toProcess)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.Log.Error("Database: error processing SQL scripts", err)
|
runtime.Log.Error("Database: error processing SQL scripts", err)
|
||||||
tx.Rollback()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.Commit()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run SQL scripts to instal or upgrade this database.
|
// Run SQL scripts to instal or upgrade this database.
|
||||||
func runScripts(runtime *env.Runtime, tx *sqlx.Tx, scripts []Script) (err error) {
|
// We do not use transactions for Microsoft SQL Server because
|
||||||
|
// CREATE FULLTEXT CATALOG statement cannot be used inside a user transaction.
|
||||||
|
func runScripts(runtime *env.Runtime, scripts []Script) (err error) {
|
||||||
|
tx, err := runtime.Db.Beginx()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// We can have multiple scripts as each Documize database change has it's own SQL script.
|
// We can have multiple scripts as each Documize database change has it's own SQL script.
|
||||||
for _, script := range scripts {
|
for _, script := range scripts {
|
||||||
runtime.Log.Info(fmt.Sprintf("Database: processing SQL script %d", script.Version))
|
runtime.Log.Info(fmt.Sprintf("Database: processing SQL script %d", script.Version))
|
||||||
|
|
||||||
err = executeSQL(tx, runtime.StoreProvider.Type(), runtime.StoreProvider.TypeVariant(), script.Script)
|
err = executeSQL(tx, runtime, script.Script)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.Log.Error(fmt.Sprintf("error executing SQL script %d", script.Version), err)
|
runtime.Log.Error(fmt.Sprintf("error executing SQL script %d", script.Version), err)
|
||||||
|
if runtime.StoreProvider.Type() != env.StoreTypeSQLServer {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record the fact we have processed this database script version.
|
// Record the fact we have processed this database script version.
|
||||||
_, err = tx.Exec(runtime.StoreProvider.QueryRecordVersionUpgrade(script.Version))
|
if runtime.StoreProvider.Type() != env.StoreTypeSQLServer {
|
||||||
|
_, err = tx.Exec(runtime.StoreProvider.QueryRecordVersionUpgrade(script.Version))
|
||||||
|
} else {
|
||||||
|
_, err = runtime.Db.Exec(runtime.StoreProvider.QueryRecordVersionUpgrade(script.Version))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// For MySQL we try the legacy DB schema.
|
// For MySQL we try the legacy DB schema.
|
||||||
if runtime.StoreProvider.Type() == env.StoreTypeMySQL {
|
if runtime.StoreProvider.Type() == env.StoreTypeMySQL {
|
||||||
|
@ -114,31 +121,45 @@ func runScripts(runtime *env.Runtime, tx *sqlx.Tx, scripts []Script) (err error)
|
||||||
_, err = tx.Exec(runtime.StoreProvider.QueryRecordVersionUpgradeLegacy(script.Version))
|
_, err = tx.Exec(runtime.StoreProvider.QueryRecordVersionUpgradeLegacy(script.Version))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.Log.Error(fmt.Sprintf("error recording execution of SQL script %d", script.Version), err)
|
runtime.Log.Error(fmt.Sprintf("error recording execution of SQL script %d", script.Version), err)
|
||||||
|
if runtime.StoreProvider.Type() != env.StoreTypeSQLServer {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Unknown issue running script on non-MySQL database.
|
// Unknown issue running script on non-MySQL database.
|
||||||
runtime.Log.Error(fmt.Sprintf("error executing SQL script %d", script.Version), err)
|
runtime.Log.Error(fmt.Sprintf("error executing SQL script %d", script.Version), err)
|
||||||
|
if runtime.StoreProvider.Type() != env.StoreTypeSQLServer {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tx.Commit()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// executeSQL runs specified SQL commands.
|
// executeSQL runs specified SQL commands.
|
||||||
func executeSQL(tx *sqlx.Tx, st env.StoreType, variant env.StoreType, SQLfile []byte) error {
|
func executeSQL(tx *sqlx.Tx, runtime *env.Runtime, SQLfile []byte) error {
|
||||||
// Turn SQL file contents into runnable SQL statements.
|
// Turn SQL file contents into runnable SQL statements.
|
||||||
stmts := getStatements(SQLfile)
|
stmts := getStatements(SQLfile)
|
||||||
|
|
||||||
for _, stmt := range stmts {
|
for _, stmt := range stmts {
|
||||||
// MariaDB has no specific JSON column type (but has JSON queries)
|
// MariaDB has no specific JSON column type (but has JSON queries)
|
||||||
if st == env.StoreTypeMySQL && variant == env.StoreTypeMariaDB {
|
if runtime.StoreProvider.Type() == env.StoreTypeMySQL &&
|
||||||
|
runtime.StoreProvider.TypeVariant() == env.StoreTypeMariaDB {
|
||||||
stmt = strings.Replace(stmt, "` JSON", "` TEXT", -1)
|
stmt = strings.Replace(stmt, "` JSON", "` TEXT", -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := tx.Exec(stmt)
|
var err error
|
||||||
|
if runtime.StoreProvider.Type() != env.StoreTypeSQLServer {
|
||||||
|
_, err = tx.Exec(stmt)
|
||||||
|
} else {
|
||||||
|
_, err = runtime.Db.Exec(stmt)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("sql statement error:", stmt)
|
fmt.Println("sql statement error:", stmt)
|
||||||
return err
|
return err
|
||||||
|
|
16
core/database/scripts/sqlserver/db_00004.sql
Normal file
16
core/database/scripts/sqlserver/db_00004.sql
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/* Enterprise edition */
|
||||||
|
|
||||||
|
-- Fulltext search support
|
||||||
|
IF EXISTS (SELECT * FROM sysfulltextcatalogs ftc WHERE ftc.name = N'dmz_search_catalog')
|
||||||
|
DROP FULLTEXT CATALOG dmz_search_catalog;
|
||||||
|
|
||||||
|
CREATE FULLTEXT CATALOG dmz_search_catalog;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX idx_doc_4 ON dmz_doc(c_refid);
|
||||||
|
CREATE UNIQUE INDEX idx_section_4 ON dmz_section(c_refid);
|
||||||
|
|
||||||
|
CREATE FULLTEXT INDEX ON dmz_doc (c_name, c_desc) KEY INDEX idx_doc_4 ON dmz_search_catalog
|
||||||
|
WITH CHANGE_TRACKING AUTO;
|
||||||
|
|
||||||
|
CREATE FULLTEXT INDEX ON dmz_section (c_name, c_body) KEY INDEX idx_section_4 ON dmz_search_catalog
|
||||||
|
WITH CHANGE_TRACKING AUTO;
|
|
@ -38,8 +38,10 @@ func (h *Handler) Reindex(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.Runtime.Log.Info("Building search index")
|
if h.Runtime.StoreProvider.Type() != env.StoreTypeSQLServer {
|
||||||
go h.Indexer.Rebuild(ctx)
|
h.Runtime.Log.Info("Building search index")
|
||||||
|
go h.Indexer.Rebuild(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
response.WriteEmpty(w)
|
response.WriteEmpty(w)
|
||||||
}
|
}
|
||||||
|
|
|
@ -313,8 +313,7 @@ func (s Store) matchFullText(ctx domain.RequestContext, keywords, itemType strin
|
||||||
ctx.OrgID,
|
ctx.OrgID,
|
||||||
ctx.UserID,
|
ctx.UserID,
|
||||||
ctx.OrgID,
|
ctx.OrgID,
|
||||||
ctx.UserID,
|
ctx.UserID)
|
||||||
keywords)
|
|
||||||
|
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
err = nil
|
err = nil
|
||||||
|
|
|
@ -13,10 +13,8 @@ package search
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/documize/community/core/stringutil"
|
|
||||||
"github.com/documize/community/domain"
|
"github.com/documize/community/domain"
|
||||||
"github.com/documize/community/domain/store"
|
"github.com/documize/community/domain/store"
|
||||||
"github.com/documize/community/model/attachment"
|
"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
|
// IndexDocument adds search index entries for document inserting title, tags and attachments as
|
||||||
// searchable items. Any existing document entries are removed.
|
// searchable items. Any existing document entries are removed.
|
||||||
func (s StoreSQLServer) IndexDocument(ctx domain.RequestContext, doc doc.Document, a []attachment.Attachment) (err error) {
|
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
|
// // 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')"),
|
// _, 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)
|
// ctx.OrgID, doc.RefID)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
// if err != nil && err != sql.ErrNoRows {
|
||||||
err = errors.Wrap(err, "execute delete document index entries")
|
// err = errors.Wrap(err, "execute delete document index entries")
|
||||||
s.Runtime.Log.Error(method, err)
|
// s.Runtime.Log.Error(method, err)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
// insert doc title
|
// // insert doc title
|
||||||
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_search (c_orgid, c_docid, c_itemid, c_itemtype, c_content) VALUES (?, ?, ?, ?, ?)"),
|
// _, 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)
|
// ctx.OrgID, doc.RefID, "", "doc", doc.Name)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
// if err != nil && err != sql.ErrNoRows {
|
||||||
err = errors.Wrap(err, "execute insert document title entry")
|
// err = errors.Wrap(err, "execute insert document title entry")
|
||||||
s.Runtime.Log.Error(method, err)
|
// s.Runtime.Log.Error(method, err)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
// insert doc tags
|
// // insert doc tags
|
||||||
tags := strings.Split(doc.Tags, "#")
|
// tags := strings.Split(doc.Tags, "#")
|
||||||
for _, t := range tags {
|
// for _, t := range tags {
|
||||||
t = strings.TrimSpace(t)
|
// t = strings.TrimSpace(t)
|
||||||
if len(t) == 0 {
|
// if len(t) == 0 {
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
|
|
||||||
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_search (c_orgid, c_docid, c_itemid, c_itemtype, c_content) VALUES (?, ?, ?, ?, ?)"),
|
// _, 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)
|
// ctx.OrgID, doc.RefID, "", "tag", t)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
// if err != nil && err != sql.ErrNoRows {
|
||||||
err = errors.Wrap(err, "execute insert document tag entry")
|
// err = errors.Wrap(err, "execute insert document tag entry")
|
||||||
s.Runtime.Log.Error(method, err)
|
// s.Runtime.Log.Error(method, err)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
for _, file := range a {
|
// 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 (?, ?, ?, ?, ?)"),
|
// _, 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)
|
// ctx.OrgID, doc.RefID, file.RefID, "file", file.Filename)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
// if err != nil && err != sql.ErrNoRows {
|
||||||
err = errors.Wrap(err, "execute insert document file entry")
|
// err = errors.Wrap(err, "execute insert document file entry")
|
||||||
s.Runtime.Log.Error(method, err)
|
// s.Runtime.Log.Error(method, err)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteDocument removes all search entries for document.
|
// DeleteDocument removes all search entries for document.
|
||||||
func (s StoreSQLServer) DeleteDocument(ctx domain.RequestContext, ID string) (err error) {
|
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=?"),
|
// _, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_search WHERE c_orgid=? AND c_docid=?"),
|
||||||
ctx.OrgID, ID)
|
// ctx.OrgID, ID)
|
||||||
|
|
||||||
if err != nil && err != sql.ErrNoRows {
|
// if err != nil && err != sql.ErrNoRows {
|
||||||
err = errors.Wrap(err, "execute delete document entries")
|
// err = errors.Wrap(err, "execute delete document entries")
|
||||||
s.Runtime.Log.Error(method, err)
|
// s.Runtime.Log.Error(method, err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -103,61 +101,61 @@ func (s StoreSQLServer) DeleteDocument(ctx domain.RequestContext, ID string) (er
|
||||||
// IndexContent adds search index entry for document context.
|
// IndexContent adds search index entry for document context.
|
||||||
// Any existing document entries are removed.
|
// Any existing document entries are removed.
|
||||||
func (s StoreSQLServer) IndexContent(ctx domain.RequestContext, p page.Page) (err error) {
|
func (s StoreSQLServer) IndexContent(ctx domain.RequestContext, p page.Page) (err error) {
|
||||||
method := "search.IndexContent"
|
// method := "search.IndexContent"
|
||||||
|
|
||||||
// remove previous search entries
|
// // 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'"),
|
// _, 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)
|
// ctx.OrgID, p.DocumentID, p.RefID)
|
||||||
|
|
||||||
if err != nil && err != sql.ErrNoRows {
|
// if err != nil && err != sql.ErrNoRows {
|
||||||
err = errors.Wrap(err, "execute delete document content entry")
|
// err = errors.Wrap(err, "execute delete document content entry")
|
||||||
s.Runtime.Log.Error(method, err)
|
// s.Runtime.Log.Error(method, err)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
err = nil
|
// err = nil
|
||||||
|
|
||||||
// prepare content
|
// // prepare content
|
||||||
content, err := stringutil.HTML(p.Body).Text(false)
|
// content, err := stringutil.HTML(p.Body).Text(false)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
err = errors.Wrap(err, "search strip HTML failed")
|
// err = errors.Wrap(err, "search strip HTML failed")
|
||||||
s.Runtime.Log.Error(method, err)
|
// s.Runtime.Log.Error(method, err)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
content = strings.TrimSpace(content)
|
// 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 (?, ?, ?, ?, ?)"),
|
// _, 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)
|
// ctx.OrgID, p.DocumentID, p.RefID, "page", content)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
// if err != nil && err != sql.ErrNoRows {
|
||||||
err = errors.Wrap(err, "execute insert section content entry")
|
// err = errors.Wrap(err, "execute insert section content entry")
|
||||||
s.Runtime.Log.Error(method, err)
|
// s.Runtime.Log.Error(method, err)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
err = nil
|
// err = nil
|
||||||
|
|
||||||
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_search (c_orgid, c_docid, c_itemid, c_itemtype, c_content) VALUES (?, ?, ?, ?, ?)"),
|
// _, 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)
|
// ctx.OrgID, p.DocumentID, p.RefID, "page", p.Name)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
// if err != nil && err != sql.ErrNoRows {
|
||||||
err = errors.Wrap(err, "execute insert section title entry")
|
// err = errors.Wrap(err, "execute insert section title entry")
|
||||||
s.Runtime.Log.Error(method, err)
|
// s.Runtime.Log.Error(method, err)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteContent removes all search entries for specific document content.
|
// DeleteContent removes all search entries for specific document content.
|
||||||
func (s StoreSQLServer) DeleteContent(ctx domain.RequestContext, pageID string) (err error) {
|
func (s StoreSQLServer) DeleteContent(ctx domain.RequestContext, pageID string) (err error) {
|
||||||
method := "search.DeleteContent"
|
// method := "search.DeleteContent"
|
||||||
|
|
||||||
// remove all search entries
|
// // remove all search entries
|
||||||
_, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_search WHERE c_orgid=? AND c_itemid=? AND c_itemtype=?"),
|
// _, err = ctx.Transaction.Exec(s.Bind("DELETE FROM dmz_search WHERE c_orgid=? AND c_itemid=? AND c_itemtype=?"),
|
||||||
ctx.OrgID, pageID, "page")
|
// ctx.OrgID, pageID, "page")
|
||||||
|
|
||||||
if err != nil && err != sql.ErrNoRows {
|
// if err != nil && err != sql.ErrNoRows {
|
||||||
err = errors.Wrap(err, "execute delete document content entry")
|
// err = errors.Wrap(err, "execute delete document content entry")
|
||||||
s.Runtime.Log.Error(method, err)
|
// s.Runtime.Log.Error(method, err)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -174,7 +172,7 @@ func (s StoreSQLServer) Documents(ctx domain.RequestContext, q search.QueryOptio
|
||||||
|
|
||||||
// Match doc names
|
// Match doc names
|
||||||
if q.Doc {
|
if q.Doc {
|
||||||
r1, err1 := s.match(ctx, q.Keywords, "doc")
|
r1, err1 := s.matchDoc(ctx, q.Keywords)
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
err = errors.Wrap(err1, "search document names")
|
err = errors.Wrap(err1, "search document names")
|
||||||
return
|
return
|
||||||
|
@ -185,7 +183,7 @@ func (s StoreSQLServer) Documents(ctx domain.RequestContext, q search.QueryOptio
|
||||||
|
|
||||||
// Match doc content
|
// Match doc content
|
||||||
if q.Content {
|
if q.Content {
|
||||||
r2, err2 := s.match(ctx, q.Keywords, "page")
|
r2, err2 := s.matchSection(ctx, q.Keywords)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
err = errors.Wrap(err2, "search document content")
|
err = errors.Wrap(err2, "search document content")
|
||||||
return
|
return
|
||||||
|
@ -194,28 +192,6 @@ func (s StoreSQLServer) Documents(ctx domain.RequestContext, q search.QueryOptio
|
||||||
results = append(results, r2...)
|
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 {
|
if len(results) == 0 {
|
||||||
results = []search.QueryResult{}
|
results = []search.QueryResult{}
|
||||||
}
|
}
|
||||||
|
@ -223,6 +199,110 @@ func (s StoreSQLServer) Documents(ctx domain.RequestContext, q search.QueryOptio
|
||||||
return
|
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) {
|
func (s StoreSQLServer) match(ctx domain.RequestContext, keywords, itemType string) (r []search.QueryResult, err error) {
|
||||||
// LIKE clause does not like quotes!
|
// LIKE clause does not like quotes!
|
||||||
keywords = strings.Replace(keywords, "'", "", -1)
|
keywords = strings.Replace(keywords, "'", "", -1)
|
||||||
|
@ -279,3 +359,4 @@ func (s StoreSQLServer) match(ctx domain.RequestContext, keywords, itemType stri
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
2131
embed/bindata.go
2131
embed/bindata.go
File diff suppressed because one or more lines are too long
|
@ -48,10 +48,12 @@
|
||||||
<i class={{concat "dicon " constants.Icon.Locked}} />
|
<i class={{concat "dicon " constants.Icon.Locked}} />
|
||||||
<div class="name">Authentication</div>
|
<div class="name">Authentication</div>
|
||||||
{{/link-to}}
|
{{/link-to}}
|
||||||
{{#link-to "customize.search" activeClass="selected" class="item" tagName="div"}}
|
{{#unless (eq appMeta.storageProvider constants.StoreProvider.SQLServer)}}
|
||||||
<i class={{concat "dicon " constants.Icon.Search}} />
|
{{#link-to "customize.search" activeClass="selected" class="item" tagName="div"}}
|
||||||
<div class="name">Search</div>
|
<i class={{concat "dicon " constants.Icon.Search}} />
|
||||||
{{/link-to}}
|
<div class="name">Search</div>
|
||||||
|
{{/link-to}}
|
||||||
|
{{/unless}}
|
||||||
{{#if (eq appMeta.edition constants.Product.EnterpriseEdition)}}
|
{{#if (eq appMeta.edition constants.Product.EnterpriseEdition)}}
|
||||||
{{#link-to "customize.audit" activeClass="selected" class="item" tagName="div"}}
|
{{#link-to "customize.audit" activeClass="selected" class="item" tagName="div"}}
|
||||||
<i class={{concat "dicon " constants.Icon.ButtonAction}} />
|
<i class={{concat "dicon " constants.Icon.ButtonAction}} />
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
{{#if (eq appMeta.storageProvider constants.StoreProvider.PostgreSQL)}}
|
{{#if (eq appMeta.storageProvider constants.StoreProvider.PostgreSQL)}}
|
||||||
<div class="syntax">
|
<div class="syntax">
|
||||||
<div class="example">apple | banana</div>
|
<div class="example">apple | banana</div>
|
||||||
<div class="explain">Show results that contain at either word</div>
|
<div class="explain">Show results that contain either word</div>
|
||||||
<div class="example">apple & banana</div>
|
<div class="example">apple & banana</div>
|
||||||
<div class="explain">Show results that contain both words</div>
|
<div class="explain">Show results that contain both words</div>
|
||||||
<div class="example">apple !macintosh</div>
|
<div class="example">apple !macintosh</div>
|
||||||
|
@ -60,6 +60,18 @@
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (eq appMeta.storageProvider constants.StoreProvider.SQLServer)}}
|
{{#if (eq appMeta.storageProvider constants.StoreProvider.SQLServer)}}
|
||||||
|
<div class="syntax">
|
||||||
|
<div class="example">apple OR banana</div>
|
||||||
|
<div class="explain">Show results that contain either word</div>
|
||||||
|
<div class="example">apple AND banana</div>
|
||||||
|
<div class="explain">Show results that contain both words</div>
|
||||||
|
<div class="example">apple AND NOT macintosh</div>
|
||||||
|
<div class="explain">Show results that contain the word "apple" but not "macintosh"</div>
|
||||||
|
<div class="example">"apple*"</div>
|
||||||
|
<div class="explain">Show results that contain words such as "apple", "apples", "applesauce", or "applet"</div>
|
||||||
|
<div class="example">"some words"</div>
|
||||||
|
<div class="explain">Show results that contain the exact phrase "some words" (for example, rows that contain "some words of wisdom" but not "some noise words")</div>
|
||||||
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,8 +3,11 @@
|
||||||
<Ui::UiSpacer @size="300" />
|
<Ui::UiSpacer @size="300" />
|
||||||
<h1 class="color-theme-700">Setup</h1>
|
<h1 class="color-theme-700">Setup</h1>
|
||||||
<p class="color-gray-800">
|
<p class="color-gray-800">
|
||||||
Review the <a href="https://docs.documize.com/s/VzO9ZqMOCgABGyfW/installation-guides/d/V16L08ucxwABhZF6/installation-guide">installation instructions</a>
|
Review the <a href="https://docs.documize.com/s/VzO9ZqMOCgABGyfW/installation-guides/d/V16L08ucxwABhZF6/installation-guide" target="_blank">installation instructions</a>
|
||||||
and recommended <a href="https://docs.documize.com/s/VzO9ZqMOCgABGyfW/installation-guides/d/V2KuM8ICcQABagM5/mysql-specific-database-tuning">database tuning guide</a>
|
and recommended <a href="https://docs.documize.com/s/VzO9ZqMOCgABGyfW/installation-guides/d/V2KuM8ICcQABagM5/database-tuning" target="_blank">database tuning guide</a>
|
||||||
|
</p>
|
||||||
|
<p class="color-gray-800">
|
||||||
|
Please enable Full-Text Search when using Microsoft SQL Server 2016+.
|
||||||
</p>
|
</p>
|
||||||
<Ui::UiSpacer @size="300" />
|
<Ui::UiSpacer @size="300" />
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue