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