diff --git a/.circleci/config.yml b/.circleci/config.yml index d650631..c83551e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2.1 executors: linux-arm64: machine: - image: ubuntu-2004:2022.04.1 + image: ubuntu-2204:2024.01.2 resource_class: arm.medium working_directory: /home/circleci/go/src/github.com/fergusstrange/embedded-postgres apple-m1: &macos-executor @@ -10,7 +10,7 @@ executors: macos: xcode: "14.2.0" orbs: - go: circleci/go@1.7.3 + go: circleci/go@1.11.0 jobs: platform_test: parameters: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e85d739..baad6bc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,11 +11,11 @@ jobs: steps: - name: Checkout id: go - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: Set Up Golang - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: - go-version: 1.18 + go-version: 1.22 - name: Check Dependencies run: | go list -json -deps > go.list @@ -37,7 +37,7 @@ jobs: - name: Nancy Vulnerability uses: sonatype-nexus-community/nancy-github-action@main with: - nancyVersion: v1.0.36 + nancyVersion: v1.0.46 nancyCommand: sleuth - name: GolangCI Lint run: | @@ -53,14 +53,14 @@ jobs: - name: Upload Coverage Report env: COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: GO111MODULE=off go get github.com/mattn/goveralls && $(go env GOPATH)/bin/goveralls -v -coverprofile=coverage.out -service=github + run: go install github.com/mattn/goveralls@latest && $(go env GOPATH)/bin/goveralls -v -coverprofile=coverage.out -service=github alpine_tests: name: Alpine Linux Platform Tests runs-on: ubuntu-latest container: - image: golang:1.18-alpine + image: golang:1.22-alpine steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Set Up run: | apk add --upgrade gcc g++ && \ @@ -75,11 +75,11 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: Set Up Golang - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: - go-version: 1.18 + go-version: 1.22 - name: Platform Tests run: | cd platform-test diff --git a/README.md b/README.md index 5fa28f8..aff90c2 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,9 @@ This library aims to require as little configuration as possible, favouring over | Password | postgres | | Database | postgres | | Version | 15.3.0 | +| Encoding | UTF8 | +| Locale | C | +| Version | 15.3.0 | | CachePath | $USER_HOME/.embedded-postgres-go/ | | RuntimePath | $USER_HOME/.embedded-postgres-go/extracted | | DataPath | $USER_HOME/.embedded-postgres-go/extracted/data | @@ -49,6 +52,7 @@ This library aims to require as little configuration as possible, favouring over | BinaryRepositoryURL | https://repo1.maven.org/maven2 | | Port | 5432 | | StartTimeout | 15 Seconds | +| StartParameters | map[string]string{"max_connections": "101"} | The *RuntimePath* directory is erased and recreated at each `Start()` and therefore not suitable for persistent data. @@ -85,10 +89,10 @@ Password("wine"). Database("gin"). Version(V12). RuntimePath("/tmp"). -BinaryRepositoryURL("https://repo.local/central.proxy"). +BinaryRepositoryURL("https://repo.local/central.proxy"). Port(9876). StartTimeout(45 * time.Second). -StartParameters(map[string]string{"max_connections": "200"}). +StartParameters(map[string]string{"max_connections": "200"}). Logger(logger)) err := postgres.Start() diff --git a/cmd/main.go b/cmd/main.go deleted file mode 100644 index 6ef68a1..0000000 --- a/cmd/main.go +++ /dev/null @@ -1,20 +0,0 @@ -package main - -import ( - "log" - - embeddedpostgres "github.com/fergusstrange/embedded-postgres" -) - -func main() { - embeddedPostgres := embeddedpostgres.NewDatabase() - if err := embeddedPostgres.Start(); err != nil { - log.Fatal(err) - } - - defer func() { - if err := embeddedPostgres.Stop(); err != nil { - log.Fatal(err) - } - }() -} diff --git a/config.go b/config.go index 75d4f64..97e48d0 100644 --- a/config.go +++ b/config.go @@ -19,6 +19,7 @@ type Config struct { dataPath string binariesPath string locale string + encoding string startParameters map[string]string binaryRepositoryURL string startTimeout time.Duration @@ -27,7 +28,7 @@ type Config struct { // DefaultConfig provides a default set of configuration to be used "as is" or modified using the provided builders. // The following can be assumed as defaults: -// Version: 15 +// Version: 16 // Port: 5432 // Database: postgres // Username: postgres @@ -110,6 +111,12 @@ func (c Config) Locale(locale string) Config { return c } +// Encoding sets the default character set for initdb +func (c Config) Encoding(encoding string) Config { + c.encoding = encoding + return c +} + // StartParameters sets run-time parameters when starting Postgres (passed to Postgres via "-c"). // // These parameters can be used to override the default configuration values in postgres.conf such diff --git a/embedded_postgres.go b/embedded_postgres.go index d3af5f6..fcf98d1 100644 --- a/embedded_postgres.go +++ b/embedded_postgres.go @@ -167,7 +167,7 @@ func (ep *EmbeddedPostgres) cleanDataDirectoryAndInit() error { return fmt.Errorf("unable to clean up data directory %s with error: %s", ep.config.dataPath, err) } - if err := ep.initDatabase(ep.config.binariesPath, ep.config.runtimePath, ep.config.dataPath, ep.config.username, ep.config.password, ep.config.locale, ep.syncedLogger.file); err != nil { + if err := ep.initDatabase(ep.config.binariesPath, ep.config.runtimePath, ep.config.dataPath, ep.config.username, ep.config.password, ep.config.locale, ep.config.encoding, ep.syncedLogger.file); err != nil { return err } diff --git a/embedded_postgres_test.go b/embedded_postgres_test.go index 474cd2a..a6b4bd2 100644 --- a/embedded_postgres_test.go +++ b/embedded_postgres_test.go @@ -123,7 +123,7 @@ func Test_ErrorWhenUnableToInitDatabase(t *testing.T) { return jarFile, true } - database.initDatabase = func(binaryExtractLocation, runtimePath, dataLocation, username, password, locale string, logger *os.File) error { + database.initDatabase = func(binaryExtractLocation, runtimePath, dataLocation, username, password, locale string, encoding string, logger *os.File) error { return errors.New("ah it did not work") } @@ -226,7 +226,7 @@ func Test_ErrorWhenCannotStartPostgresProcess(t *testing.T) { return jarFile, true } - database.initDatabase = func(binaryExtractLocation, runtimePath, dataLocation, username, password, locale string, logger *os.File) error { + database.initDatabase = func(binaryExtractLocation, runtimePath, dataLocation, username, password, locale string, encoding string, logger *os.File) error { _, _ = logger.Write([]byte("ah it did not work")) return nil } @@ -257,6 +257,7 @@ func Test_CustomConfig(t *testing.T) { Port(9876). StartTimeout(10 * time.Second). Locale("C"). + Encoding("UTF8"). Logger(nil)) if err := database.Start(); err != nil { @@ -356,6 +357,36 @@ func Test_CustomLocaleConfig(t *testing.T) { } } +func Test_CustomEncodingConfig(t *testing.T) { + database := NewDatabase(DefaultConfig().Encoding("UTF8")) + if err := database.Start(); err != nil { + shutdownDBAndFail(t, err, database) + } + + db, err := sql.Open("postgres", "host=localhost port=5432 user=postgres password=postgres dbname=postgres sslmode=disable") + if err != nil { + shutdownDBAndFail(t, err, database) + } + + rows := db.QueryRow("SHOW SERVER_ENCODING;") + + var ( + value string + ) + if err := rows.Scan(&value); err != nil { + shutdownDBAndFail(t, err, database) + } + assert.Equal(t, "UTF8", value) + + if err := db.Close(); err != nil { + shutdownDBAndFail(t, err, database) + } + + if err := database.Stop(); err != nil { + shutdownDBAndFail(t, err, database) + } +} + func Test_ConcurrentStart(t *testing.T) { var wg sync.WaitGroup @@ -417,10 +448,7 @@ func Test_ConcurrentStart(t *testing.T) { } func Test_CustomStartParameters(t *testing.T) { - database := NewDatabase(DefaultConfig().StartParameters(map[string]string{ - "max_connections": "101", - "shared_buffers": "16 MB", // Ensure a parameter with spaces encodes correctly. - })) + database := NewDatabase(DefaultConfig().StartParameters(map[string]string{"max_connections": "101"})) if err := database.Start(); err != nil { shutdownDBAndFail(t, err, database) } diff --git a/prepare_database.go b/prepare_database.go index 0e8fa65..751aaea 100644 --- a/prepare_database.go +++ b/prepare_database.go @@ -18,10 +18,10 @@ const ( fmtAfterError = "%v happened after error: %w" ) -type initDatabase func(binaryExtractLocation, runtimePath, pgDataDir, username, password, locale string, logger *os.File) error +type initDatabase func(binaryExtractLocation, runtimePath, pgDataDir, username, password, locale string, encoding string, logger *os.File) error type createDatabase func(port uint32, username, password, database string) error -func defaultInitDatabase(binaryExtractLocation, runtimePath, pgDataDir, username, password, locale string, logger *os.File) error { +func defaultInitDatabase(binaryExtractLocation, runtimePath, pgDataDir, username, password, locale string, encoding string, logger *os.File) error { passwordFile, err := createPasswordFile(runtimePath, password) if err != nil { return err @@ -38,6 +38,10 @@ func defaultInitDatabase(binaryExtractLocation, runtimePath, pgDataDir, username args = append(args, fmt.Sprintf("--locale=%s", locale)) } + if encoding != "" { + args = append(args, fmt.Sprintf("--encoding=%s", encoding)) + } + postgresInitDBBinary := filepath.Join(binaryExtractLocation, "bin/initdb") postgresInitDBProcess := exec.Command(postgresInitDBBinary, args...) postgresInitDBProcess.Stderr = logger diff --git a/prepare_database_test.go b/prepare_database_test.go index cad9873..2700d27 100644 --- a/prepare_database_test.go +++ b/prepare_database_test.go @@ -12,7 +12,7 @@ import ( ) func Test_defaultInitDatabase_ErrorWhenCannotCreatePasswordFile(t *testing.T) { - err := defaultInitDatabase("path_not_exists", "path_not_exists", "path_not_exists", "Tom", "Beer", "", os.Stderr) + err := defaultInitDatabase("path_not_exists", "path_not_exists", "path_not_exists", "Tom", "Beer", "", "", os.Stderr) assert.EqualError(t, err, "unable to write password file to path_not_exists/pwfile") } @@ -49,7 +49,7 @@ func Test_defaultInitDatabase_ErrorWhenCannotStartInitDBProcess(t *testing.T) { _, _ = logFile.Write([]byte("and here are the logs!")) - err = defaultInitDatabase(binTempDir, runtimeTempDir, filepath.Join(runtimeTempDir, "data"), "Tom", "Beer", "", logFile) + err = defaultInitDatabase(binTempDir, runtimeTempDir, filepath.Join(runtimeTempDir, "data"), "Tom", "Beer", "", "", logFile) assert.NotNil(t, err) assert.Contains(t, err.Error(), fmt.Sprintf("unable to init database using '%s/bin/initdb -A password -U Tom -D %s/data --pwfile=%s/pwfile'", @@ -72,7 +72,7 @@ func Test_defaultInitDatabase_ErrorInvalidLocaleSetting(t *testing.T) { } }() - err = defaultInitDatabase(tempDir, tempDir, filepath.Join(tempDir, "data"), "postgres", "postgres", "en_XY", os.Stderr) + err = defaultInitDatabase(tempDir, tempDir, filepath.Join(tempDir, "data"), "postgres", "postgres", "en_XY", "", os.Stderr) assert.NotNil(t, err) assert.Contains(t, err.Error(), fmt.Sprintf("unable to init database using '%s/bin/initdb -A password -U postgres -D %s/data --pwfile=%s/pwfile --locale=en_XY'", @@ -81,6 +81,27 @@ func Test_defaultInitDatabase_ErrorInvalidLocaleSetting(t *testing.T) { tempDir)) } +func Test_defaultInitDatabase_ErrorInvalidEncodingSetting(t *testing.T) { + tempDir, err := os.MkdirTemp("", "prepare_database_test") + if err != nil { + panic(err) + } + + defer func() { + if err := os.RemoveAll(tempDir); err != nil { + panic(err) + } + }() + + err = defaultInitDatabase(tempDir, tempDir, filepath.Join(tempDir, "data"), "postgres", "postgres", "", "invalid", os.Stderr) + + assert.NotNil(t, err) + assert.Contains(t, err.Error(), fmt.Sprintf("unable to init database using '%s/bin/initdb -A password -U postgres -D %s/data --pwfile=%s/pwfile --encoding=invalid'", + tempDir, + tempDir, + tempDir)) +} + func Test_defaultInitDatabase_PwFileRemoved(t *testing.T) { tempDir, err := os.MkdirTemp("", "prepare_database_test") if err != nil {