Code

client: Add helper functions for formatting and executing queries.
[sysdb/go.git] / client / query.go
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
136 // vim: set tw=78 sw=4 sw=4 noexpandtab :