dsn.go (147 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to you under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package avatica import ( "errors" "fmt" "net/url" "strconv" "strings" "time" ) type authentication int const ( none authentication = iota basic digest spnego ) // Config is a configuration parsed from a DSN string type Config struct { endpoint string maxRowsTotal int64 frameMaxSize int32 location *time.Location schema string transactionIsolation uint32 batching bool authentication authentication avaticaUser string avaticaPassword string principal krb5Principal keytab string krb5Conf string krb5CredentialCache string } type krb5Principal struct { username string realm string } // ParseDSN parses a DSN string to a Config func ParseDSN(dsn string) (*Config, error) { conf := &Config{ maxRowsTotal: -1, frameMaxSize: -1, location: time.UTC, transactionIsolation: 0, batching: false, } parsed, err := url.ParseRequestURI(dsn) if err != nil { return nil, fmt.Errorf("unable to parse DSN: %w", err) } queries := parsed.Query() if v := queries.Get("maxRowsTotal"); v != "" { maxRowTotal, err := strconv.Atoi(v) if err != nil { return nil, fmt.Errorf("invalid value for maxRowsTotal: %w", err) } conf.maxRowsTotal = int64(maxRowTotal) } if v := queries.Get("frameMaxSize"); v != "" { maxRowTotal, err := strconv.Atoi(v) if err != nil { return nil, fmt.Errorf("invalid value for frameMaxSize: %w", err) } conf.frameMaxSize = int32(maxRowTotal) } if v := queries.Get("location"); v != "" { loc, err := time.LoadLocation(v) if err != nil { return nil, fmt.Errorf("invalid value for location: %w", err) } conf.location = loc } if v := queries.Get("transactionIsolation"); v != "" { isolation, err := strconv.Atoi(v) if err != nil { return nil, fmt.Errorf("invalid value for transactionIsolation: %w", err) } if isolation < 0 || isolation > 8 || isolation&(isolation-1) != 0 { return nil, fmt.Errorf("transactionIsolation must be 0, 1, 2, 4 or 8, %d given", isolation) } conf.transactionIsolation = uint32(isolation) } if v := queries.Get("batching"); v != "" { if v == "true" { conf.batching = true } } if v := queries.Get("authentication"); v != "" { auth := strings.ToUpper(v) if auth == "BASIC" { conf.authentication = basic } else if auth == "DIGEST" { conf.authentication = digest } else if auth == "SPNEGO" { conf.authentication = spnego } else { return nil, errors.New("authentication must be either BASIC, DIGEST or SPNEGO") } if conf.authentication == basic || conf.authentication == digest { user := queries.Get("avaticaUser") if user == "" { return nil, fmt.Errorf("authentication is set to %s, but avaticaUser is empty", v) } conf.avaticaUser = user pass := queries.Get("avaticaPassword") if pass == "" { return nil, fmt.Errorf("authentication is set to %s, but avaticaPassword is empty", v) } conf.avaticaPassword = pass } else if conf.authentication == spnego { principal := queries.Get("principal") keytab := queries.Get("keytab") krb5Conf := queries.Get("krb5Conf") krb5CredentialCache := queries.Get("krb5CredentialCache") if principal == "" && keytab == "" && krb5Conf == "" && krb5CredentialCache == "" { return nil, errors.New("when using SPNEGO authetication, you must provide the principal, keytab and krb5Conf parameters or a krb5TicketCache parameter") } if !((principal != "" && keytab != "" && krb5Conf != "") || (principal == "" && keytab == "" && krb5Conf == "")) { return nil, errors.New("when using SPNEGO authentication with a principal and keytab, the principal, keytab and krb5Conf parameters are required") } if (principal != "" || keytab != "" || krb5Conf != "") && krb5CredentialCache != "" { return nil, errors.New("ambigious configuration for SPNEGO authentication: use either principal, keytab and krb5Conf or krb5TicketCache") } if principal != "" { splittedPrincipal := strings.Split(principal, "@") if len(splittedPrincipal) != 2 { return nil, fmt.Errorf("invalid kerberos principal (%s): the principal should be in the format primary/instance@realm where instance is optional", principal) } conf.principal = krb5Principal{ username: splittedPrincipal[0], realm: splittedPrincipal[1], } conf.keytab = keytab conf.krb5Conf = krb5Conf } else if krb5CredentialCache != "" { conf.krb5CredentialCache = krb5CredentialCache } } } if parsed.Path != "" { s := strings.Split(parsed.Path, "/") conf.schema = s[len(s)-1] } parsed.User = nil parsed.RawQuery = "" parsed.Fragment = "" conf.endpoint = parsed.String() return conf, nil }