blob: b9be6f49cd4f342159b5dd8743f0e6130afcdef9 [file] [log] [blame]
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package postgres
import (
"context"
"database/sql"
"fmt"
"strings"
"github.com/lib/pq"
"golang.org/x/pkgsite/internal"
"golang.org/x/pkgsite/internal/database"
"golang.org/x/pkgsite/internal/derrors"
"golang.org/x/pkgsite/internal/experiment"
"golang.org/x/pkgsite/internal/postgres/symbolsearch"
)
func upsertSymbolSearchDocuments(ctx context.Context, tx *database.DB,
modulePath, v string) (err error) {
defer derrors.Wrap(&err, "upsertSymbolSearchDocuments(ctx, ddb, %q, %q)", modulePath, v)
if !experiment.IsActive(ctx, internal.ExperimentInsertSymbolSearchDocuments) {
return nil
}
// If a user is looking for the symbol "DB.Begin", from package
// database/sql, we want them to be able to find this by searching for
// "DB.Begin" and "sql.DB.Begin". Searching for "sql.DB", "DB", "Begin" or
// "sql.DB" will not return "DB.Begin".
// If a user is looking for the symbol "DB.Begin", from package
// database/sql, we want them to be able to find this by searching for
// "DB.Begin", "Begin", and "sql.DB.Begin". Searching for "sql.DB" or
// "DB" will not return "DB.Begin".
q := `
INSERT INTO symbol_search_documents (
package_path_id,
symbol_name_id,
unit_id,
package_symbol_id,
goos,
goarch
)
SELECT DISTINCT ON (sd.package_path_id, ps.symbol_name_id)
sd.package_path_id,
ps.symbol_name_id,
sd.unit_id,
ps.id AS package_symbol_id,
d.goos,
d.goarch
FROM search_documents sd
INNER JOIN units u
ON sd.unit_id = u.id
INNER JOIN documentation d
ON d.unit_id = sd.unit_id
INNER JOIN documentation_symbols ds
ON d.id = ds.documentation_id
INNER JOIN package_symbols ps
ON ps.id = ds.package_symbol_id
WHERE
sd.module_path = $1 AND sd.version = $2
AND u.name != 'main' -- do not insert data for commands
ORDER BY
sd.package_path_id,
ps.symbol_name_id,
-- Order should match internal.BuildContexts.
CASE WHEN d.goos = 'all' THEN 0
WHEN d.goos = 'linux' THEN 1
WHEN d.goos = 'windows' THEN 2
WHEN d.goos = 'darwin' THEN 3
WHEN d.goos = 'js' THEN 4
END
ON CONFLICT (package_path_id, symbol_name_id)
DO UPDATE SET
unit_id = excluded.unit_id,
package_symbol_id = excluded.package_symbol_id,
goos = excluded.goos,
goarch = excluded.goarch;`
_, err = tx.Exec(ctx, q, modulePath, v)
return err
}
// symbolSearch searches all symbols in the symbol_search_documents table for
// the query.
//
// TODO(https://golang.org/issue/44142): factor out common code between
// symbolSearch and deepSearch.
func (db *DB) symbolSearch(ctx context.Context, q string, limit, offset, maxResultCount int) searchResponse {
var results []*SearchResult
collect := func(rows *sql.Rows) error {
var r SearchResult
if err := rows.Scan(
&r.PackagePath,
&r.ModulePath,
&r.Version,
&r.Name,
&r.Synopsis,
pq.Array(&r.Licenses),
&r.CommitTime,
&r.NumImportedBy,
&r.SymbolName,
&r.SymbolGOOS,
&r.SymbolGOARCH,
&r.SymbolKind,
&r.SymbolSynopsis,
&r.NumResults); err != nil {
return fmt.Errorf("symbolSearch: rows.Scan(): %v", err)
}
results = append(results, &r)
return nil
}
var (
query string
err error
)
if len(strings.Fields(q)) == 1 {
switch len(strings.Split(q, ".")) {
case 1:
// There is only 1 word in the search, and there are no dots, so
// this must be the symbol name.
query = symbolsearch.QuerySymbol
case 2:
// There is only 1 element, split by 1 dot, so the search must
// either be for <package>.<symbol> or <type>.<methodOrFieldName>.
query = symbolsearch.QueryOneDot
case 3:
// There is only 1 element, split by 2 dots, so the search must
// be for <package>.<type>.<methodOrFieldName>.
query = symbolsearch.QueryPackageDotSymbol
default:
// There is no situation where we will get results for oe element
// containing more than 2 dots.
err = fmt.Errorf("unsupported query structure: %q", q)
}
} else {
// The search query contains multiple words, separated by spaces.
query = symbolsearch.QueryMultiWord
}
if err == nil {
err = db.db.RunQuery(ctx, query, collect, q, limit, offset)
if err != nil {
results = nil
}
if len(results) > 0 && results[0].NumResults > uint64(maxResultCount) {
for _, r := range results {
r.NumResults = uint64(maxResultCount)
}
}
}
return searchResponse{
source: "symbol",
results: results,
err: err,
}
}