| // 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, |
| } |
| } |