internal: add an interface for internal/postgres.DB

And use that interface to remove the dependency from internal/frontend
to internal/postgres.

The dependency still exists in the test code. That will be taken care
of in another CL.

For #61399
Change-Id: Ic1b3c257e6eb34858cf31b7596251aaff71d3482
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/510417
kokoro-CI: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jamal Carvalho <jamal@golang.org>
Reviewed-by: Michael Matloob <matloob@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Michael Matloob <matloob@golang.org>
diff --git a/internal/frontend/404.go b/internal/frontend/404.go
index 1a062dd..aea7376 100644
--- a/internal/frontend/404.go
+++ b/internal/frontend/404.go
@@ -22,7 +22,6 @@
 	"golang.org/x/pkgsite/internal/derrors"
 	"golang.org/x/pkgsite/internal/experiment"
 	"golang.org/x/pkgsite/internal/log"
-	"golang.org/x/pkgsite/internal/postgres"
 	"golang.org/x/pkgsite/internal/stdlib"
 	"golang.org/x/pkgsite/internal/version"
 )
@@ -47,7 +46,7 @@
 	ds internal.DataSource, fullPath, modulePath, requestedVersion string) (err error) {
 	defer derrors.Wrap(&err, "servePathNotFoundPage(w, r, %q, %q)", fullPath, requestedVersion)
 
-	db, ok := ds.(*postgres.DB)
+	db, ok := ds.(internal.PostgresDB)
 	if !ok {
 		return datasourceNotSupportedErr()
 	}
@@ -216,7 +215,7 @@
 
 // previousFetchStatusAndResponse returns the fetch result from a
 // previous fetch of the fullPath and requestedVersion.
-func previousFetchStatusAndResponse(ctx context.Context, db *postgres.DB,
+func previousFetchStatusAndResponse(ctx context.Context, db internal.PostgresDB,
 	fullPath, modulePath, requestedVersion string) (_ *fetchResult, err error) {
 	defer derrors.Wrap(&err, "previousFetchStatusAndResponse(w, r, %q, %q)", fullPath, requestedVersion)
 
diff --git a/internal/frontend/fetch.go b/internal/frontend/fetch.go
index 325bfe9..c1050fe 100644
--- a/internal/frontend/fetch.go
+++ b/internal/frontend/fetch.go
@@ -25,7 +25,6 @@
 	"golang.org/x/pkgsite/internal/experiment"
 	"golang.org/x/pkgsite/internal/fetch"
 	"golang.org/x/pkgsite/internal/log"
-	"golang.org/x/pkgsite/internal/postgres"
 	"golang.org/x/pkgsite/internal/proxy"
 	"golang.org/x/pkgsite/internal/queue"
 	"golang.org/x/pkgsite/internal/source"
@@ -93,7 +92,7 @@
 // result of the request.
 func (s *Server) serveFetch(w http.ResponseWriter, r *http.Request, ds internal.DataSource) (err error) {
 	defer derrors.Wrap(&err, "serveFetch(%q)", r.URL.Path)
-	if _, ok := ds.(*postgres.DB); !ok {
+	if _, ok := ds.(internal.PostgresDB); !ok {
 		// There's no reason for other DataSources to need this codepath.
 		return datasourceNotSupportedErr()
 	}
@@ -142,7 +141,7 @@
 	}
 
 	// Generate all possible module paths for the fullPath.
-	db := ds.(*postgres.DB)
+	db := ds.(internal.PostgresDB)
 	modulePaths, err := modulePathsToFetch(ctx, db, fullPath, modulePath)
 	if err != nil {
 		var serr *serverError
@@ -171,7 +170,7 @@
 // checkPossibleModulePaths will then poll the database for each module path,
 // until a result is returned or the request times out. If shouldQueue is false,
 // it will return the fetchResult, regardless of what the status is.
-func (s *Server) checkPossibleModulePaths(ctx context.Context, db *postgres.DB,
+func (s *Server) checkPossibleModulePaths(ctx context.Context, db internal.PostgresDB,
 	fullPath, requestedVersion string, modulePaths []string, shouldQueue bool) []*fetchResult {
 	var wg sync.WaitGroup
 	ctx, cancel := context.WithTimeout(ctx, fetchTimeout)
@@ -336,7 +335,7 @@
 }
 
 // pollForPath polls the database until a row for fullPath is found.
-func pollForPath(ctx context.Context, db *postgres.DB, pollEvery time.Duration,
+func pollForPath(ctx context.Context, db internal.PostgresDB, pollEvery time.Duration,
 	fullPath, modulePath, requestedVersion string, taskIDChangeInterval time.Duration) *fetchResult {
 	fr := &fetchResult{modulePath: modulePath}
 	defer derrors.Wrap(&fr.err, "pollForRedirectURL(%q, %q, %q)", modulePath, fullPath, requestedVersion)
@@ -370,7 +369,7 @@
 // Note that if an error occurs while writing to the version_map table,
 // checkForPath will not know. Instead, it will keep running until the request
 // times out.
-func checkForPath(ctx context.Context, db *postgres.DB,
+func checkForPath(ctx context.Context, db internal.PostgresDB,
 	fullPath, modulePath, requestedVersion string, taskIDChangeInterval time.Duration) (fr *fetchResult) {
 	defer func() {
 		// Based on
@@ -544,7 +543,7 @@
 // worker.FetchAndUpdateState that does not update module_version_states, so that
 // we don't have to import internal/worker here. It is not meant to be used
 // when running on AppEngine.
-func FetchAndUpdateState(ctx context.Context, modulePath, requestedVersion string, proxyClient *proxy.Client, sourceClient *source.Client, db *postgres.DB) (_ int, err error) {
+func FetchAndUpdateState(ctx context.Context, modulePath, requestedVersion string, proxyClient *proxy.Client, sourceClient *source.Client, db internal.PostgresDB) (_ int, err error) {
 	defer func() {
 		if err != nil {
 			log.Infof(ctx, "FetchAndUpdateState(%q, %q) completed with err: %v. ", modulePath, requestedVersion, err)
diff --git a/internal/frontend/imports.go b/internal/frontend/imports.go
index 947275a..0c21b8e 100644
--- a/internal/frontend/imports.go
+++ b/internal/frontend/imports.go
@@ -11,7 +11,6 @@
 	"golang.org/x/pkgsite/internal"
 	"golang.org/x/pkgsite/internal/log"
 	"golang.org/x/pkgsite/internal/middleware"
-	"golang.org/x/pkgsite/internal/postgres"
 	"golang.org/x/pkgsite/internal/stdlib"
 	"golang.org/x/text/message"
 )
@@ -95,7 +94,7 @@
 // fetchImportedByDetails fetches importers for the package version specified by
 // path and version from the database and returns a ImportedByDetails.
 func fetchImportedByDetails(ctx context.Context, ds internal.DataSource, pkgPath, modulePath string) (*ImportedByDetails, error) {
-	db, ok := ds.(*postgres.DB)
+	db, ok := ds.(internal.PostgresDB)
 	if !ok {
 		// The proxydatasource does not support the imported by page.
 		return nil, datasourceNotSupportedErr()
diff --git a/internal/frontend/redirect.go b/internal/frontend/redirect.go
index aa26648..8ce7235 100644
--- a/internal/frontend/redirect.go
+++ b/internal/frontend/redirect.go
@@ -9,8 +9,8 @@
 	"net/http"
 	"strings"
 
+	"golang.org/x/pkgsite/internal"
 	"golang.org/x/pkgsite/internal/derrors"
-	"golang.org/x/pkgsite/internal/postgres"
 	"golang.org/x/pkgsite/internal/stdlib"
 )
 
@@ -28,7 +28,7 @@
 
 // stdlibPathForShortcut returns a path in the stdlib that shortcut should redirect to,
 // or the empty string if there is no such path.
-func stdlibPathForShortcut(ctx context.Context, db *postgres.DB, shortcut string) (path string, err error) {
+func stdlibPathForShortcut(ctx context.Context, db internal.PostgresDB, shortcut string) (path string, err error) {
 	defer derrors.Wrap(&err, "stdlibPathForShortcut(ctx, %q)", shortcut)
 	if !stdlib.Contains(shortcut) {
 		return "", nil
diff --git a/internal/frontend/search.go b/internal/frontend/search.go
index 2ff7833..8ff604a 100644
--- a/internal/frontend/search.go
+++ b/internal/frontend/search.go
@@ -23,7 +23,6 @@
 	"golang.org/x/pkgsite/internal/derrors"
 	"golang.org/x/pkgsite/internal/log"
 	"golang.org/x/pkgsite/internal/middleware"
-	"golang.org/x/pkgsite/internal/postgres"
 	"golang.org/x/pkgsite/internal/stdlib"
 	"golang.org/x/pkgsite/internal/version"
 	"golang.org/x/pkgsite/internal/vuln"
@@ -242,7 +241,7 @@
 
 	// Pageless search: always start from the beginning.
 	offset := 0
-	dbresults, err := ds.Search(ctx, cq, postgres.SearchOptions{
+	dbresults, err := ds.Search(ctx, cq, internal.SearchOptions{
 		MaxResults:     pageParams.limit,
 		Offset:         offset,
 		MaxResultCount: maxResultCount,
@@ -287,7 +286,7 @@
 	return sp, nil
 }
 
-func newSearchResult(r *postgres.SearchResult, searchSymbols bool, pr *message.Printer) *SearchResult {
+func newSearchResult(r *internal.SearchResult, searchSymbols bool, pr *message.Printer) *SearchResult {
 	// For commands, change the name from "main" to the last component of the import path.
 	chipText := ""
 	name := r.Name
@@ -473,7 +472,7 @@
 
 // symbolSynopsis returns the string to be displayed in the code snippet
 // section for a symbol search result.
-func symbolSynopsis(r *postgres.SearchResult) string {
+func symbolSynopsis(r *internal.SearchResult) string {
 	switch r.SymbolKind {
 	case internal.SymbolKindField:
 		return fmt.Sprintf(`
@@ -493,7 +492,7 @@
 	return r.SymbolSynopsis
 }
 
-func packagePaths(heading string, rs []*postgres.SearchResult) *subResult {
+func packagePaths(heading string, rs []*internal.SearchResult) *subResult {
 	if len(rs) == 0 {
 		return nil
 	}
diff --git a/internal/frontend/urlinfo.go b/internal/frontend/urlinfo.go
index 5420dc6..a5dba10 100644
--- a/internal/frontend/urlinfo.go
+++ b/internal/frontend/urlinfo.go
@@ -16,7 +16,6 @@
 	"golang.org/x/pkgsite/internal/derrors"
 	"golang.org/x/pkgsite/internal/experiment"
 	"golang.org/x/pkgsite/internal/log"
-	"golang.org/x/pkgsite/internal/postgres"
 	"golang.org/x/pkgsite/internal/stdlib"
 	"golang.org/x/pkgsite/internal/version"
 )
@@ -190,7 +189,7 @@
 }
 
 func checkExcluded(ctx context.Context, ds internal.DataSource, fullPath string) error {
-	db, ok := ds.(*postgres.DB)
+	db, ok := ds.(internal.PostgresDB)
 	if !ok {
 		return nil
 	}
diff --git a/internal/frontend/versions.go b/internal/frontend/versions.go
index 699d04a..773bffd 100644
--- a/internal/frontend/versions.go
+++ b/internal/frontend/versions.go
@@ -15,7 +15,6 @@
 	"golang.org/x/mod/semver"
 	"golang.org/x/pkgsite/internal"
 	"golang.org/x/pkgsite/internal/log"
-	"golang.org/x/pkgsite/internal/postgres"
 	"golang.org/x/pkgsite/internal/stdlib"
 	"golang.org/x/pkgsite/internal/version"
 	"golang.org/x/pkgsite/internal/vuln"
@@ -86,7 +85,7 @@
 }
 
 func fetchVersionsDetails(ctx context.Context, ds internal.DataSource, um *internal.UnitMeta, vc *vuln.Client) (*VersionsDetails, error) {
-	db, ok := ds.(*postgres.DB)
+	db, ok := ds.(internal.PostgresDB)
 	if !ok {
 		// The proxydatasource does not support the imported by page.
 		return nil, datasourceNotSupportedErr()
diff --git a/internal/interfaces.go b/internal/interfaces.go
new file mode 100644
index 0000000..b2331d5
--- /dev/null
+++ b/internal/interfaces.go
@@ -0,0 +1,27 @@
+// Copyright 2023 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 internal
+
+import "context"
+
+// PostgresDB provides an interface satisfied by *(internal/postgres.DB) so that
+// packages in pkgsite can use the database if it exists without needing a
+// dependency on the database driver packages.
+type PostgresDB interface {
+	DataSource
+	IsPostgresDB()
+
+	IsExcluded(ctx context.Context, path string) (_ bool, err error)
+	GetImportedBy(ctx context.Context, pkgPath, modulePath string, limit int) (paths []string, err error)
+	GetImportedByCount(ctx context.Context, pkgPath, modulePath string) (_ int, err error)
+	GetLatestMajorPathForV1Path(ctx context.Context, v1path string) (_ string, _ int, err error)
+	GetStdlibPathsWithSuffix(ctx context.Context, suffix string) (paths []string, err error)
+	GetSymbolHistory(ctx context.Context, packagePath, modulePath string) (_ *SymbolHistory, err error)
+	GetVersionMap(ctx context.Context, modulePath, requestedVersion string) (_ *VersionMap, err error)
+	GetVersionMaps(ctx context.Context, paths []string, requestedVersion string) (_ []*VersionMap, err error)
+	GetVersionsForPath(ctx context.Context, path string) (_ []*ModuleInfo, err error)
+	InsertModule(ctx context.Context, m *Module, lmv *LatestModuleVersions) (isLatest bool, err error)
+	UpsertVersionMap(ctx context.Context, vm *VersionMap) (err error)
+}
diff --git a/internal/postgres/postgres.go b/internal/postgres/postgres.go
index d56f08c..45de904 100644
--- a/internal/postgres/postgres.go
+++ b/internal/postgres/postgres.go
@@ -29,6 +29,11 @@
 	return newdb(db, false)
 }
 
+// Used to check that a DataSource is a PostgresDB without doing a
+// direct type assertion on *DB.
+func (*DB) IsPostgresDB() {
+}
+
 // NewBypassingLicenseCheck returns a new postgres DB that bypasses license
 // checks. That means all data will be inserted and returned for
 // non-redistributable modules, packages and directories.