1 ///
2 module dpq.query;
3 
4 import dpq.connection;
5 import dpq.value;
6 import dpq.exception;
7 import dpq.result;
8 
9 version(unittest)
10 {
11    import std.stdio;
12 }
13 
14 /**
15    A nice wrapper around various DB querying functions,
16    to fill even everyday PostgreSQL querying with joy.
17 
18    Examples:
19    -------------------
20    Connection c; // an established connection
21 
22    Query q; // Will not have the connection set (!!!)
23    q.connection = c; // We can set it manually
24 
25    q = Query(c); // Or simply use the constructor
26    -------------------
27  */
28 struct Query
29 {
30    private string _command;
31    private Value[] _params;
32    private Connection* _connection;
33 
34    /**
35       Constructs a new Query object, reusing the last opened connection.
36       Will fail if no connection has been established.
37 
38       A command string msut be provided, optionally, values can be provided too,
39       but will usually be added later on.
40 
41       The query internally keeps a list of params that it will be executed with.
42 
43       Please note that using the Query's empty constructor will NOT set the Query's
44       connection, and the Query will therefore be quite unusable unless you set the
45       connection later.
46 
47       Examples:
48       ---------------
49       auto q = Query("SELECT 1::INT");
50 
51       auto q = Query("SELECT $1::INT", 1);
52       ---------------
53     */
54    this(string command, Value[] params = [])
55    {
56       if (dpqDefaultConnection() == null)
57          throw new DPQException("Query: No established connection was found and none was provided.");
58 
59       _connection = dpqDefaultConnection();
60       _command = command;
61       _params = params;
62    }
63 
64    /**
65       Like the above constructor, except it also accepts a Connection as the first
66       param. A copy of the Connection is not made.
67 
68       Examples:
69       ---------------
70       Connection conn; // an established connection
71       auto q = Query(conn, "SELECT 1::INT");
72 
73       Connection conn; // an established connection
74       auto q = Query(conn, "SELECT $1::INT", 1);
75       ---------------
76     */
77    this(ref Connection conn, string command = "", Value[] params = [])
78    {
79       _connection = &conn;
80       _command = command;
81       _params = params;
82    }
83 
84    unittest
85    {
86       writeln(" * Query");
87 
88       dpqConnect("host=127.0.0.1 dbname=test user=test");
89 
90       writeln("\t * this()");
91       Query q;
92       // Empty constructor uses init values
93       assert(q._connection == null);
94 
95       writeln("\t * this(command, params[])");
96       string cmd = "some command";
97       q = Query(cmd);
98       assert(q._connection == dpqDefaultConnection(), `Default connection error`);
99       assert(q._command == cmd, `cmd`);
100       assert(q._params == [], `empty arr`);
101 
102       Connection c2 = Connection("host=127.0.0.1 dbname=test user=test");
103       writeln("\t * this(Connection, command, params[])");
104       q = Query(c2);
105       assert(q._connection == &c2);
106 
107       q = Query(cmd);
108       assert(q._connection == dpqDefaultConnection());
109    }
110 
111    /**
112       A setter for the connection.
113 
114       The connection MUST be set before executing the query, but it is a lot more
115       handy to simply use the constructor that takes the Connection instead of
116       using this.
117     */
118    @property void connection(ref Connection conn)
119    {
120       _connection = &conn;
121    }
122 
123    /**
124       A getter/setter pair for the command that will be executed.
125     */
126    @property string command()
127    {
128       return _command;
129    }
130 
131    /// ditto
132    @property void command(string c)
133    {
134       _command = c;
135    }
136 
137    /**
138       Add a param to the list of params that will be sent with the query.
139       It's probably a better idea to just use run function with all the values,
140       but in some cases, adding params one by one can result in a more readable code.
141     */
142    void addParam(T)(T val)
143    {
144       _params ~= Value(val);
145    }
146 
147    unittest
148    {
149       Query q;
150       assert(q._params.length == 0);
151 
152       q.addParam(1);
153 
154       assert(q._params.length == 1);
155       assert(q._params[0] == Value(1));
156    }
157 
158    /**
159       In reality just an alias to addParam, but can be chained to add multiple
160       params.
161 
162       Examples:
163       -----------------
164       auto q = Query("SELECT $1, $2, $3");
165       q << "something" << 123 << 'c';
166       -----------------
167     */
168    ref Query opBinary(string op, T)(T val)
169          if (op == "<<")
170    {
171       addParam(val);
172       return this;
173    }
174 
175    /**
176       Sets the query's command and resets the params. Connection is not affected
177       Useful if you want to reuse the same query object.
178     */
179    void opAssign(string str)
180    {
181       command = str;
182       _params = [];
183    }
184 
185    /**
186       Runs the Query, returning a Result object.
187       Optionally accepts a list of params for the query to be ran with. The params
188       are added to the query, and if the query is re-ran for the second time, do
189       not need to be added again.
190 
191       Examples:
192       ----------------
193       Connection c;
194       auto q = Query(c);
195       q = "SELECT $1";
196       q.run(123);
197       ----------------
198     */
199    Result run()
200    {
201       import std.datetime.stopwatch : StopWatch;
202 
203       StopWatch sw;
204       sw.start();
205 
206       Result r = _connection.execParams(_command, _params);
207       sw.stop();
208 
209       r.time = sw.peek();
210       return r;
211    }
212 
213    /// ditto
214    Result run(T...)(T params)
215    {
216       foreach (p; params)
217          addParam(p);
218 
219       return run();
220    }
221 
222    /// ditto, async
223    bool runAsync(T...)(T params)
224    {
225       foreach (p; params)
226          addParam(p);
227 
228       return runAsync();
229    }
230 
231    // ditto
232    bool runAsync()
233    {
234       _connection.sendParams(_command, _params);
235       return true; // FIXME: must return the actual result from PQsendQueryParams
236    }
237 
238    unittest
239    {
240       writeln("\t * run");
241 
242       auto c = Connection("host=127.0.0.1 dbname=test user=test");
243 
244       auto q = Query(c, "SELECT 1::INT");
245 
246       auto r = q.run();
247       assert(r.rows == 1);
248       assert(r.columns == 1);
249       assert(r[0][0].as!int == 1);
250 
251       writeln("\t\t * async");
252       q.runAsync();
253 
254       r = c.lastResult();
255       assert(r.rows == 1);
256       assert(r.columns == 1);
257       assert(r[0][0].as!int == 1);
258 
259       writeln("\t * run(params...)");
260 
261       q = "SELECT $1";
262       q.run(1);
263       assert(r.rows == 1);
264       assert(r.columns == 1);
265       assert(r[0][0].as!int == 1);
266 
267       writeln("\t\t * async");
268 
269       q.runAsync(1);
270       r = c.lastResult();
271       assert(r.rows == 1);
272       assert(r.columns == 1);
273       assert(r[0][0].as!int == 1);
274    }
275 }