Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
119 lines
3.5 KiB
Go
119 lines
3.5 KiB
Go
// Copyright © 2023 Ory Corp
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package sqlcon
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/ory/x/logrusx"
|
|
)
|
|
|
|
// ParseConnectionOptions parses values for max_conns, max_idle_conns, max_conn_lifetime from DSNs.
|
|
// It also returns the URI without those query parameters.
|
|
func ParseConnectionOptions(l *logrusx.Logger, dsn string) (maxConns int, maxIdleConns int, maxConnLifetime, maxIdleConnTime time.Duration, cleanedDSN string) {
|
|
maxConns = maxParallelism() * 2
|
|
maxIdleConns = maxParallelism()
|
|
cleanedDSN = dsn
|
|
|
|
hostPath, rawQuery, ok := strings.Cut(dsn, "?")
|
|
if !ok {
|
|
l.
|
|
WithField("sql_max_connections", maxConns).
|
|
WithField("sql_max_idle_connections", maxIdleConns).
|
|
WithField("sql_max_connection_lifetime", maxConnLifetime).
|
|
WithField("sql_max_idle_connection_time", maxIdleConnTime).
|
|
Debugf("No SQL connection options have been defined, falling back to default connection options.")
|
|
return
|
|
}
|
|
|
|
query, err := url.ParseQuery(rawQuery)
|
|
if err != nil {
|
|
l.
|
|
WithField("sql_max_connections", maxConns).
|
|
WithField("sql_max_idle_connections", maxIdleConns).
|
|
WithField("sql_max_connection_lifetime", maxConnLifetime).
|
|
WithField("sql_max_idle_connection_time", maxIdleConnTime).
|
|
WithError(err).
|
|
Warnf("Unable to parse SQL DSN query, falling back to default connection options.")
|
|
return
|
|
}
|
|
|
|
if v := query.Get("max_conns"); v != "" {
|
|
s, err := strconv.ParseInt(v, 10, 64)
|
|
if err != nil {
|
|
l.WithError(err).Warnf(`SQL DSN query parameter "max_conns" value %v could not be parsed to int, falling back to default value %d`, v, maxConns)
|
|
} else {
|
|
maxConns = int(s)
|
|
}
|
|
query.Del("max_conns")
|
|
}
|
|
|
|
if v := query.Get("max_idle_conns"); v != "" {
|
|
s, err := strconv.ParseInt(v, 10, 64)
|
|
if err != nil {
|
|
l.WithError(err).Warnf(`SQL DSN query parameter "max_idle_conns" value %v could not be parsed to int, falling back to default value %d`, v, maxIdleConns)
|
|
} else {
|
|
maxIdleConns = int(s)
|
|
}
|
|
query.Del("max_idle_conns")
|
|
}
|
|
|
|
if v := query.Get("max_conn_lifetime"); v != "" {
|
|
s, err := time.ParseDuration(v)
|
|
if err != nil {
|
|
l.WithError(err).Warnf(`SQL DSN query parameter "max_conn_lifetime" value %v could not be parsed to duration, falling back to default value %d`, v, maxConnLifetime)
|
|
} else {
|
|
maxConnLifetime = s
|
|
}
|
|
query.Del("max_conn_lifetime")
|
|
}
|
|
|
|
if v := query.Get("max_conn_idle_time"); v != "" {
|
|
s, err := time.ParseDuration(v)
|
|
if err != nil {
|
|
l.WithError(err).Warnf(`SQL DSN query parameter "max_conn_idle_time" value %v could not be parsed to duration, falling back to default value %d`, v, maxIdleConnTime)
|
|
} else {
|
|
maxIdleConnTime = s
|
|
}
|
|
query.Del("max_conn_idle_time")
|
|
}
|
|
cleanedDSN = fmt.Sprintf("%s?%s", hostPath, query.Encode())
|
|
|
|
return
|
|
}
|
|
|
|
// FinalizeDSN will return a finalized DSN URI.
|
|
func FinalizeDSN(l *logrusx.Logger, dsn string) string {
|
|
if !strings.HasPrefix(dsn, "mysql://") {
|
|
return dsn
|
|
}
|
|
|
|
var q url.Values
|
|
hostPath, query, ok := strings.Cut(dsn, "?")
|
|
|
|
if !ok {
|
|
q = make(url.Values)
|
|
} else {
|
|
var err error
|
|
q, err = url.ParseQuery(query)
|
|
if err != nil {
|
|
l.WithError(err).Warnf("Unable to parse SQL DSN query, could not finalize the DSN URI.")
|
|
return dsn
|
|
}
|
|
}
|
|
|
|
q.Set("multiStatements", "true")
|
|
q.Set("parseTime", "true")
|
|
|
|
// This causes an UPDATE to return the number of matching rows instead of
|
|
// the number of rows changed. This ensures compatibility with PostgreSQL and SQLite behavior.
|
|
q.Set("clientFoundRows", "true")
|
|
|
|
return fmt.Sprintf("%s?%s", hostPath, q.Encode())
|
|
}
|