@@ -4,14 +4,32 @@ import (
44 "errors"
55 "fmt"
66 "os/exec"
7+ "regexp"
78
9+ "github.com/hashicorp/go-version"
810 "github.com/stripe/pg-schema-diff/internal/pgengine"
911)
1012
13+ const (
14+ // FixedRestrictKey is a constant restricted key that can be used for tests and other use cases where
15+ // a constant restrict key is needed.
16+ FixedRestrictKey = "pgschemadiffrestrict"
17+ )
18+
19+ var (
20+ // versionRe matches the version returned by pg_dump.
21+ versionRe = regexp .MustCompile (`pg_dump \(PostgreSQL\) (\d+(?:\.\d+)?)` )
22+
23+ version15 = version .Must (version .NewSemver ("15.0" ))
24+ )
25+
1126// Parameter represents a parameter to be pg_dump. Don't use a type alias for a string slice
1227// because all parameters for pgdump should be explicitly added here
1328type Parameter struct {
14- values []string `explicit:"always"`
29+ values []string
30+ // minimumVersion is the minimum required version pg_dump must return for the parameter to be added. If
31+ // pg_dump is an older version, it will not be added. If nil, there is no restriction.
32+ minimumVersion * version.Version
1533}
1634
1735func WithExcludeSchema (pattern string ) Parameter {
@@ -26,6 +44,17 @@ func WithSchemaOnly() Parameter {
2644 }
2745}
2846
47+ // WithRestrictKey is used by PSQL to prevent injection of "meta" commands. If not explicitly provided,
48+ // a random one will be generated for each pg_dump run. This most likely needs to be fixed for any
49+ // usages of pg_dump in tests.
50+ func WithRestrictKey (restrictKey string ) Parameter {
51+ return Parameter {
52+ values : []string {"--restrict-key" , restrictKey },
53+ // Added in 17.6. https://www.postgresql.org/docs/release/17.6/.
54+ minimumVersion : version15 ,
55+ }
56+ }
57+
2958// GetDump gets the pg_dump of the inputted database.
3059// It is only intended to be used for testing. You cannot securely pass passwords with this implementation, so it will
3160// only accept databases created for unit tests (spun up with the pgengine package)
@@ -39,13 +68,50 @@ func GetDump(db *pgengine.DB, additionalParams ...Parameter) (string, error) {
3968}
4069
4170func GetDumpUsingBinary (pgDumpBinaryPath string , db * pgengine.DB , additionalParams ... Parameter ) (string , error ) {
71+ version , err := getVersion (pgDumpBinaryPath )
72+ if err != nil {
73+ return "" , fmt .Errorf ("getVersion: %w" , err )
74+ }
75+
4276 params := []string {
4377 db .GetDSN (),
4478 }
4579 for _ , param := range additionalParams {
80+ if param .minimumVersion != nil && param .minimumVersion .GreaterThan (version ) {
81+ // Exclude the parameter if the minimum version is not satisfied.
82+ continue
83+ }
4684 params = append (params , param .values ... )
4785 }
86+ return runPgDumpCmd (pgDumpBinaryPath , params ... )
87+ }
88+
89+ // ParseVersion parses a version string from pg_dump output and returns a Version object.
90+ // This function is exported to make it testable.
91+ func ParseVersion (versionString string ) (* version.Version , error ) {
92+ matches := versionRe .FindStringSubmatch (versionString )
93+ if len (matches ) < 2 {
94+ return nil , fmt .Errorf ("could not extract version from string: %s" , versionString )
95+ }
96+
97+ // Parse the extracted version string
98+ v , err := version .NewVersion (matches [1 ])
99+ if err != nil {
100+ return nil , fmt .Errorf ("could not parse version %s: %w" , matches [1 ], err )
101+ }
102+ return v , nil
103+ }
104+
105+ func getVersion (pgDumpBinaryPath string ) (* version.Version , error ) {
106+ versionString , err := runPgDumpCmd (pgDumpBinaryPath , "--version" )
107+ if err != nil {
108+ return nil , err
109+ }
110+
111+ return ParseVersion (versionString )
112+ }
48113
114+ func runPgDumpCmd (pgDumpBinaryPath string , params ... string ) (string , error ) {
49115 output , err := exec .Command (pgDumpBinaryPath , params ... ).CombinedOutput ()
50116 if err != nil {
51117 return "" , fmt .Errorf ("running pg dump \n output=%s\n : %w" , output , err )
0 commit comments