1 //
2 // Copyright (C) 2014-2015 Sebastian 'tokkee' Harl <sh@tokkee.org>
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions
7 // are met:
8 // 1. Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // 2. Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 //
14 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
16 // TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
18 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21 // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 package client
28 import (
29 "errors"
30 "fmt"
31 "regexp"
32 "time"
34 "github.com/sysdb/go/proto"
35 "github.com/sysdb/go/sysdb"
36 )
38 // An Identifier is a string that may not be quoted or escaped in a query.
39 type Identifier string
41 // The default format for date-time values.
42 var dtFormat = "2006-01-02 15:04:05"
44 func stringify(values ...interface{}) ([]interface{}, error) {
45 str := make([]interface{}, len(values))
46 for i, v := range values {
47 switch val := v.(type) {
48 case uint8, uint16, uint32, uint64, int8, int16, int32, int64, int:
49 str[i] = fmt.Sprintf("%d", val)
50 case float32, float64:
51 str[i] = fmt.Sprintf("%e", val)
52 case Identifier:
53 str[i] = string(val)
54 case string:
55 str[i] = proto.EscapeString(val)
56 case time.Time:
57 str[i] = val.Format(dtFormat)
58 default:
59 return nil, fmt.Errorf("cannot embed value %v of type %T in query", v, v)
60 }
61 }
62 return str, nil
63 }
65 // The fmt package does not expose these errors except through the formatted
66 // string. Let's just assume that this pattern never occurs in a real query
67 // string (or else, users will have to work around this by not using
68 // QueryString()).
69 var badArgRE = regexp.MustCompile(`%!.?\(.+`)
71 // QueryString formats a query string. The query q may include printf string
72 // verbs (%s) for each argument. The arguments may be of type Identifier,
73 // string, or time.Time and will be formatted to make them suitable for use in
74 // a query.
75 //
76 // This function tries to prevent injection attacks but it's not fool-proof.
77 // It will go away once the SysDB network protocol supports arguments to
78 // queries.
79 func QueryString(q string, args ...interface{}) (string, error) {
80 args, err := stringify(args...)
81 if err != nil {
82 return "", err
83 }
85 str := fmt.Sprintf(q, args...)
87 // Try to identify format string errors.
88 if e := badArgRE.Find([]byte(str)); e != nil {
89 return "", errors.New(string(e))
90 }
92 return str, nil
93 }
95 // Query executes a query on the server. It returns a sysdb object on success.
96 func (c *Client) Query(q string) (interface{}, error) {
97 res, err := c.Call(&proto.Message{
98 Type: proto.ConnectionQuery,
99 Raw: []byte(q),
100 })
101 if err != nil {
102 return nil, err
103 }
104 if res.Type != proto.ConnectionData {
105 return nil, fmt.Errorf("unexpected result type %d", res.Type)
106 }
108 t, err := res.DataType()
109 if err != nil {
110 return nil, fmt.Errorf("failed to unmarshal response: %v", err)
111 }
113 var obj interface{}
114 switch t {
115 case proto.HostList:
116 var hosts []sysdb.Host
117 err = proto.Unmarshal(res, &hosts)
118 obj = hosts
119 case proto.Host:
120 var host sysdb.Host
121 err = proto.Unmarshal(res, &host)
122 obj = &host
123 case proto.Timeseries:
124 var ts sysdb.Timeseries
125 err = proto.Unmarshal(res, &ts)
126 obj = &ts
127 default:
128 return nil, fmt.Errorf("unsupported data type %d", t)
129 }
130 if err != nil {
131 return nil, fmt.Errorf("failed to unmarshal response: %v", err)
132 }
133 return obj, nil
134 }
136 // vim: set tw=78 sw=4 sw=4 noexpandtab :