Repository: supabase/cli
Pull request #1097 (2023-05-15 05:48:03)

fix: support linting remote database by url
## What kind of change does this PR introduce?

Bug fix https://github.com/supabase/cli/issues/1096

## What is the new behavior?

- parses `--db-url` and `--linked` flag so that `db lint` works against remote db

## Additional context

Add any other context or screenshots.


coveralls: ## Pull Request Test Coverage Report for [Build 4976963876](https://coveralls.io/builds/59935939)

* **13** of **27**   **(48.15%)**  changed or added relevant lines in **3** files are covered.
* **1** unchanged line in **1** file lost coverage.
* Overall coverage increased (+**0.01%**) to **62.968%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [internal/utils/misc.go](https://coveralls.io/builds/59935939/source?filename=internal%2Futils%2Fmisc.go#L336) | 2 | 3 | 66.67%
| [cmd/db.go](https://coveralls.io/builds/59935939/source?filename=cmd%2Fdb.go#L183) | 2 | 8 | 25.0%
| [internal/db/lint/lint.go](https://coveralls.io/builds/59935939/source?filename=internal%2Fdb%2Flint%2Flint.go#L62) | 9 | 16 | 56.25%
<!-- | **Total:** | **13** | **27** | **48.15%** | -->

|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [internal/utils/misc.go](https://coveralls.io/builds/59935939/source?filename=internal%2Futils%2Fmisc.go#L337) | 1 | 78.74% |
<!-- | **Total:** | **1** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/59935939/badge)](https://coveralls.io/builds/59935939) |
| :-- | --: |
| Change from base [Build 4976609912](https://coveralls.io/builds/59934769): |  0.01% |
| Covered Lines: | 4387 |
| Relevant Lines: | 6967 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/cmd/db.go b/cmd/db.go
index ea27e656..057a88ea 100644
--- a/cmd/db.go
+++ b/cmd/db.go
@@ -180,8 +180,14 @@ var (
 		Use:   "lint",
 		Short: "Checks local database for typing error",
 		RunE: func(cmd *cobra.Command, args []string) error {
+			fsys := afero.NewOsFs()
+			if linked || len(dbUrl) > 0 {
+				if err := parseDatabaseConfig(fsys); err != nil {
+					return err
+				}
+			}
 			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
-			return lint.Run(ctx, schema, level.Value, afero.NewOsFs())
+			return lint.Run(ctx, schema, level.Value, dbConfig, afero.NewOsFs())
 		},
 	}
 
@@ -218,7 +224,7 @@ func init() {
 	diffFlags.BoolVar(&useMigra, "use-migra", true, "Use migra to generate schema diff.")
 	diffFlags.BoolVar(&usePgAdmin, "use-pgadmin", false, "Use pgAdmin to generate schema diff.")
 	dbDiffCmd.MarkFlagsMutuallyExclusive("use-migra", "use-pgadmin")
-	diffFlags.BoolVar(&linked, "linked", false, "Diffs local schema against linked project.")
+	diffFlags.BoolVar(&linked, "linked", false, "Diffs local schema against the linked project.")
 	diffFlags.StringVarP(&file, "file", "f", "", "Saves schema diff to a new migration file.")
 	diffFlags.StringSliceVarP(&schema, "schema", "s", []string{}, "List of schema to include.")
 	diffFlags.Lookup("schema").DefValue = "all"
@@ -252,6 +258,7 @@ func init() {
 	dbCmd.AddCommand(dbResetCmd)
 	// Build lint command
 	lintFlags := dbLintCmd.Flags()
+	lintFlags.BoolVar(&linked, "linked", false, "Lints the linked project for schema errors.")
 	lintFlags.StringSliceVarP(&schema, "schema", "s", []string{}, "List of schema to include.")
 	lintFlags.Lookup("schema").DefValue = "all"
 	lintFlags.Var(&level, "level", "Error level to emit.")
diff --git a/internal/db/lint/lint.go b/internal/db/lint/lint.go
index 5bacb4a0..80440b6c 100644
--- a/internal/db/lint/lint.go
+++ b/internal/db/lint/lint.go
@@ -9,6 +9,7 @@ import (
 	"os"
 	"strings"
 
+	"github.com/jackc/pgconn"
 	"github.com/jackc/pgx/v4"
 	"github.com/spf13/afero"
 	"github.com/supabase/cli/internal/db/diff"
@@ -37,20 +38,14 @@ func toEnum(level string) LintLevel {
 	return -1
 }
 
-func Run(ctx context.Context, schema []string, level string, fsys afero.Fs, opts ...func(*pgx.ConnConfig)) error {
+func Run(ctx context.Context, schema []string, level string, config pgconn.Config, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
 	// Sanity checks.
-	if err := utils.LoadConfigFS(fsys); err != nil {
-		return err
-	}
-	if err := utils.AssertSupabaseDbIsRunning(); err != nil {
-		return err
-	}
-	// Run lint script
-	conn, err := utils.ConnectLocalPostgres(ctx, "localhost", utils.Config.Db.Port, "postgres", opts...)
+	conn, err := connect(ctx, config, fsys, options...)
 	if err != nil {
 		return err
 	}
 	defer conn.Close(context.Background())
+	// Run lint script
 	result, err := LintDatabase(ctx, conn, schema)
 	if err != nil {
 		return err
@@ -62,6 +57,21 @@ func Run(ctx context.Context, schema []string, level string, fsys afero.Fs, opts
 	return printResultJSON(result, toEnum(level), os.Stdout)
 }
 
+func connect(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...func(*pgx.ConnConfig)) (*pgx.Conn, error) {
+	if len(config.Password) > 0 {
+		fmt.Fprintln(os.Stderr, "Connecting to remote database...")
+		return utils.ConnectRemotePostgres(ctx, config, options...)
+	}
+	fmt.Fprintln(os.Stderr, "Connecting to local database...")
+	if err := utils.LoadConfigFS(fsys); err != nil {
+		return nil, err
+	}
+	if err := utils.AssertSupabaseDbIsRunning(); err != nil {
+		return nil, err
+	}
+	return utils.ConnectLocalPostgres(ctx, "localhost", utils.Config.Db.Port, "postgres", options...)
+}
+
 func filterResult(result []Result, minLevel LintLevel) (filtered []Result) {
 	for _, r := range result {
 		out := Result{Function: r.Function}
diff --git a/internal/utils/misc.go b/internal/utils/misc.go
index 14e00c59..78a58573 100644
--- a/internal/utils/misc.go
+++ b/internal/utils/misc.go
@@ -321,16 +321,6 @@ func AssertSupabaseCliIsSetUpFS(fsys afero.Fs) error {
 	return nil
 }
 
-func AssertIsLinkedFS(fsys afero.Fs) error {
-	if _, err := fsys.Stat(ProjectRefPath); errors.Is(err, os.ErrNotExist) {
-		return ErrNotLinked
-	} else if err != nil {
-		return err
-	}
-
-	return nil
-}
-
 func AssertProjectRefIsValid(projectRef string) error {
 	if !ProjectRefPattern.MatchString(projectRef) {
 		return ErrInvalidRef
@@ -340,8 +330,10 @@ func AssertProjectRefIsValid(projectRef string) error {
 
 func LoadProjectRef(fsys afero.Fs) (string, error) {
 	projectRefBytes, err := afero.ReadFile(fsys, ProjectRefPath)
-	if err != nil {
+	if errors.Is(err, os.ErrNotExist) {
 		return "", ErrNotLinked
+	} else if err != nil {
+		return "", err
 	}
 	projectRef := string(bytes.TrimSpace(projectRefBytes))
 	if !ProjectRefPattern.MatchString(projectRef) {


Test patch:
diff --git a/internal/db/lint/lint_test.go b/internal/db/lint/lint_test.go
index e47761f3..9747b820 100644
--- a/internal/db/lint/lint_test.go
+++ b/internal/db/lint/lint_test.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/client"
+	"github.com/jackc/pgconn"
 	"github.com/jackc/pgerrcode"
 	"github.com/spf13/afero"
 	"github.com/stretchr/testify/assert"
@@ -63,7 +64,7 @@ func TestLintCommand(t *testing.T) {
 		Reply("SELECT 1", []interface{}{"f1", string(data)}).
 		Query("rollback").Reply("ROLLBACK")
 	// Run test
-	assert.NoError(t, Run(context.Background(), []string{"public"}, "warning", fsys, conn.Intercept))
+	assert.NoError(t, Run(context.Background(), []string{"public"}, "warning", pgconn.Config{}, fsys, conn.Intercept))
 	// Validate api
 	assert.Empty(t, apitest.ListUnmatchedRequests())
 }