nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: ariga/atlas
Pull request #289 (2021-12-07 11:43:36)

sql/sqlspec: adding function to print Type as string
Needed a way to go from `schemaspec.Type` to something the driver can parse into a `schema.Type`. 


Patch:
diff --git a/sql/sqlspec/types.go b/sql/sqlspec/types.go
new file mode 100644
index 00000000..c3eb45c7
--- /dev/null
+++ b/sql/sqlspec/types.go
@@ -0,0 +1,42 @@
+package sqlspec
+
+import (
+	"errors"
+	"strings"
+
+	"ariga.io/atlas/schema/schemaspec"
+)
+
+// PrintType returns the string representation of a column type which can be parsed
+// by the driver into a schema.Type.
+func PrintType(typ *schemaspec.Type, spec *schemaspec.TypeSpec) (string, error) {
+	if len(spec.Attributes) == 0 {
+		return typ.T, nil
+	}
+	var (
+		args        []string
+		mid, suffix string
+	)
+	for _, arg := range typ.Attributes {
+		// TODO(rotemtam): make this part of the TypeSpec
+		if arg.K == "unsigned" {
+			b, err := arg.Bool()
+			if err != nil {
+				return "", err
+			}
+			if b {
+				suffix += " unsigned"
+			}
+			continue
+		}
+		lit, ok := arg.V.(*schemaspec.LiteralValue)
+		if !ok {
+			return "", errors.New("expecting literal value")
+		}
+		args = append(args, lit.V)
+	}
+	if len(args) > 0 {
+		mid = "(" + strings.Join(args, ",") + ")"
+	}
+	return typ.T + mid + suffix, nil
+}


Test patch:
diff --git a/sql/sqlspec/types_test.go b/sql/sqlspec/types_test.go
new file mode 100644
index 00000000..706741e8
--- /dev/null
+++ b/sql/sqlspec/types_test.go
@@ -0,0 +1,54 @@
+package sqlspec_test
+
+import (
+	"reflect"
+	"testing"
+
+	"ariga.io/atlas/schema/schemaspec"
+	"ariga.io/atlas/sql/internal/specutil"
+	"ariga.io/atlas/sql/sqlspec"
+	"github.com/stretchr/testify/require"
+)
+
+func TestTypePrint(t *testing.T) {
+	intSpec := &schemaspec.TypeSpec{
+		Name: "int",
+		T:    "int",
+		Attributes: []*schemaspec.TypeAttr{
+			{Name: "unsigned", Kind: reflect.Bool, Required: false},
+		},
+	}
+	for _, tt := range []struct {
+		spec     *schemaspec.TypeSpec
+		typ      *schemaspec.Type
+		expected string
+	}{
+		{
+			spec:     intSpec,
+			typ:      &schemaspec.Type{T: "int"},
+			expected: "int",
+		},
+		{
+			spec:     intSpec,
+			typ:      &schemaspec.Type{T: "int", Attributes: []*schemaspec.Attr{specutil.LitAttr("unsigned", "true")}},
+			expected: "int unsigned",
+		},
+		{
+			spec: &schemaspec.TypeSpec{
+				T:    "varchar",
+				Name: "varchar",
+				Attributes: []*schemaspec.TypeAttr{
+					{Name: "size", Kind: reflect.Int, Required: true},
+				},
+			},
+			typ:      &schemaspec.Type{T: "varchar", Attributes: []*schemaspec.Attr{specutil.LitAttr("size", "255")}},
+			expected: "varchar(255)",
+		},
+	} {
+		t.Run(tt.expected, func(t *testing.T) {
+			s, err := sqlspec.PrintType(tt.typ, tt.spec)
+			require.NoError(t, err)
+			require.EqualValues(t, tt.expected, s)
+		})
+	}
+}
Repository: ariga/atlas
Pull request #302 (2021-12-12 15:00:55)

sql/internal/specutil: move printtype method to registry


Patch:
diff --git a/sql/internal/specutil/types.go b/sql/internal/specutil/types.go
index 84d83243..21345861 100644
--- a/sql/internal/specutil/types.go
+++ b/sql/internal/specutil/types.go
@@ -14,7 +14,11 @@ import (
 
 // PrintType returns the string representation of a column type which can be parsed
 // by the driver into a schema.Type.
-func PrintType(typ *schemaspec.Type, spec *schemaspec.TypeSpec) (string, error) {
+func (r *TypeRegistry) PrintType(typ *schemaspec.Type) (string, error) {
+	spec, ok := r.Find(typ.T)
+	if !ok {
+		return "", fmt.Errorf("specutil: type %q not found in registry", typ.T)
+	}
 	if len(spec.Attributes) == 0 {
 		return typ.T, nil
 	}


Test patch:
diff --git a/sql/internal/specutil/types_test.go b/sql/internal/specutil/types_test.go
index 7b803bd3..160e127d 100644
--- a/sql/internal/specutil/types_test.go
+++ b/sql/internal/specutil/types_test.go
@@ -45,7 +45,10 @@ func TestTypePrint(t *testing.T) {
 		},
 	} {
 		t.Run(tt.expected, func(t *testing.T) {
-			s, err := PrintType(tt.typ, tt.spec)
+			r := &TypeRegistry{}
+			err := r.Register(tt.spec)
+			require.NoError(t, err)
+			s, err := r.PrintType(tt.typ)
 			require.NoError(t, err)
 			require.EqualValues(t, tt.expected, s)
 		})
Repository: ariga/atlas
Pull request #317 (2021-12-19 12:53:08)

sql/internal/specutil: disallow required arg after optional


Patch:
diff --git a/sql/internal/specutil/types.go b/sql/internal/specutil/types.go
index f6de78c3..dc0e4956 100644
--- a/sql/internal/specutil/types.go
+++ b/sql/internal/specutil/types.go
@@ -72,6 +72,9 @@ type TypeRegistry struct {
 // Register adds one or more TypeSpec to the registry.
 func (r *TypeRegistry) Register(specs ...*schemaspec.TypeSpec) error {
 	for _, s := range specs {
+		if err := validSpec(s); err != nil {
+			return fmt.Errorf("specutil: invalid typespec %q: %w", s.Name, err)
+		}
 		if _, exists := r.findT(s.T); exists {
 			return fmt.Errorf("specutil: type with T of %q already registered", s.T)
 		}
@@ -83,6 +86,17 @@ func (r *TypeRegistry) Register(specs ...*schemaspec.TypeSpec) error {
 	return nil
 }
 
+func validSpec(typeSpec *schemaspec.TypeSpec) error {
+	var seenOptional bool
+	for _, attr := range typeSpec.Attributes {
+		if seenOptional && attr.Required {
+			return fmt.Errorf("attr %q required after optional attr", attr.Name)
+		}
+		seenOptional = !attr.Required
+	}
+	return nil
+}
+
 // NewRegistry creates a new *TypeRegistry, registers the provided types and panics
 // if an error occurs.
 func NewRegistry(specs ...*schemaspec.TypeSpec) *TypeRegistry {


Test patch:
diff --git a/sql/internal/specutil/types_test.go b/sql/internal/specutil/types_test.go
index f4f44673..54f849d2 100644
--- a/sql/internal/specutil/types_test.go
+++ b/sql/internal/specutil/types_test.go
@@ -67,6 +67,37 @@ func TestRegistry(t *testing.T) {
 	require.EqualValues(t, spec, text)
 }
 
+func TestValidSpec(t *testing.T) {
+	registry := &TypeRegistry{}
+	err := registry.Register(&schemaspec.TypeSpec{
+		Name: "X",
+		T:    "X",
+		Attributes: []*schemaspec.TypeAttr{
+			{Name: "a", Required: false},
+			{Name: "b", Required: true},
+		},
+	})
+	require.EqualError(t, err, `specutil: invalid typespec "X": attr "b" required after optional attr`)
+	err = registry.Register(&schemaspec.TypeSpec{
+		Name: "X",
+		T:    "X",
+		Attributes: []*schemaspec.TypeAttr{
+			{Name: "a", Required: true},
+			{Name: "b", Required: false},
+		},
+	})
+	require.NoError(t, err)
+	err = registry.Register(&schemaspec.TypeSpec{
+		Name: "Y",
+		T:    "Y",
+		Attributes: []*schemaspec.TypeAttr{
+			{Name: "a", Required: false},
+			{Name: "b", Required: false},
+		},
+	})
+	require.NoError(t, err)
+}
+
 func TestRegistryConvert(t *testing.T) {
 	r := &TypeRegistry{}
 	err := r.Register(
Repository: ariga/atlas
Pull request #318 (2021-12-19 14:51:43)

sql/internal/specutil: disallow slice type attr except in last position


Patch:
diff --git a/sql/internal/specutil/types.go b/sql/internal/specutil/types.go
index dc0e4956..243bd149 100644
--- a/sql/internal/specutil/types.go
+++ b/sql/internal/specutil/types.go
@@ -88,7 +88,10 @@ func (r *TypeRegistry) Register(specs ...*schemaspec.TypeSpec) error {
 
 func validSpec(typeSpec *schemaspec.TypeSpec) error {
 	var seenOptional bool
-	for _, attr := range typeSpec.Attributes {
+	for i, attr := range typeSpec.Attributes {
+		if attr.Kind == reflect.Slice && i < len(typeSpec.Attributes)-1 {
+			return fmt.Errorf("attr %q is of kind slice but not last", attr.Name)
+		}
 		if seenOptional && attr.Required {
 			return fmt.Errorf("attr %q required after optional attr", attr.Name)
 		}


Test patch:
diff --git a/sql/internal/specutil/types_test.go b/sql/internal/specutil/types_test.go
index 54f849d2..b6655c53 100644
--- a/sql/internal/specutil/types_test.go
+++ b/sql/internal/specutil/types_test.go
@@ -70,6 +70,32 @@ func TestRegistry(t *testing.T) {
 func TestValidSpec(t *testing.T) {
 	registry := &TypeRegistry{}
 	err := registry.Register(&schemaspec.TypeSpec{
+		Name: "X",
+		T:    "X",
+		Attributes: []*schemaspec.TypeAttr{
+			{Name: "a", Required: false, Kind: reflect.Slice},
+			{Name: "b", Required: true},
+		},
+	})
+	require.EqualError(t, err, `specutil: invalid typespec "X": attr "a" is of kind slice but not last`)
+	err = registry.Register(&schemaspec.TypeSpec{
+		Name: "Z",
+		T:    "Z",
+		Attributes: []*schemaspec.TypeAttr{
+			{Name: "b", Required: true},
+			{Name: "a", Required: false, Kind: reflect.Slice},
+		},
+	})
+	require.NoError(t, err)
+	err = registry.Register(&schemaspec.TypeSpec{
+		Name: "Z2",
+		T:    "Z2",
+		Attributes: []*schemaspec.TypeAttr{
+			{Name: "a", Required: false, Kind: reflect.Slice},
+		},
+	})
+	require.NoError(t, err)
+	err = registry.Register(&schemaspec.TypeSpec{
 		Name: "X",
 		T:    "X",
 		Attributes: []*schemaspec.TypeAttr{
Repository: ariga/atlas
Pull request #367 (2022-01-04 20:19:02)

cmd/action/internal/update: improve new version copy


Patch:
diff --git a/cmd/action/internal/update/update.go b/cmd/action/internal/update/update.go
index 2237b091..9d2a8327 100644
--- a/cmd/action/internal/update/update.go
+++ b/cmd/action/internal/update/update.go
@@ -85,7 +85,7 @@ func shouldUpdate(version string, path string, latestReleaseF func() (LatestRele
 	if semver.Compare(version, l.Version) != -1 {
 		return false, "", nil
 	}
-	return true, fmt.Sprintf("new atlas version %s, %s", l.Version, l.URL), nil
+	return true, fmt.Sprintf("A new version of Atlas is available (%s):\n%s", l.Version, l.URL), nil
 }
 
 func latestReleaseFromGithub() (LatestRelease, error) {


Test patch:
diff --git a/cmd/action/internal/update/update_test.go b/cmd/action/internal/update/update_test.go
index 86a4268b..127d43ec 100644
--- a/cmd/action/internal/update/update_test.go
+++ b/cmd/action/internal/update/update_test.go
@@ -28,7 +28,7 @@ func TestCheckForUpdate(t *testing.T) {
 			store:          &Store{Version: "v1.2.3", URL: url, CheckedAt: time.Now().Add(-25 * time.Hour)},
 			latestRelease:  &LatestRelease{Version: "v2.2.3", URL: url},
 			shouldNotify:   true,
-			message:        fmt.Sprintf("new atlas version v2.2.3, %s", url),
+			message:        fmt.Sprintf("A new version of Atlas is available (v2.2.3):\n%s", url),
 		},
 		{
 			name:           "fresh store do nothing",
@@ -43,7 +43,7 @@ func TestCheckForUpdate(t *testing.T) {
 			latestVersion:  "v2.2.3",
 			latestRelease:  &LatestRelease{Version: "v2.2.3", URL: url},
 			shouldNotify:   true,
-			message:        fmt.Sprintf("new atlas version v2.2.3, %s", url),
+			message:        fmt.Sprintf("A new version of Atlas is available (v2.2.3):\n%s", url),
 		},
 		{
 			name:           "no store fetch and update newer minor version",
@@ -51,7 +51,7 @@ func TestCheckForUpdate(t *testing.T) {
 			latestVersion:  "v1.3.3",
 			latestRelease:  &LatestRelease{Version: "v2.2.3", URL: url},
 			shouldNotify:   true,
-			message:        fmt.Sprintf("new atlas version v2.2.3, %s", url),
+			message:        fmt.Sprintf("A new version of Atlas is available (v2.2.3):\n%s", url),
 		},
 		{
 			name:           "no store fetch and update newer patch version",
@@ -59,7 +59,7 @@ func TestCheckForUpdate(t *testing.T) {
 			latestVersion:  "v1.2.4",
 			latestRelease:  &LatestRelease{Version: "v2.2.3", URL: url},
 			shouldNotify:   true,
-			message:        fmt.Sprintf("new atlas version v2.2.3, %s", url),
+			message:        fmt.Sprintf("A new version of Atlas is available (v2.2.3):\n%s", url),
 		},
 	}
 	for _, tt := range tests {
Repository: ariga/atlas
Pull request #389 (2022-01-10 21:01:47)

sql/internal/specutil: support marshaling unsupported types as sql() …
…expressions in HCL for MySQL and SQLite

this brings support for bi-directional conversion of columns with 

```
type = sql("custom")
```

this PR solves the issue for MySQL and SQLite.
With Postgres there's a small issue @a8m and I will discuss tommorow


Patch:
diff --git a/sql/internal/specutil/types.go b/sql/internal/specutil/types.go
index 50584f8a..e5fc210c 100644
--- a/sql/internal/specutil/types.go
+++ b/sql/internal/specutil/types.go
@@ -167,6 +167,11 @@ func (r *TypeRegistry) findT(t string) (*schemaspec.TypeSpec, bool) {
 
 // Convert converts the schema.Type to a *schemaspec.Type.
 func (r *TypeRegistry) Convert(typ schema.Type) (*schemaspec.Type, error) {
+	if ut, ok := typ.(*schema.UnsupportedType); ok {
+		return &schemaspec.Type{
+			T: ut.T,
+		}, nil
+	}
 	rv := reflect.ValueOf(typ)
 	if rv.Kind() == reflect.Ptr {
 		rv = rv.Elem()
@@ -264,7 +269,7 @@ func (r *TypeRegistry) Type(typ *schemaspec.Type, extra []*schemaspec.Attr) (sch
 	nfa := typeNonFuncArgs(typeSpec)
 	picked := pickTypeAttrs(extra, nfa)
 	cp := &schemaspec.Type{
-		T:     typ.T,
+		T: typ.T,
 	}
 	cp.Attrs = appendIfNotExist(typ.Attrs, picked)
 	printType, err := r.PrintType(cp)
diff --git a/sql/sqlite/inspect.go b/sql/sqlite/inspect.go
index 540fced8..4db529db 100644
--- a/sql/sqlite/inspect.go
+++ b/sql/sqlite/inspect.go
@@ -226,7 +226,7 @@ func parseRawType(c string) (schema.Type, error) {
 	case "uuid":
 		return &UUIDType{T: t}, nil
 	default:
-		return nil, fmt.Errorf("unknown column type %q", t)
+		return &schema.UnsupportedType{T: t}, nil
 	}
 }
 


Test patch:
diff --git a/sql/mysql/sqlspec_test.go b/sql/mysql/sqlspec_test.go
index 284e6b2e..a45a2be5 100644
--- a/sql/mysql/sqlspec_test.go
+++ b/sql/mysql/sqlspec_test.go
@@ -286,6 +286,10 @@ func TestTypes(t *testing.T) {
 			typeExpr: "char(255)",
 			expected: &schema.StringType{T: TypeChar, Size: 255},
 		},
+		{
+			typeExpr: `sql("custom")`,
+			expected: &schema.UnsupportedType{T: "custom"},
+		},
 		{
 			typeExpr: "binary(255)",
 			expected: &schema.BinaryType{T: TypeBinary, Size: 255},
diff --git a/sql/postgres/sqlspec_test.go b/sql/postgres/sqlspec_test.go
index dcc2d333..e8f4c87e 100644
--- a/sql/postgres/sqlspec_test.go
+++ b/sql/postgres/sqlspec_test.go
@@ -165,6 +165,10 @@ func TestTypes(t *testing.T) {
 			typeExpr: "bit(10)",
 			expected: &BitType{T: TypeBit, Len: 10},
 		},
+		{
+			typeExpr: `hstore`,
+			expected: &UserDefinedType{T: "hstore"},
+		},
 		{
 			typeExpr: "bit_varying(10)",
 			expected: &BitType{T: TypeBitVar, Len: 10},
diff --git a/sql/sqlite/sqlspec_test.go b/sql/sqlite/sqlspec_test.go
index 586d137c..40ff8efc 100644
--- a/sql/sqlite/sqlspec_test.go
+++ b/sql/sqlite/sqlspec_test.go
@@ -161,6 +161,10 @@ func TestTypes(t *testing.T) {
 			typeExpr: "int(10)",
 			expected: &schema.IntegerType{T: "int"},
 		},
+		{
+			typeExpr: `sql("custom")`,
+			expected: &schema.UnsupportedType{T: "custom"},
+		},
 		{
 			typeExpr: "tinyint(10)",
 			expected: &schema.IntegerType{T: "tinyint"},
Repository: ariga/atlas
Pull request #395 (2022-01-11 21:44:18)

sql/mysql: support precision and scale in type registry


Patch:
diff --git a/sql/mysql/sqlspec.go b/sql/mysql/sqlspec.go
index 25ba08da..34e6a2b6 100644
--- a/sql/mysql/sqlspec.go
+++ b/sql/mysql/sqlspec.go
@@ -247,8 +247,8 @@ var TypeRegistry = specutil.NewRegistry(
 		specutil.TypeSpec(TypeSmallInt, unsignedTypeAttr(), specutil.SizeTypeAttr(false)),
 		specutil.TypeSpec(TypeMediumInt, unsignedTypeAttr(), specutil.SizeTypeAttr(false)),
 		specutil.TypeSpec(TypeBigInt, unsignedTypeAttr(), specutil.SizeTypeAttr(false)),
-		specutil.TypeSpec(TypeDecimal),
-		specutil.TypeSpec(TypeNumeric),
+		specutil.TypeSpec(TypeDecimal, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false}),
+		specutil.TypeSpec(TypeNumeric, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false}),
 		specutil.TypeSpec(TypeFloat, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false}),
 		specutil.TypeSpec(TypeDouble, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false}),
 		specutil.TypeSpec(TypeReal, &schemaspec.TypeAttr{Name: "precision", Kind: reflect.Int, Required: false}, &schemaspec.TypeAttr{Name: "scale", Kind: reflect.Int, Required: false}),


Test patch:
diff --git a/sql/mysql/sqlspec_test.go b/sql/mysql/sqlspec_test.go
index a45a2be5..810061f9 100644
--- a/sql/mysql/sqlspec_test.go
+++ b/sql/mysql/sqlspec_test.go
@@ -395,10 +395,26 @@ func TestTypes(t *testing.T) {
 			typeExpr: "decimal",
 			expected: &schema.DecimalType{T: TypeDecimal},
 		},
+		{
+			typeExpr: "decimal(10)",
+			expected: &schema.DecimalType{T: TypeDecimal, Precision: 10},
+		},
+		{
+			typeExpr: "decimal(10,2)",
+			expected: &schema.DecimalType{T: TypeDecimal, Precision: 10, Scale: 2},
+		},
 		{
 			typeExpr: "numeric",
 			expected: &schema.DecimalType{T: TypeNumeric},
 		},
+		{
+			typeExpr: "numeric(10)",
+			expected: &schema.DecimalType{T: TypeNumeric, Precision: 10},
+		},
+		{
+			typeExpr: "numeric(10,2)",
+			expected: &schema.DecimalType{T: TypeNumeric, Precision: 10, Scale: 2},
+		},
 		{
 			typeExpr: "float(10,0)",
 			expected: &schema.FloatType{T: TypeFloat, Precision: 10},
Repository: ariga/atlas
Pull request #411 (2022-01-14 11:23:15)

sql/postgres: empty comments are equivalent to NULL


Patch:
diff --git a/sql/postgres/migrate.go b/sql/postgres/migrate.go
index c9ec4c79..2a067c5d 100644
--- a/sql/postgres/migrate.go
+++ b/sql/postgres/migrate.go
@@ -319,11 +319,11 @@ func (s *state) alterTable(t *schema.Table, changes []schema.Change) error {
 
 func (s *state) addComments(t *schema.Table) {
 	var c schema.Comment
-	if sqlx.Has(t.Attrs, &c) {
+	if sqlx.Has(t.Attrs, &c) && c.Text != "" {
 		s.tableComment(t, c.Text, "")
 	}
 	for i := range t.Columns {
-		if sqlx.Has(t.Columns[i].Attrs, &c) {
+		if sqlx.Has(t.Columns[i].Attrs, &c) && c.Text != "" {
 			b := Build("COMMENT ON COLUMN").Table(t)
 			b.WriteByte('.')
 			b.Ident(t.Columns[i].Name)
@@ -335,7 +335,7 @@ func (s *state) addComments(t *schema.Table) {
 		}
 	}
 	for i := range t.Indexes {
-		if sqlx.Has(t.Indexes[i].Attrs, &c) {
+		if sqlx.Has(t.Indexes[i].Attrs, &c) && c.Text != "" {
 			b := Build("COMMENT ON INDEX").Ident(t.Indexes[i].Name).P("IS", quote(c.Text))
 			s.append(&migrate.Change{
 				Cmd:     b.Clone().P("IS", quote(c.Text)).String(),


Test patch:
diff --git a/sql/postgres/migrate_test.go b/sql/postgres/migrate_test.go
index e4d2e960..a13716b3 100644
--- a/sql/postgres/migrate_test.go
+++ b/sql/postgres/migrate_test.go
@@ -43,10 +43,11 @@ func TestPlanChanges(t *testing.T) {
 					T: &schema.Table{
 						Name: "posts",
 						Columns: []*schema.Column{
-							{Name: "id", Type: &schema.ColumnType{Type: &schema.IntegerType{T: "integer"}}, Attrs: []schema.Attr{&Identity{}}},
+							{Name: "id", Type: &schema.ColumnType{Type: &schema.IntegerType{T: "integer"}}, Attrs: []schema.Attr{&Identity{}, &schema.Comment{}}},
 							{Name: "text", Type: &schema.ColumnType{Type: &schema.StringType{T: "text"}, Null: true}},
 						},
 						Attrs: []schema.Attr{
+							&schema.Comment{},
 							&schema.Check{Name: "id_nonzero", Expr: `("id" > 0)`},
 							&schema.Check{Name: "text_len", Expr: `(length("text") > 0)`, Attrs: []schema.Attr{&NoInherit{}}},
 						},
Repository: ariga/atlas
Pull request #470 (2022-01-23 20:16:07)

cmd/action: Parse schema name in postgres dsn
`lib/pq` supports parsing runtime parameters from the DSN. Allowing for
configuration of the schema to inspect or apply on using the
`search_path` parameter.

Closes: #457


Patch:
diff --git a/cmd/action/mux.go b/cmd/action/mux.go
index ac31e675..9de7d7d2 100644
--- a/cmd/action/mux.go
+++ b/cmd/action/mux.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"database/sql"
 	"fmt"
+	"net/url"
 	"os"
 	"path/filepath"
 	"regexp"
@@ -90,7 +91,7 @@ func SchemaNameFromDSN(url string) (string, error) {
 		}
 		return cfg.DBName, err
 	case "postgres":
-		return "public", nil
+		return postgresSchema(dsn)
 	case "sqlite":
 		return schemaName(dsn)
 	default:
@@ -98,6 +99,24 @@ func SchemaNameFromDSN(url string) (string, error) {
 	}
 }
 
+func postgresSchema(dsn string) (string, error) {
+	url, err := url.Parse(dsn)
+	if err != nil {
+		// For backwards compatibility, we default to "public" when failing to
+		// parse.
+		return "public", nil
+	}
+	// lib/pq supports setting default schemas via the `search_path` parameter
+	// in a dsn.
+	//
+	// See: https://github.com/lib/pq/blob/8446d16b8935fdf2b5c0fe333538ac395e3e1e4b/conn.go#L1155-L1165
+	if schema := url.Query().Get("search_path"); schema != "" {
+		return schema, nil
+	}
+
+	return "public", nil
+}
+
 func schemaName(dsn string) (string, error) {
 	err := sqliteFileExists(dsn)
 	if err != nil {


Test patch:
diff --git a/cmd/action/mux_test.go b/cmd/action/mux_test.go
index 676557ff..86a0535e 100644
--- a/cmd/action/mux_test.go
+++ b/cmd/action/mux_test.go
@@ -116,3 +116,34 @@ func Test_SQLiteInMemory(t *testing.T) {
 	_, err := action.SchemaNameFromDSN("sqlite://file:test.db?cache=shared&mode=memory")
 	r.NoError(err)
 }
+
+func Test_PostgresSchemaDSN(t *testing.T) {
+	var tests = []struct {
+		dsn      string
+		expected string
+	}{
+		{
+			dsn:      "postgres://localhost:5432/dbname?search_path=foo",
+			expected: "foo",
+		},
+		{
+			dsn:      "postgres://localhost:5432/dbname",
+			expected: "public",
+		},
+		{
+			dsn:      "postgres://(bad:host)?search_path=foo",
+			expected: "public",
+		},
+		{
+			dsn:      "postgres://localhost:5432/dbname?search_path=",
+			expected: "public",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.dsn, func(t *testing.T) {
+			schema, err := action.SchemaNameFromDSN(tt.dsn)
+			require.NoError(t, err)
+			require.Equal(t, tt.expected, schema)
+		})
+	}
+}