Skip to content

Commit 5d46e28

Browse files
committed
Create a sqlcommon with shared funcions/structs between gormv1 and gormv2
Signed-off-by: Marcos Yacob <marcosyacob@gmail.com>
1 parent fc9f1ac commit 5d46e28

19 files changed

Lines changed: 436 additions & 413 deletions
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package sqlcommon
2+
3+
import (
4+
"github.com/hashicorp/hcl/hcl/ast"
5+
)
6+
7+
// Configuration for the sql datastore implementation.
8+
// Pointer values are used to distinguish between "unset" and "zero" values.
9+
type Configuration struct {
10+
DatabaseTypeNode ast.Node `hcl:"database_type" json:"database_type"`
11+
ConnectionString string `hcl:"connection_string" json:"connection_string"`
12+
RoConnectionString string `hcl:"ro_connection_string" json:"ro_connection_string"`
13+
RootCAPath string `hcl:"root_ca_path" json:"root_ca_path"`
14+
ClientCertPath string `hcl:"client_cert_path" json:"client_cert_path"`
15+
ClientKeyPath string `hcl:"client_key_path" json:"client_key_path"`
16+
ConnMaxLifetime *string `hcl:"conn_max_lifetime" json:"conn_max_lifetime"`
17+
MaxOpenConns *int `hcl:"max_open_conns" json:"max_open_conns"`
18+
MaxIdleConns *int `hcl:"max_idle_conns" json:"max_idle_conns"`
19+
DisableMigration bool `hcl:"disable_migration" json:"disable_migration"`
20+
21+
DBTypeConfig *DBTypeConfig
22+
// Undocumented flags
23+
LogSQL bool `hcl:"log_sql" json:"log_sql"`
24+
}
25+
26+
type DBTypeConfig struct {
27+
AWSMySQL *AWSConfig `hcl:"aws_mysql" json:"aws_mysql"`
28+
AWSPostgres *AWSConfig `hcl:"aws_postgres" json:"aws_postgres"`
29+
DatabaseType string
30+
}
31+
32+
type AWSConfig struct {
33+
Region string `hcl:"region"`
34+
AccessKeyID string `hcl:"access_key_id"`
35+
SecretAccessKey string `hcl:"secret_access_key"`
36+
}
37+
38+
func (a *AWSConfig) Validate() error {
39+
if a.Region == "" {
40+
return NewSQLError("region must be specified")
41+
}
42+
return nil
43+
}
44+
45+
// GetConnectionString returns the connection string corresponding to the database connection.
46+
func GetConnectionString(cfg *Configuration, isReadOnly bool) string {
47+
connectionString := cfg.ConnectionString
48+
if isReadOnly {
49+
connectionString = cfg.RoConnectionString
50+
}
51+
return connectionString
52+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package sqlcommon
2+
3+
import (
4+
"net/url"
5+
"path/filepath"
6+
"runtime"
7+
)
8+
9+
// EmbellishSQLite3ConnString adds query values supported by
10+
// github.com/mattn/go-sqlite3 to enable journal mode and foreign key support.
11+
// These query values MUST be part of the connection string in order to be
12+
// enabled for *each* connection opened by db/sql. If the connection string is
13+
// not already a file: URI, it is converted first.
14+
func EmbellishSQLite3ConnString(connectionString string) (string, error) {
15+
// On Windows, when parsing an absolute path like "c:\tmp\lite",
16+
// "c" is parsed as the URL scheme
17+
if runtime.GOOS == "windows" && filepath.IsAbs(connectionString) {
18+
connectionString = "/" + connectionString
19+
}
20+
21+
u, err := url.Parse(connectionString)
22+
if err != nil {
23+
return "", NewWrappedSQLError(err)
24+
}
25+
26+
switch {
27+
case u.Scheme == "":
28+
// connection string is a path. move the path section into the
29+
// opaque section so it renders property for sqlite3, for example:
30+
// data.db = file:data.db
31+
// ./data.db = file:./data.db
32+
// /data.db = file:/data.db
33+
u.Scheme = "file"
34+
u.Opaque, u.Path = u.Path, ""
35+
case u.Scheme != "file":
36+
// only no scheme (i.e. file path) or file scheme is supported
37+
return "", NewSQLError("unsupported scheme %q", u.Scheme)
38+
}
39+
40+
q := u.Query()
41+
q.Set("_foreign_keys", "ON")
42+
q.Set("_journal_mode", "WAL")
43+
u.RawQuery = q.Encode()
44+
return u.String(), nil
45+
}

pkg/server/datastore/sqlstore/sqlite_test.go renamed to pkg/server/datastore/sqlcommon/connstring_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package sqlstore
1+
package sqlcommon
22

33
import (
44
"testing"
@@ -66,9 +66,15 @@ func TestEmbellishSQLite3ConnString(t *testing.T) {
6666

6767
for _, testCase := range testCases {
6868
t.Run(testCase.name, func(t *testing.T) {
69-
actual, err := embellishSQLite3ConnString(testCase.in)
69+
actual, err := EmbellishSQLite3ConnString(testCase.in)
7070
require.NoError(t, err)
7171
require.Equal(t, testCase.expected, actual)
7272
})
7373
}
7474
}
75+
76+
func TestEmbellishSQLite3ConnStringRejectsUnsupportedScheme(t *testing.T) {
77+
_, err := EmbellishSQLite3ConnString("mysql://data.db")
78+
require.Error(t, err)
79+
require.Contains(t, err.Error(), "unsupported scheme")
80+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package sqlcommon
2+
3+
import (
4+
"errors"
5+
6+
"github.com/go-sql-driver/mysql"
7+
"github.com/lib/pq"
8+
)
9+
10+
func IsPostgresConstraintViolation(err error) bool {
11+
var e *pq.Error
12+
ok := errors.As(err, &e)
13+
// "23xxx" is the constraint violation class for PostgreSQL
14+
return ok && e.Code.Class() == "23"
15+
}
16+
17+
func IsMySQLConstraintViolation(err error) bool {
18+
var e *mysql.MySQLError
19+
ok := errors.As(err, &e)
20+
return ok && e.Number == 1062 // ER_DUP_ENTRY
21+
}
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,74 @@
1-
package sqlstore
1+
package sqlcommon
22

3-
import (
4-
"fmt"
5-
)
3+
import "fmt"
64

75
const (
86
datastoreSQLErrorPrefix = "datastore-sql"
97
datastoreValidationErrorPrefix = "datastore-validation"
108
)
119

12-
type sqlError struct {
10+
type SQLError struct {
1311
err error
1412
msg string
1513
}
1614

17-
func (s *sqlError) Error() string {
15+
func (s *SQLError) Error() string {
1816
if s == nil {
1917
return ""
2018
}
21-
2219
if s.err != nil {
2320
return fmt.Sprintf("%s: %s", datastoreSQLErrorPrefix, s.err)
2421
}
25-
2622
return fmt.Sprintf("%s: %s", datastoreSQLErrorPrefix, s.msg)
2723
}
2824

29-
func (s *sqlError) Unwrap() error {
25+
func (s *SQLError) Unwrap() error {
3026
if s == nil {
3127
return nil
3228
}
33-
3429
return s.err
3530
}
3631

37-
type validationError struct {
32+
type ValidationError struct {
3833
err error
3934
msg string
4035
}
4136

42-
func (v *validationError) Error() string {
37+
func (v *ValidationError) Error() string {
4338
if v == nil {
4439
return ""
4540
}
46-
4741
if v.err != nil {
4842
return fmt.Sprintf("%s: %s", datastoreValidationErrorPrefix, v.err)
4943
}
50-
5144
return fmt.Sprintf("%s: %s", datastoreValidationErrorPrefix, v.msg)
5245
}
5346

54-
func (v *validationError) Unwrap() error {
47+
func (v *ValidationError) Unwrap() error {
5548
if v == nil {
5649
return nil
5750
}
58-
5951
return v.err
6052
}
6153

62-
func newSQLError(fmtMsg string, args ...any) error {
63-
return &sqlError{
64-
msg: fmt.Sprintf(fmtMsg, args...),
65-
}
54+
func NewSQLError(fmtMsg string, args ...any) error {
55+
return &SQLError{msg: fmt.Sprintf(fmtMsg, args...)}
6656
}
6757

68-
func newWrappedSQLError(err error) error {
58+
func NewWrappedSQLError(err error) error {
6959
if err == nil {
7060
return nil
7161
}
72-
73-
return &sqlError{
74-
err: err,
75-
}
62+
return &SQLError{err: err}
7663
}
7764

78-
func newValidationError(fmtMsg string, args ...any) error {
79-
return &validationError{
80-
msg: fmt.Sprintf(fmtMsg, args...),
81-
}
65+
func NewValidationError(fmtMsg string, args ...any) error {
66+
return &ValidationError{msg: fmt.Sprintf(fmtMsg, args...)}
8267
}
8368

84-
func newWrappedValidationError(err error) error {
69+
func NewWrappedValidationError(err error) error {
8570
if err == nil {
8671
return nil
8772
}
88-
89-
return &validationError{
90-
err: err,
91-
}
73+
return &ValidationError{err: err}
9274
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package sqlcommon
2+
3+
import (
4+
"errors"
5+
"testing"
6+
)
7+
8+
func TestSQLErrorPrefixesAndUnwrap(t *testing.T) {
9+
base := errors.New("boom")
10+
if got := NewWrappedSQLError(base).Error(); got != "datastore-sql: boom" {
11+
t.Fatalf("got %q", got)
12+
}
13+
if !errors.Is(NewWrappedSQLError(base), base) {
14+
t.Fatal("wrapped SQL error should unwrap to base")
15+
}
16+
if got := NewValidationError("bad %d", 7).Error(); got != "datastore-validation: bad 7" {
17+
t.Fatalf("got %q", got)
18+
}
19+
if NewWrappedSQLError(nil) != nil {
20+
t.Fatal("nil in => nil out")
21+
}
22+
if got := NewSQLError("bad %d", 3).Error(); got != "datastore-sql: bad 3" {
23+
t.Fatalf("got %q", got)
24+
}
25+
if got := NewWrappedValidationError(base).Error(); got != "datastore-validation: boom" {
26+
t.Fatalf("got %q", got)
27+
}
28+
if !errors.Is(NewWrappedValidationError(base), base) {
29+
t.Fatal("wrapped validation error should unwrap to base")
30+
}
31+
if NewWrappedValidationError(nil) != nil {
32+
t.Fatal("nil in => nil out")
33+
}
34+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//go:build cgo
2+
3+
package sqlcommon
4+
5+
import (
6+
"errors"
7+
8+
"github.com/mattn/go-sqlite3"
9+
)
10+
11+
func IsSQLiteConstraintViolation(err error) bool {
12+
if err == nil {
13+
return false
14+
}
15+
var e sqlite3.Error
16+
ok := errors.As(err, &e)
17+
return ok && e.Code == sqlite3.ErrConstraint
18+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//go:build !cgo
2+
3+
package sqlcommon
4+
5+
func IsSQLiteConstraintViolation(err error) bool {
6+
return false
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package sqlcommon
2+
3+
const (
4+
SQLiteVersionQuery = "SELECT sqlite_version()"
5+
PostgresVersionQuery = "SHOW server_version"
6+
MySQLVersionQuery = "SELECT VERSION()"
7+
)

pkg/server/datastore/sqlstore/dialect.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import (
44
"context"
55

66
"github.com/jinzhu/gorm"
7+
"github.com/spiffe/spire/pkg/server/datastore/sqlcommon"
78
)
89

910
type dialect interface {
10-
connect(ctx context.Context, cfg *configuration, isReadOnly bool) (db *gorm.DB, version string, supportsCTE bool, err error)
11+
connect(ctx context.Context, cfg *sqlcommon.Configuration, isReadOnly bool) (db *gorm.DB, version string, supportsCTE bool, err error)
1112
isConstraintViolation(err error) bool
1213
}

0 commit comments

Comments
 (0)