Skip to content

Allow query parameters for ParseURL #1884

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 15, 2021
Merged

Conversation

dmke
Copy link
Contributor

@dmke dmke commented Sep 10, 2021

Background

In my Go programs, I usually want to modiffy the database connection options using CLI flags or environment variables. For example, to tune Postgres connections, I can set an environment variable like

PG="postgres://user@localhost:5432/testdb?sslmode=verify-full&connect_timeout=10" ./myprog

This is very handy for containerized applications.

In the Go program, one only needs to pass that string into ParseConfig:

config, err := pgx.ParseConfig(os.Getenv("PG"))
conn, err := pgx.Connect(config)

Issue

With the Go Redis library, this is not so simple:

_, err := redis.ParseURL("redis://localhost/0?dial_timeout=10")
//=> redis: no options supported

Here, the library requires me to either setup and parse additional flags/env vars, or extract the parameters from the URL before passing it to ParseURL.

Implementation details

This PR addresses this issue, by allowing (a subset of) the Option fields to be expressed as query parameters.

I've settled on the following behaviour:

  • field names are mapped using snake-case conversion
    • max_retries=2opt.MaxRetries == 2
    • pool_fifo=trueopt.PoolFIFO == true
    • mapping happens manually, I did not use the reflect package
  • query parameters "network", "addr", "username" and "password" are ignored
    • these are covered by the URL Scheme, Host, UserInfo fields
  • only scalar type fields are supported
    • i.e. bool, int, time.Duration
    • string is notably absent, because all Option string fields are already covered (but adding support is trivially easy)
    • struct fields (like opt.TLSConfig) are not supported yet. If they would ever be, someone needs to decide how to represent them as query parameters (cf. tls_config_insecure_skip_verify=true vs. tls_config.insecure_skip_verify=true vs. tls_config[insecure_skip_verify]=true)
    • functions (opt.OnConnect) are not supported, and probably will never be
  • for duration fields, values must be a valid input for time.ParseDuration
    • for convenience, plain integers are interpreted as seconds
    • read_timeout=60 == read_timeout=1mopt.ReadTimeout == time.Minute
  • only the last value is interpreted if a parameter is given multiple times
    • redis://.../0?db=1&db=2opt.DB == 2
  • unknown parameter names will result in an error
    • this is closest to the previous behaviour, which produced an error if any parameter was present

This is in preparation for supporting query parameters
in ParseURL:

- use an expected *Options instance to execute assertions on
- extract assertions into helper function
- enable parallel testing
- condense test table
@vmihailenco
Copy link
Collaborator

vmihailenco commented Sep 11, 2021

This looks good, we could make code a little bit cleaner:

type queryOptions struct {
    q *url.Values
}

// Err returns the first encountered error.
func (q *queryOptions) Err() error {}
func (q *queryOptions) Int64(name string) (int64, ok) {}

q := queryOptions{q: u.Query()}

if db, ok := q.Int64("db"); ok {}
if maxRetries, ok := q.Int64("max_retries"); ok {}

if err := q.Err(); err != nil {
    return err
}

What do you think?

@dmke
Copy link
Contributor Author

dmke commented Sep 11, 2021

Great suggestion. I'll start working on that.

@dmke
Copy link
Contributor Author

dmke commented Sep 11, 2021

@vmihailenco Done.

@dmke dmke force-pushed the feat/parseurl branch 2 times, most recently from 5edaee8 to e611262 Compare September 13, 2021 10:09
@dmke
Copy link
Contributor Author

dmke commented Sep 13, 2021

I have one question left: In the doc string, I've written

  • query parameters "network", "addr", "username" and "password" are ignored
    (these are covered by other URL attributes)

However, this code will currently fail:

ParseURL("redis://user@localhost/?username=admin")
//=> redis: unexpected option: username

I feel like, this is the correct behaviour, but it's inconsistent with the way other (duplicate) parameters are handled:

opts, _ := ParseURL("redis://user@localhost/0?db=1")
//=> opts.Database == 1

We've got basically three options here:

  1. Follow doc string (requires some delete(q.q, "username") before checking r.remaining()).
  2. Yield error (needs update to the doc string).
  3. Allow updating opts.Username via query parameters.

@vmihailenco, do you have a preference?

@vmihailenco
Copy link
Collaborator

Looks like ?db=123 is an exception needed to support Unix sockets unix://foo:bar@/tmp/redis.sock?db=1. Let's not duplicate other options.

However, this code will currently fail:

I think this code should continue to fail (option #2).

@dmke
Copy link
Contributor Author

dmke commented Sep 14, 2021

Okay, I'll update the doc string later, I'm currently a bit busy with different things.

Before this change, ParseURL would only accept a very restricted
set of URLs (it returned an error, if it encountered any parameter).

This commit introduces the ability to process URLs like

	redis://localhost/1?dial_timeout=10s

and similar.

Go programs which were providing a configuration tunable (e.g.
CLI flag, config entry or environment variable) to configure
the Redis connection now don't need to perform this task
themselves.
@dmke
Copy link
Contributor Author

dmke commented Sep 14, 2021

@vmihailenco Done.

@vmihailenco
Copy link
Collaborator

Thanks - this looks very good 👍 I am going to steal this for https://github.com/uptrace/bun/tree/master/driver/pgdriver configuration :)

@vmihailenco vmihailenco merged commit 9971188 into redis:master Sep 15, 2021
@dmke
Copy link
Contributor Author

dmke commented Sep 15, 2021

Sure, go ahead 😀

@dmke dmke deleted the feat/parseurl branch September 15, 2021 06:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants