Skip to content

Commit

Permalink
feat: Support postgres db for store and forward (#1605)
Browse files Browse the repository at this point in the history
* feat: Support postgres db for store and forward

Support postgres db for store and forward.

Signed-off-by: Lindsey Cheng <beckysocute@gmail.com>

* fix: Add postgres store methods unit tests

- Add postgres store methods unit tests.
- Revert redigo package version.

Signed-off-by: Lindsey Cheng <beckysocute@gmail.com>

* fix: Modify NewClient testcase

Relates to #1603. Modify NewClient testcase.

Signed-off-by: Lindsey Cheng <beckysocute@gmail.com>

* fix: Update RetrieveFromStore ordered by created timestamp

Update RetrieveFromStore to retrieve data ordered by created timestamp.

Signed-off-by: Lindsey Cheng <beckysocute@gmail.com>

---------

Signed-off-by: Lindsey Cheng <beckysocute@gmail.com>
  • Loading branch information
lindseysimple authored Sep 2, 2024
1 parent 5062475 commit ac7762b
Show file tree
Hide file tree
Showing 15 changed files with 944 additions and 96 deletions.
19 changes: 11 additions & 8 deletions app-service-template/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ replace github.com/edgexfoundry/app-functions-sdk-go/v3 => ../

require (
github.com/edgexfoundry/app-functions-sdk-go/v3 v3.1.1
github.com/edgexfoundry/go-mod-core-contracts/v3 v3.2.0-dev.30
github.com/edgexfoundry/go-mod-core-contracts/v3 v3.2.0-dev.32
github.com/google/uuid v1.6.0
github.com/labstack/echo/v4 v4.11.4
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
Expand All @@ -26,10 +26,10 @@ require (
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/diegoholiveira/jsonlogic/v3 v3.5.3 // indirect
github.com/eclipse/paho.mqtt.golang v1.4.3 // indirect
github.com/edgexfoundry/go-mod-bootstrap/v3 v3.2.0-dev.48 // indirect
github.com/edgexfoundry/go-mod-configuration/v3 v3.2.0-dev.11 // indirect
github.com/edgexfoundry/go-mod-messaging/v3 v3.2.0-dev.29 // indirect
github.com/eclipse/paho.mqtt.golang v1.5.0 // indirect
github.com/edgexfoundry/go-mod-bootstrap/v3 v3.2.0-dev.52 // indirect
github.com/edgexfoundry/go-mod-configuration/v3 v3.2.0-dev.12 // indirect
github.com/edgexfoundry/go-mod-messaging/v3 v3.2.0-dev.31 // indirect
github.com/edgexfoundry/go-mod-registry/v3 v3.2.0-dev.13 // indirect
github.com/edgexfoundry/go-mod-secrets/v3 v3.2.0-dev.9 // indirect
github.com/fatih/color v1.16.0 // indirect
Expand Down Expand Up @@ -65,7 +65,7 @@ require (
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/schema v1.4.1 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/consul/api v1.29.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
Expand All @@ -75,6 +75,11 @@ require (
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/serf v0.10.1 // indirect
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.6.0 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kataras/go-events v0.0.3 // indirect
github.com/klauspost/compress v1.17.2 // indirect
Expand Down Expand Up @@ -134,15 +139,13 @@ require (
go.opentelemetry.io/otel/trace v1.28.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
google.golang.org/grpc v1.64.1 // indirect
google.golang.org/protobuf v1.34.2 // indirect
Expand Down
138 changes: 53 additions & 85 deletions app-service-template/go.sum

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ require (
github.com/gomodule/redigo v1.8.9
github.com/google/uuid v1.6.0
github.com/hashicorp/go-multierror v1.1.1
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438
github.com/jackc/pgx/v5 v5.6.0
github.com/labstack/echo/v4 v4.11.4
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
github.com/stretchr/testify v1.9.0
Expand Down Expand Up @@ -68,6 +70,9 @@ require (
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/serf v0.10.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kataras/go-events v0.0.3 // indirect
github.com/klauspost/compress v1.17.2 // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,16 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0=
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jeremija/gosubmit v0.2.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc=
github.com/jeremija/gosubmit v0.2.7/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
Expand Down
6 changes: 6 additions & 0 deletions internal/app/storeforward.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//
// Copyright (c) 2021 One Track Consulting
// Copyright (C) 2024 IOTech Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -25,6 +26,7 @@ import (
"github.com/edgexfoundry/app-functions-sdk-go/v3/internal/bootstrap/container"
"github.com/edgexfoundry/app-functions-sdk-go/v3/internal/common"
"github.com/edgexfoundry/app-functions-sdk-go/v3/internal/store/db"
"github.com/edgexfoundry/app-functions-sdk-go/v3/internal/store/db/postgres"
"github.com/edgexfoundry/app-functions-sdk-go/v3/internal/store/db/redis"
"github.com/edgexfoundry/app-functions-sdk-go/v3/pkg/interfaces"
bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container"
Expand All @@ -34,6 +36,8 @@ import (
"github.com/edgexfoundry/go-mod-bootstrap/v3/di"
)

const baseScriptPath = "./res/db/sql"

// RegisterCustomStoreFactory allows registration of alternative storage implementation to back the Store&Forward loop
func (svc *Service) RegisterCustomStoreFactory(name string, factory func(cfg bootstrapConfig.Database, cred bootstrapConfig.Credentials) (interfaces.StoreClient, error)) error {
if name == db.RedisDB {
Expand All @@ -53,6 +57,8 @@ func (svc *Service) createStoreClient(database bootstrapConfig.Database, credent
switch strings.ToLower(database.Type) {
case db.RedisDB:
return redis.NewClient(database, credentials)
case db.Postgres:
return postgres.NewClient(svc.ctx.appCtx, database, credentials, baseScriptPath, "", svc.lc)
default:
if factory, found := svc.customStoreClientFactories[strings.ToUpper(database.Type)]; found {
return factory(database, credentials)
Expand Down
4 changes: 3 additions & 1 deletion internal/store/db/db.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*******************************************************************************
* Copyright 2019 Dell Inc.
* Copyright (c) 2021 Intel Corporation
* Copyright (C) 2024 IOTech Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
Expand All @@ -20,7 +21,8 @@ import "errors"

const (
// Database providers
RedisDB = "redisdb"
RedisDB = "redisdb"
Postgres = "postgres"
)

var (
Expand Down
File renamed without changes.
85 changes: 85 additions & 0 deletions internal/store/db/postgres/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// Copyright (C) 2024 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

package postgres

import (
"context"
"fmt"
"net/url"
"os"
"sync"

bootstrapConfig "github.com/edgexfoundry/go-mod-bootstrap/v3/config"

"github.com/edgexfoundry/go-mod-core-contracts/v3/clients/logger"
"github.com/edgexfoundry/go-mod-core-contracts/v3/errors"

"github.com/jackc/pgx/v5/pgxpool"
)

const defaultDBName = "edgex_db"

var once sync.Once
var dc *Client

type Client struct {
connPool *pgxpool.Pool
loggingClient logger.LoggingClient
}

// NewClient returns a pointer to the Postgres client
func NewClient(ctx context.Context, config bootstrapConfig.Database, credentials bootstrapConfig.Credentials, baseScriptPath, extScriptPath string, lc logger.LoggingClient) (*Client, errors.EdgeX) {
// Get the database name from the environment variable
databaseName := os.Getenv("EDGEX_DBNAME")
if databaseName == "" {
databaseName = defaultDBName
}

var edgeXerr errors.EdgeX
once.Do(func() {
// use url encode to prevent special characters in the connection string
connectionStr := "postgres://" + fmt.Sprintf("%s:%s@%s:%d/%s", url.PathEscape(credentials.Username), url.PathEscape(credentials.Password), url.PathEscape(config.Host), config.Port, url.PathEscape(databaseName))
dbPool, err := pgxpool.New(ctx, connectionStr)
if err != nil {
edgeXerr = wrapDBError("fail to create pg connection pool", err)
}

dc = &Client{
connPool: dbPool,
loggingClient: lc,
}
})
if edgeXerr != nil {
return nil, errors.NewCommonEdgeX(errors.KindDatabaseError, "failed to create a Postgres client", edgeXerr)
}

// invoke Ping to test the connectivity to the DB
if err := dc.connPool.Ping(ctx); err != nil {
return nil, wrapDBError("failed to acquire a connection from database connection pool", err)
}

lc.Info("Successfully connect to Postgres database")

// execute base DB scripts
if edgeXerr = executeDBScripts(ctx, dc.connPool, baseScriptPath); edgeXerr != nil {
return nil, errors.NewCommonEdgeX(errors.Kind(edgeXerr), "failed to execute Postgres base DB scripts", edgeXerr)
}
if baseScriptPath != "" {
lc.Info("Successfully execute Postgres base DB scripts")
}

// execute extension DB scripts
if edgeXerr = executeDBScripts(ctx, dc.connPool, extScriptPath); edgeXerr != nil {
return nil, errors.NewCommonEdgeX(errors.Kind(edgeXerr), "failed to execute Postgres extension DB scripts", edgeXerr)
}

return dc, nil
}

// CloseSession closes the connections to postgres
func (c Client) CloseSession() {
c.connPool.Close()
}
80 changes: 80 additions & 0 deletions internal/store/db/postgres/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//go:build postgresRunning
// +build postgresRunning

/*******************************************************************************
* Copyright (C) 2024 IOTech Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*******************************************************************************/

// This test will only be executed if the tag postgresRunning is added when running
// the tests with a command like:
// go test -tags postgresRunning

package postgres

import (
"context"
"testing"

"github.com/edgexfoundry/app-functions-sdk-go/v3/internal/store/db"

bootstrapConfig "github.com/edgexfoundry/go-mod-bootstrap/v3/config"
"github.com/edgexfoundry/go-mod-core-contracts/v3/clients/logger"

"github.com/stretchr/testify/require"
)

const (
TestHost = "localhost"
TestPort = 5432
TestTimeout = "5s"
TestMaxIdle = 5000
TestBatchSize = 1337

TestRetryCount = 100
TestPipelinePosition = 1337
TestVersion = "your"
TestCorrelationID = "test"
TestPipelineId = "test-pipeline"
)

var TestValidNoAuthConfig = bootstrapConfig.Database{
Type: db.Postgres,
Host: TestHost,
Port: TestPort,
Timeout: TestTimeout,
}

var TestCredential = bootstrapConfig.Credentials{Username: "postgres", Password: "mysecretpassword"}

func TestClient_NewClient(t *testing.T) {
tests := []struct {
name string
config bootstrapConfig.Database
expectedError bool
}{
{"Success, no auth", TestValidNoAuthConfig, false},
}

lc := logger.NewMockClient()
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
_, err := NewClient(context.Background(), test.config, TestCredential, "", "", lc)

if test.expectedError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
41 changes: 41 additions & 0 deletions internal/store/db/postgres/sql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// Copyright (C) 2024 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

package postgres

import (
"fmt"
)

// Constants for column names in the database table
const (
contentCol = "content"
idCol = "id"
)

// sqlInsertContent returns the SQL statement for inserting a new row with id, name, and content into the table
func sqlInsertContent(table string) string {
return fmt.Sprintf("INSERT INTO %s(%s, %s) VALUES ($1, $2)", table, idCol, contentCol)
}

// sqlQueryCountByJSONField returns the SQL statement for selecting content column in the table by the given JSON query string ordered by created column
func sqlQueryContentByJSONField(table string) string {
return fmt.Sprintf("SELECT content FROM %s WHERE content @> $1::jsonb ORDER BY created", table)
}

// sqlCheckExistsById returns the SQL statement for checking if a row exists in the table by id
func sqlCheckExistsById(table string) string {
return fmt.Sprintf("SELECT EXISTS(SELECT 1 FROM %s WHERE %s = $1)", table, idCol)
}

// sqlUpdateContentById returns the SQL statement for updating the content of a row in the table by id
func sqlUpdateContentById(table string) string {
return fmt.Sprintf("UPDATE %s SET %s = $1 WHERE %s = $2", table, contentCol, idCol)
}

// sqlDeleteById returns the SQL statement for deleting a row from the table by id
func sqlDeleteById(table string) string {
return fmt.Sprintf("DELETE FROM %s WHERE %s = $1", table, idCol)
}
Loading

0 comments on commit ac7762b

Please sign in to comment.