From 07893511a0ecaf3b485b10c2e5b7a534ce4feca8 Mon Sep 17 00:00:00 2001 From: Sebastian Harl Date: Tue, 12 May 2015 22:31:44 +0200 Subject: [PATCH] client: Add helper functions for formatting and executing queries. These are basically taken over from the SysDB webui code which will be based on this new code in the future. --- client/query.go | 136 +++++++++++++++++++++++++++++++++++++++++++ client/query_test.go | 66 +++++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 client/query.go create mode 100644 client/query_test.go diff --git a/client/query.go b/client/query.go new file mode 100644 index 0000000..d1224f4 --- /dev/null +++ b/client/query.go @@ -0,0 +1,136 @@ +// +// Copyright (C) 2014-2015 Sebastian 'tokkee' Harl +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package client + +import ( + "errors" + "fmt" + "regexp" + "time" + + "github.com/sysdb/go/proto" + "github.com/sysdb/go/sysdb" +) + +// An Identifier is a string that may not be quoted or escaped in a query. +type Identifier string + +// The default format for date-time values. +var dtFormat = "2006-01-02 15:04:05" + +func stringify(values ...interface{}) ([]interface{}, error) { + str := make([]interface{}, len(values)) + for i, v := range values { + switch val := v.(type) { + case uint8, uint16, uint32, uint64, int8, int16, int32, int64, int: + str[i] = fmt.Sprintf("%d", val) + case float32, float64: + str[i] = fmt.Sprintf("%e", val) + case Identifier: + str[i] = string(val) + case string: + str[i] = proto.EscapeString(val) + case time.Time: + str[i] = val.Format(dtFormat) + default: + return nil, fmt.Errorf("cannot embed value %v of type %T in query", v, v) + } + } + return str, nil +} + +// The fmt package does not expose these errors except through the formatted +// string. Let's just assume that this pattern never occurs in a real query +// string (or else, users will have to work around this by not using +// QueryString()). +var badArgRE = regexp.MustCompile(`%!.?\(.+`) + +// QueryString formats a query string. The query q may include printf string +// verbs (%s) for each argument. The arguments may be of type Identifier, +// string, or time.Time and will be formatted to make them suitable for use in +// a query. +// +// This function tries to prevent injection attacks but it's not fool-proof. +// It will go away once the SysDB network protocol supports arguments to +// queries. +func QueryString(q string, args ...interface{}) (string, error) { + args, err := stringify(args...) + if err != nil { + return "", err + } + + str := fmt.Sprintf(q, args...) + + // Try to identify format string errors. + if e := badArgRE.Find([]byte(str)); e != nil { + return "", errors.New(string(e)) + } + + return str, nil +} + +// Query executes a query on the server. It returns a sysdb object on success. +func (c *Client) Query(q string) (interface{}, error) { + res, err := c.Call(&proto.Message{ + Type: proto.ConnectionQuery, + Raw: []byte(q), + }) + if err != nil { + return nil, err + } + if res.Type != proto.ConnectionData { + return nil, fmt.Errorf("unexpected result type %d", res.Type) + } + + t, err := res.DataType() + if err != nil { + return nil, fmt.Errorf("failed to unmarshal response: %v", err) + } + + var obj interface{} + switch t { + case proto.HostList: + var hosts []sysdb.Host + err = proto.Unmarshal(res, &hosts) + obj = hosts + case proto.Host: + var host sysdb.Host + err = proto.Unmarshal(res, &host) + obj = &host + case proto.Timeseries: + var ts sysdb.Timeseries + err = proto.Unmarshal(res, &ts) + obj = &ts + default: + return nil, fmt.Errorf("unsupported data type %d", t) + } + if err != nil { + return nil, fmt.Errorf("failed to unmarshal response: %v", err) + } + return obj, nil +} + +// vim: set tw=78 sw=4 sw=4 noexpandtab : diff --git a/client/query_test.go b/client/query_test.go new file mode 100644 index 0000000..c62fed7 --- /dev/null +++ b/client/query_test.go @@ -0,0 +1,66 @@ +// +// Copyright (C) 2015 Sebastian 'tokkee' Harl +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package client + +import ( + "testing" + "time" +) + +func TestQueryString(t *testing.T) { + ts, _ := time.Parse("2006-01-02 15:04:05", "2006-01-02 15:04:05") + for _, test := range []struct { + q string + args []interface{} + want string + wantErr bool + }{ + {"some %s; foo %s", []interface{}{"thing", "bar"}, "some 'thing'; foo 'bar'", false}, + {"s=%s", []interface{}{"'a"}, "s='''a'", false}, + {"t=%s", []interface{}{ts}, "t=2006-01-02 15:04:05", false}, + {"i=%s; f=%s", []interface{}{1234, 47.11}, "i=1234; f=4.711000e+01", false}, + {"t=%d", []interface{}{ts}, "", true}, + {"some %s; foo %s", []interface{}{"a", "b", "c"}, "", true}, + {"some %s; foo %s", []interface{}{"a"}, "", true}, + {"s=%s", []interface{}{`multi +line +text`}, "s='multi\nline\ntext'", false}, + {"s=%d", []interface{}{`multi +line +error`}, "", true}, + } { + s, err := QueryString(test.q, test.args...) + if s != test.want || (err != nil) != test.wantErr { + e := "" + if test.wantErr { + e = "" + } + t.Errorf("QueryString(%q, %v) = %q, %v; want %q, %s", test.q, test.args, s, err, test.want, e) + } + } +} + +// vim: set tw=78 sw=4 sw=4 noexpandtab : -- 2.30.2