feat(pgsql): implement application_name handling in connection

This commit is contained in:
2026-04-26 17:45:25 +02:00
parent ed7130bba8
commit 837160b77a
4 changed files with 93 additions and 11 deletions

85
pkg/pgsql/connection.go Normal file
View File

@@ -0,0 +1,85 @@
package pgsql
import (
"context"
"fmt"
"runtime/debug"
"strings"
"github.com/jackc/pgx/v5"
)
const (
defaultApplicationPrefix = "relspecgo"
postgresIdentifierMaxLen = 63
)
// BuildApplicationName returns a PostgreSQL application_name in the form:
// relspecgo/<version>[:<component>]
func BuildApplicationName(component string) string {
appName := fmt.Sprintf("%s/%s", defaultApplicationPrefix, relspecVersion())
component = strings.TrimSpace(component)
if component != "" {
appName = appName + ":" + component
}
if len(appName) > postgresIdentifierMaxLen {
appName = appName[:postgresIdentifierMaxLen]
}
return appName
}
// ParseConfigWithApplicationName parses a connection string and applies a default
// application_name when one is not explicitly provided by the caller.
func ParseConfigWithApplicationName(connString, component string) (*pgx.ConnConfig, error) {
cfg, err := pgx.ParseConfig(connString)
if err != nil {
return nil, err
}
if cfg.RuntimeParams == nil {
cfg.RuntimeParams = map[string]string{}
}
if strings.TrimSpace(cfg.RuntimeParams["application_name"]) == "" {
cfg.RuntimeParams["application_name"] = BuildApplicationName(component)
}
return cfg, nil
}
// Connect establishes a PostgreSQL connection with a default relspec
// application_name when the caller does not provide one in the DSN.
func Connect(ctx context.Context, connString, component string) (*pgx.Conn, error) {
cfg, err := ParseConfigWithApplicationName(connString, component)
if err != nil {
return nil, err
}
return pgx.ConnectConfig(ctx, cfg)
}
func relspecVersion() string {
info, ok := debug.ReadBuildInfo()
if !ok {
return "dev"
}
version := strings.TrimSpace(info.Main.Version)
if version != "" && version != "(devel)" {
return version
}
for _, setting := range info.Settings {
if setting.Key == "vcs.revision" {
revision := strings.TrimSpace(setting.Value)
if len(revision) >= 7 {
return revision[:7]
}
if revision != "" {
return revision
}
}
}
return "dev"
}

View File

@@ -664,7 +664,7 @@ func (r *Reader) parseColumn(line, tableName, schemaName string) (*models.Column
return column, constraint return column, constraint
} }
func splitInlineComment(line string) (string, string) { func splitInlineComment(line string) (content string, inlineComment string) {
commentStart := strings.Index(line, "//") commentStart := strings.Index(line, "//")
if commentStart == -1 { if commentStart == -1 {
return line, "" return line, ""
@@ -673,7 +673,7 @@ func splitInlineComment(line string) (string, string) {
return strings.TrimSpace(line[:commentStart]), strings.TrimSpace(line[commentStart+2:]) return strings.TrimSpace(line[:commentStart]), strings.TrimSpace(line[commentStart+2:])
} }
func splitColumnSignatureAndAttrs(line string) (string, string) { func splitColumnSignatureAndAttrs(line string) (signature string, attrs string) {
trimmed := strings.TrimSpace(line) trimmed := strings.TrimSpace(line)
if trimmed == "" || !strings.HasSuffix(trimmed, "]") { if trimmed == "" || !strings.HasSuffix(trimmed, "]") {
return trimmed, "" return trimmed, ""
@@ -699,7 +699,7 @@ func splitColumnSignatureAndAttrs(line string) (string, string) {
return trimmed, "" return trimmed, ""
} }
func parseColumnSignature(signature string) (string, string, bool) { func parseColumnSignature(signature string) (columnName string, columnType string, ok bool) {
signature = strings.TrimSpace(signature) signature = strings.TrimSpace(signature)
if signature == "" { if signature == "" {
return "", "", false return "", "", false
@@ -726,8 +726,8 @@ func parseColumnSignature(signature string) (string, string, bool) {
return "", "", false return "", "", false
} }
columnName := stripQuotes(strings.TrimSpace(signature[:splitAt])) columnName = stripQuotes(strings.TrimSpace(signature[:splitAt]))
columnType := stripWrappingQuotes(strings.TrimSpace(signature[splitAt:])) columnType = stripWrappingQuotes(strings.TrimSpace(signature[splitAt:]))
if columnName == "" || columnType == "" { if columnName == "" || columnType == "" {
return "", "", false return "", "", false
} }

View File

@@ -5,11 +5,10 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/jackc/pgx/v5"
"git.warky.dev/wdevs/relspecgo/pkg/models" "git.warky.dev/wdevs/relspecgo/pkg/models"
"git.warky.dev/wdevs/relspecgo/pkg/pgsql" "git.warky.dev/wdevs/relspecgo/pkg/pgsql"
"git.warky.dev/wdevs/relspecgo/pkg/readers" "git.warky.dev/wdevs/relspecgo/pkg/readers"
"github.com/jackc/pgx/v5"
) )
// Reader implements the readers.Reader interface for PostgreSQL databases // Reader implements the readers.Reader interface for PostgreSQL databases
@@ -244,7 +243,7 @@ func (r *Reader) ReadTable() (*models.Table, error) {
// connect establishes a connection to the PostgreSQL database // connect establishes a connection to the PostgreSQL database
func (r *Reader) connect() error { func (r *Reader) connect() error {
conn, err := pgx.Connect(r.ctx, r.options.ConnectionString) conn, err := pgsql.Connect(r.ctx, r.options.ConnectionString, "reader-pgsql")
if err != nil { if err != nil {
return err return err
} }

View File

@@ -10,8 +10,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/jackc/pgx/v5"
"git.warky.dev/wdevs/relspecgo/pkg/models" "git.warky.dev/wdevs/relspecgo/pkg/models"
"git.warky.dev/wdevs/relspecgo/pkg/pgsql" "git.warky.dev/wdevs/relspecgo/pkg/pgsql"
"git.warky.dev/wdevs/relspecgo/pkg/writers" "git.warky.dev/wdevs/relspecgo/pkg/writers"
@@ -1353,7 +1351,7 @@ func (w *Writer) executeDatabaseSQL(db *models.Database, connString string) erro
// Connect to database // Connect to database
ctx := context.Background() ctx := context.Background()
conn, err := pgx.Connect(ctx, connString) conn, err := pgsql.Connect(ctx, connString, "writer-pgsql")
if err != nil { if err != nil {
return fmt.Errorf("failed to connect to database: %w", err) return fmt.Errorf("failed to connect to database: %w", err)
} }