diff --git a/pkg/pgsql/connection.go b/pkg/pgsql/connection.go new file mode 100644 index 0000000..17d9b63 --- /dev/null +++ b/pkg/pgsql/connection.go @@ -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/[:] +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" +} diff --git a/pkg/readers/dbml/reader.go b/pkg/readers/dbml/reader.go index 96fd59c..c89a752 100644 --- a/pkg/readers/dbml/reader.go +++ b/pkg/readers/dbml/reader.go @@ -664,7 +664,7 @@ func (r *Reader) parseColumn(line, tableName, schemaName string) (*models.Column return column, constraint } -func splitInlineComment(line string) (string, string) { +func splitInlineComment(line string) (content string, inlineComment string) { commentStart := strings.Index(line, "//") if commentStart == -1 { return line, "" @@ -673,7 +673,7 @@ func splitInlineComment(line string) (string, string) { 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) if trimmed == "" || !strings.HasSuffix(trimmed, "]") { return trimmed, "" @@ -699,7 +699,7 @@ func splitColumnSignatureAndAttrs(line string) (string, string) { return trimmed, "" } -func parseColumnSignature(signature string) (string, string, bool) { +func parseColumnSignature(signature string) (columnName string, columnType string, ok bool) { signature = strings.TrimSpace(signature) if signature == "" { return "", "", false @@ -726,8 +726,8 @@ func parseColumnSignature(signature string) (string, string, bool) { return "", "", false } - columnName := stripQuotes(strings.TrimSpace(signature[:splitAt])) - columnType := stripWrappingQuotes(strings.TrimSpace(signature[splitAt:])) + columnName = stripQuotes(strings.TrimSpace(signature[:splitAt])) + columnType = stripWrappingQuotes(strings.TrimSpace(signature[splitAt:])) if columnName == "" || columnType == "" { return "", "", false } diff --git a/pkg/readers/pgsql/reader.go b/pkg/readers/pgsql/reader.go index fd4bc4b..fba8061 100644 --- a/pkg/readers/pgsql/reader.go +++ b/pkg/readers/pgsql/reader.go @@ -5,11 +5,10 @@ import ( "fmt" "strings" - "github.com/jackc/pgx/v5" - "git.warky.dev/wdevs/relspecgo/pkg/models" "git.warky.dev/wdevs/relspecgo/pkg/pgsql" "git.warky.dev/wdevs/relspecgo/pkg/readers" + "github.com/jackc/pgx/v5" ) // 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 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 { return err } diff --git a/pkg/writers/pgsql/writer.go b/pkg/writers/pgsql/writer.go index cd588b2..732273d 100644 --- a/pkg/writers/pgsql/writer.go +++ b/pkg/writers/pgsql/writer.go @@ -10,8 +10,6 @@ import ( "strings" "time" - "github.com/jackc/pgx/v5" - "git.warky.dev/wdevs/relspecgo/pkg/models" "git.warky.dev/wdevs/relspecgo/pkg/pgsql" "git.warky.dev/wdevs/relspecgo/pkg/writers" @@ -1353,7 +1351,7 @@ func (w *Writer) executeDatabaseSQL(db *models.Database, connString string) erro // Connect to database ctx := context.Background() - conn, err := pgx.Connect(ctx, connString) + conn, err := pgsql.Connect(ctx, connString, "writer-pgsql") if err != nil { return fmt.Errorf("failed to connect to database: %w", err) }