func newDBAttribute()

in xray/sql_context.go [306:409]


func newDBAttribute(ctx context.Context, driverName string, d driver.Driver, conn driver.Conn, dsn string, filtered bool) (*dbAttribute, error) {
	var attr dbAttribute

	// Detect if DSN is a URL or not, set appropriate attribute
	urlDsn := dsn
	if !strings.Contains(dsn, "//") {
		urlDsn = "//" + urlDsn
	}
	// Here we're trying to detect things like `host:port/database` as a URL, which is pretty hard
	// So we just assume that if it's got a scheme, a user, or a query that it's probably a URL
	if u, err := url.Parse(urlDsn); err == nil && (u.Scheme != "" || u.User != nil || u.RawQuery != "" || strings.Contains(u.Path, "@")) {
		// Check that this isn't in the form of user/pass@host:port/db, as that will shove the host into the path
		if strings.Contains(u.Path, "@") {
			u, err = url.Parse(fmt.Sprintf("%s//%s%%2F%s", u.Scheme, u.Host, u.Path[1:]))
			if err != nil {
				return nil, err
			}
		}

		// Strip password from user:password pair in address
		if u.User != nil {
			uname := u.User.Username()

			// Some drivers use "user/pass@host:port" instead of "user:pass@host:port"
			// So we must manually attempt to chop off a potential password.
			// But we can skip this if we already found the password.
			if _, ok := u.User.Password(); !ok {
				uname = strings.Split(uname, "/")[0]
			}

			u.User = url.User(uname)
		}

		// Strip password and X-Amz-Security-Token (present in RDS IAM authentication) from query parameters
		q := u.Query()
		q.Del("password")
		q.Del("X-Amz-Security-Token")
		u.RawQuery = q.Encode()

		// In the case of known DSL sub segment name will be dbname@host
		host, _, _ := net.SplitHostPort(u.Host)
		if len(host) > 0 {
			attr.host = "@" + host
		} else {
			attr.host = host
		}

		attr.url = u.String()
		if !strings.Contains(dsn, "//") {
			attr.url = attr.url[2:]
		}
	} else {
		// We don't *think* it's a URL, so now we have to try our best to strip passwords from
		// some unknown DSL. We attempt to detect whether it's space-delimited or semicolon-delimited
		// then remove any keys with the name "password" or "pwd". This won't catch everything, but
		// from surveying the current (Jan 2017) landscape of drivers it should catch most.
		if filtered {
			attr.connectionString = dsn
		} else {
			attr.connectionString = stripPasswords(dsn)
		}
	}

	// Detect database type and use that to populate attributes
	var detectors []func(ctx context.Context, conn driver.Conn, attr *dbAttribute) error
	switch driverName {
	case "postgres":
		detectors = append(detectors, postgresDetector)
	case "mysql":
		detectors = append(detectors, mysqlDetector)
	default:
		detectors = append(detectors, postgresDetector, mysqlDetector, mssqlDetector, oracleDetector)
	}
	for _, detector := range detectors {
		if detector(ctx, conn, &attr) == nil {
			break
		}
		attr.databaseType = "Unknown"
		attr.databaseVersion = "Unknown"
		attr.user = "Unknown"
		attr.dbname = "Unknown"
	}

	// There's no standard to get SQL driver version information
	// So we invent an interface by which drivers can provide us this data
	type versionedDriver interface {
		Version() string
	}

	if vd, ok := d.(versionedDriver); ok {
		attr.driverVersion = vd.Version()
	} else {
		t := reflect.TypeOf(d)
		for t.Kind() == reflect.Ptr {
			t = t.Elem()
		}
		attr.driverVersion = t.PkgPath()
	}

	if attrHook != nil {
		attrHook(&attr)
	}
	return &attr, nil
}