Skip to content

Commit 73997a2

Browse files
PostgreSQL state store v2 (dapr#3250)
Signed-off-by: ItalyPaleAle <[email protected]> Co-authored-by: Bernd Verst <[email protected]>
1 parent 9dba9c8 commit 73997a2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+3263
-386
lines changed

.build-tools/component-folders.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,5 @@ excludeFolders:
4040
- state/gcp
4141
- state/hashicorp
4242
- state/oci
43+
- state/postgresql
4344
- state/utils

.github/scripts/test-info.mjs

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -615,14 +615,17 @@ const components = {
615615
conformanceSetup: 'conformance-state.cloudflare.workerskv-setup.sh',
616616
conformanceDestroy: 'conformance-state.cloudflare.workerskv-destroy.sh',
617617
},
618-
'state.cockroachdb': {
618+
'state.cockroachdb.v1': {
619619
conformance: true,
620620
certification: true,
621621
conformanceSetup: 'docker-compose.sh cockroachdb',
622622
sourcePkg: [
623623
'state/cockroachdb',
624-
'common/component/postgresql',
624+
'common/component/postgresql/interfaces',
625+
'common/component/postgresql/transactions',
626+
'common/component/postgresql/v1',
625627
'common/component/sql',
628+
'common/component/sql/migrations',
626629
],
627630
},
628631
'state.etcd.v1': {
@@ -664,26 +667,32 @@ const components = {
664667
conformance: true,
665668
conformanceSetup: 'docker-compose.sh oracledatabase',
666669
},
667-
'state.postgresql': {
670+
'state.postgresql.v1': {
668671
certification: true,
669672
sourcePkg: [
670-
'state/postgresql',
673+
'state/postgresql/v1',
671674
'common/authentication/postgresql',
672-
'common/component/postgresql',
675+
'common/component/postgresql/interfaces',
676+
'common/component/postgresql/transactions',
677+
'common/component/postgresql/v1',
673678
'common/component/sql',
679+
'common/component/sql/migrations',
674680
],
675681
},
676-
'state.postgresql.docker': {
682+
'state.postgresql.v1.docker': {
677683
conformance: true,
678684
conformanceSetup: 'docker-compose.sh postgresql',
679685
sourcePkg: [
680-
'state/postgresql',
686+
'state/postgresql/v1',
681687
'common/authentication/postgresql',
682-
'common/component/postgresql',
688+
'common/component/postgresql/interfaces',
689+
'common/component/postgresql/transactions',
690+
'common/component/postgresql/v1',
683691
'common/component/sql',
692+
'common/component/sql/migrations',
684693
],
685694
},
686-
'state.postgresql.azure': {
695+
'state.postgresql.v1.azure': {
687696
conformance: true,
688697
requiredSecrets: [
689698
'AzureDBPostgresConnectionString',
@@ -692,10 +701,53 @@ const components = {
692701
'AzureDBPostgresTenantId',
693702
],
694703
sourcePkg: [
695-
'state/postgresql',
704+
'state/postgresql/v1',
696705
'common/authentication/postgresql',
697-
'common/component/postgresql',
706+
'common/component/postgresql/interfaces',
707+
'common/component/postgresql/transactions',
708+
'common/component/postgresql/v1',
698709
'common/component/sql',
710+
'common/component/sql/migrations',
711+
],
712+
},
713+
'state.postgresql.v2': {
714+
certification: true,
715+
sourcePkg: [
716+
'state/postgresql/v2',
717+
'common/authentication/postgresql',
718+
'common/component/postgresql/interfaces',
719+
'common/component/postgresql/transactions',
720+
'common/component/sql',
721+
'common/component/sql/migrations',
722+
],
723+
},
724+
'state.postgresql.v2.docker': {
725+
conformance: true,
726+
conformanceSetup: 'docker-compose.sh postgresql',
727+
sourcePkg: [
728+
'state/postgresql/v2',
729+
'common/authentication/postgresql',
730+
'common/component/postgresql/interfaces',
731+
'common/component/postgresql/transactions',
732+
'common/component/sql',
733+
'common/component/sql/migrations',
734+
],
735+
},
736+
'state.postgresql.v2.azure': {
737+
conformance: true,
738+
requiredSecrets: [
739+
'AzureDBPostgresConnectionString',
740+
'AzureDBPostgresClientId',
741+
'AzureDBPostgresClientSecret',
742+
'AzureDBPostgresTenantId',
743+
],
744+
sourcePkg: [
745+
'state/postgresql/v2',
746+
'common/authentication/postgresql',
747+
'common/component/postgresql/interfaces',
748+
'common/component/postgresql/transactions',
749+
'common/component/sql',
750+
'common/component/sql/migrations',
699751
],
700752
},
701753
'state.redis': {

bindings/postgres/metadata.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,17 @@ import (
2020

2121
type psqlMetadata struct {
2222
pgauth.PostgresAuthMetadata `mapstructure:",squash"`
23-
24-
// URL is the connection string to connect to the database.
25-
// Deprecated alias: use connectionString instead.
26-
URL string `mapstructure:"url"`
2723
}
2824

2925
func (m *psqlMetadata) InitWithMetadata(meta map[string]string) error {
3026
// Reset the object
3127
m.PostgresAuthMetadata.Reset()
32-
m.URL = ""
3328

3429
err := kitmd.DecodeMetadata(meta, &m)
3530
if err != nil {
3631
return err
3732
}
3833

39-
// Legacy options
40-
if m.ConnectionString == "" && m.URL != "" {
41-
m.ConnectionString = m.URL
42-
}
43-
4434
// Validate and sanitize input
4535
// Azure AD auth is supported for this component
4636
err = m.PostgresAuthMetadata.InitWithMetadata(meta, true)

bindings/postgres/metadata.yaml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,6 @@ metadata:
7070
database driver to choose.
7171
example: "5m"
7272
type: duration
73-
- name: url
74-
deprecated: true
75-
required: false
76-
description: |
77-
Deprecated alias for "connectionString"
78-
type: string
79-
sensitive: true
80-
example: |
81-
"user=dapr password=secret host=dapr.example.com port=5432 dbname=dapr sslmode=verify-ca"
8273
- name: queryExecMode
8374
required: false
8475
description: |

common/authentication/postgresql/metadata.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828

2929
// PostgresAuthMetadata contains authentication metadata for PostgreSQL components.
3030
type PostgresAuthMetadata struct {
31-
ConnectionString string `mapstructure:"connectionString"`
31+
ConnectionString string `mapstructure:"connectionString" mapstructurealiases:"url"`
3232
ConnectionMaxIdleTime time.Duration `mapstructure:"connectionMaxIdleTime"`
3333
MaxConns int `mapstructure:"maxConns"`
3434
UseAzureAD bool `mapstructure:"useAzureAD"`

common/component/postgresql/interfaces/interfaces.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2021 The Dapr Authors
2+
Copyright 2023 The Dapr Authors
33
Licensed under the Apache License, Version 2.0 (the "License");
44
you may not use this file except in compliance with the License.
55
You may obtain a copy of the License at
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
Copyright 2023 The Dapr Authors
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package transactions
15+
16+
import (
17+
"context"
18+
"fmt"
19+
"time"
20+
21+
"github.com/jackc/pgx/v5"
22+
23+
pginterfaces "github.com/dapr/components-contrib/common/component/postgresql/interfaces"
24+
"github.com/dapr/kit/logger"
25+
)
26+
27+
// ExecuteInTransaction executes a function in a transaction.
28+
// If the handler returns an error, the transaction is rolled back automatically.
29+
func ExecuteInTransaction[T any](ctx context.Context, log logger.Logger, db pginterfaces.PGXPoolConn, timeout time.Duration, fn func(ctx context.Context, tx pgx.Tx) (T, error)) (res T, err error) {
30+
// Start the transaction
31+
queryCtx, queryCancel := context.WithTimeout(ctx, timeout)
32+
defer queryCancel()
33+
tx, err := db.Begin(queryCtx)
34+
if err != nil {
35+
return res, fmt.Errorf("failed to begin transaction: %w", err)
36+
}
37+
38+
// Rollback in case of failure
39+
var success bool
40+
defer func() {
41+
if success {
42+
return
43+
}
44+
rollbackCtx, rollbackCancel := context.WithTimeout(ctx, timeout)
45+
defer rollbackCancel()
46+
rollbackErr := tx.Rollback(rollbackCtx)
47+
if rollbackErr != nil {
48+
// Log errors only
49+
log.Errorf("Error while attempting to roll back transaction: %v", rollbackErr)
50+
}
51+
}()
52+
53+
// Execute the callback
54+
res, err = fn(ctx, tx)
55+
if err != nil {
56+
return res, err
57+
}
58+
59+
// Commit the transaction
60+
queryCtx, queryCancel = context.WithTimeout(ctx, timeout)
61+
defer queryCancel()
62+
err = tx.Commit(queryCtx)
63+
if err != nil {
64+
return res, fmt.Errorf("failed to commit transaction: %w", err)
65+
}
66+
success = true
67+
68+
return res, nil
69+
}

common/component/postgresql/metadata.go renamed to common/component/postgresql/v1/metadata.go

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ limitations under the License.
1414
package postgresql
1515

1616
import (
17-
"fmt"
17+
"errors"
1818
"time"
1919

2020
pgauth "github.com/dapr/components-contrib/common/authentication/postgresql"
@@ -24,31 +24,28 @@ import (
2424
)
2525

2626
const (
27-
cleanupIntervalKey = "cleanupIntervalInSeconds"
28-
timeoutKey = "timeoutInSeconds"
29-
3027
defaultTableName = "state"
3128
defaultMetadataTableName = "dapr_metadata"
32-
defaultCleanupInternal = 3600 // In seconds = 1 hour
33-
defaultTimeout = 20 // Default timeout for network requests, in seconds
29+
defaultCleanupInternal = time.Hour
30+
defaultTimeout = 20 * time.Second // Default timeout for network requests
3431
)
3532

3633
type pgMetadata struct {
3734
pgauth.PostgresAuthMetadata `mapstructure:",squash"`
3835

3936
TableName string `mapstructure:"tableName"` // Could be in the format "schema.table" or just "table"
4037
MetadataTableName string `mapstructure:"metadataTableName"` // Could be in the format "schema.table" or just "table"
41-
Timeout time.Duration `mapstructure:"timeoutInSeconds"`
42-
CleanupInterval *time.Duration `mapstructure:"cleanupIntervalInSeconds"`
38+
Timeout time.Duration `mapstructure:"timeout" mapstructurealiases:"timeoutInSeconds"`
39+
CleanupInterval *time.Duration `mapstructure:"cleanupInterval" mapstructurealiases:"cleanupIntervalInSeconds"`
4340
}
4441

4542
func (m *pgMetadata) InitWithMetadata(meta state.Metadata, azureADEnabled bool) error {
4643
// Reset the object
4744
m.PostgresAuthMetadata.Reset()
4845
m.TableName = defaultTableName
4946
m.MetadataTableName = defaultMetadataTableName
50-
m.CleanupInterval = ptr.Of(defaultCleanupInternal * time.Second)
51-
m.Timeout = defaultTimeout * time.Second
47+
m.CleanupInterval = ptr.Of(defaultCleanupInternal)
48+
m.Timeout = defaultTimeout
5249

5350
// Decode the metadata
5451
err := metadata.DecodeMetadata(meta.Properties, &m)
@@ -64,18 +61,18 @@ func (m *pgMetadata) InitWithMetadata(meta state.Metadata, azureADEnabled bool)
6461

6562
// Timeout
6663
if m.Timeout < 1*time.Second {
67-
return fmt.Errorf("invalid value for '%s': must be greater than 0", timeoutKey)
64+
return errors.New("invalid value for 'timeout': must be greater than 1s")
6865
}
6966

7067
// Cleanup interval
7168
// Non-positive value from meta means disable auto cleanup.
72-
if m.CleanupInterval != nil && *m.CleanupInterval <= 0 {
73-
if meta.Properties[cleanupIntervalKey] == "" {
74-
// Unfortunately the mapstructure decoder decodes an empty string to 0, a missing key would be nil however
75-
m.CleanupInterval = ptr.Of(defaultCleanupInternal * time.Second)
76-
} else {
77-
m.CleanupInterval = nil
78-
}
69+
// We need to do this check because an empty string and "0" are treated differently by DecodeMetadata
70+
v, ok := meta.GetProperty("cleanupInterval", "cleanupIntervalInSeconds")
71+
if ok && v == "" {
72+
// Handle the case of an empty string, but present
73+
m.CleanupInterval = ptr.Of(defaultCleanupInternal)
74+
} else if (ok && v == "0") || (m.CleanupInterval != nil && *m.CleanupInterval <= 0) {
75+
m.CleanupInterval = nil
7976
}
8077

8178
return nil

0 commit comments

Comments
 (0)