1 module dpq.querybuilder;
2 
3 import dpq.value;
4 import dpq.query;
5 import dpq.connection;
6 import dpq.attributes;
7 import dpq.column;
8 
9 import std.typecons;
10 import std.string;
11 
12 version (unittest) import std.stdio;
13 
14 
15 enum Order : string
16 {
17 	asc = "ASC",
18 	desc = "DESC"
19 };
20 
21 private enum QueryType
22 {
23 	select = "SELECT",
24 	update = "UPDATE",
25 	insert = "INSERT",
26 	delete_ = "DELETE"
27 }
28 
29 struct QueryBuilder
30 {
31 	private
32 	{
33 		string[] _columns;
34 		string _table;
35 		string _filter;
36 
37 		string[] _orderBy;
38 		Order[] _orders;
39 
40 		int _limit = -1;
41 
42 		Nullable!int _offset;
43 		Value[string] _params;
44 		Value[] _indexParams;
45 
46 		// UPDATE's SET
47 		string[] _set;
48 
49 		int _paramIndex = 0;
50 		QueryType _type;
51 		Connection* _connection;
52 	}
53 
54 	this(ref Connection connection)
55 	{
56 		_connection = &connection;
57 	}
58 
59 	void opIndexAssign(T)(T val, string key)
60 	{
61 		_params[key] = val;
62 	}
63 
64 	unittest
65 	{
66 		writeln(" * QueryBuilder");
67 		writeln("\t * opIndexAssign");
68 
69 		QueryBuilder qb;
70 		qb["foo"] = 123;
71 		qb["bar"] = "456";
72 
73 		assert(qb._params["foo"] == Value(123));
74 		assert(qb._params["bar"] == Value("456"));
75 	}
76 
77 	// SELECT methods
78 	ref QueryBuilder select(string[] cols...)
79 	{
80 		_columns = cols;
81 		_type = QueryType.select;
82 		return this;
83 	}
84 
85 
86 	ref QueryBuilder select(Column[] cols...)
87 	{
88 		_type = QueryType.select;
89 		_columns = [];
90 		foreach(col; cols)
91 		{
92 			if (col.column != col.asName)
93 				_columns ~= "%s AS %s".format(col.column, col.asName);
94 			else
95 				_columns ~= col.column;
96 		}
97 
98 		return this;
99 	}
100 
101 	unittest
102 	{
103 		writeln("\t * select");
104 
105 		QueryBuilder qb;
106 		qb.select("foo", "bar", "baz");
107 		assert(qb._columns == ["foo", "bar", "baz"]);
108 
109 		Column[] cs = [Column("foo", "foo_test"), Column("bar")];
110 		qb.select(cs);
111 		assert(qb._columns == ["foo AS foo_test", "bar"]);
112 	}
113 
114 	ref QueryBuilder from(string from)
115 	{
116 		assert(
117 				_type == QueryType.select || _type == QueryType.delete_,
118 				"QueryBuilder.from() can only be used for SELECT or DELETE queries.");
119 
120 		_table = from;
121 		return this;
122 	}
123 
124 	ref QueryBuilder from(T)()
125 	{
126 		return from(relationName!T);
127 	}
128 
129 	unittest
130 	{
131 		writeln("\t\t * from");
132 		QueryBuilder qb;
133 		
134 		qb.from("sometable");
135 		assert(qb._table == "sometable");
136 
137 		struct Test {}
138 		qb.from!Test;
139 		assert(qb._table == "test");
140 	}
141 
142 	ref QueryBuilder where(string filter)
143 	{
144 		_filter = filter;
145 		return this;
146 	}
147 
148 	ref QueryBuilder where(T)(string col, T val)
149 	{
150 		_params["__where_filt"] = Value(val);
151 		_filter = "%s = {__where_filt}".format(col);
152 
153 		return this;
154 	}
155 
156 	unittest
157 	{
158 		writeln("\t\t * where");
159 
160 		string str = "a = $1 AND b = $2";
161 		QueryBuilder qb;
162 		qb.where(str);
163 		assert(qb._filter == str);
164 
165 		qb.where("some_field", 1);
166 		assert(qb._filter == "some_field = {__where_filt}");
167 		assert(qb._params["__where_filt"] == Value(1));
168 	}
169 
170 	ref QueryBuilder order(string col, Order order)
171 	{
172 		assert(_type == QueryType.select, "QueryBuilder.order() can only be used for SELECT queries.");
173 		_orderBy ~= col;
174 		_orders ~= order;
175 		return this;
176 	}
177 
178 	unittest
179 	{
180 		writeln("\t\t * order");
181 
182 		QueryBuilder qb;
183 
184 		qb.order("some_col", Order.asc);
185 
186 		assert(qb._orderBy[0] == "some_col");
187 		assert(qb._orders[0] == Order.asc);
188 
189 		qb.order("some_other_col", Order.desc);
190 
191 		assert(qb._orderBy[1] == "some_other_col");
192 		assert(qb._orders[1] == Order.desc);
193 	}
194 	
195 	ref QueryBuilder limit(int limit)
196 	{
197 		assert(_type == QueryType.select, "QueryBuilder.limit() can only be used for SELECT queries.");
198 
199 		_limit = limit;
200 		return this;
201 	}
202 
203 	unittest
204 	{
205 		writeln("\t\t * limit");
206 
207 		QueryBuilder qb;
208 		qb.limit(1);
209 		assert(qb._limit == 1);
210 	}
211 
212 	ref QueryBuilder offset(int offset)
213 	{
214 		assert(_type == QueryType.select, "QueryBuilder.offset() can only be used for SELECT queries.");
215 		_offset = offset;
216 		return this;
217 	}
218 
219 	unittest
220 	{
221 		writeln("\t\t * offset");
222 
223 		QueryBuilder qb;
224 		qb.offset(1);
225 		assert(qb._offset == 1);
226 	}
227 
228 	// UPDATE methods
229 	ref QueryBuilder update(string table)
230 	{
231 		_table = table;
232 		_type = QueryType.update;
233 		return this;
234 	}
235 	
236 	ref QueryBuilder update(T)()
237 	{
238 		return update(relationName!T);
239 	}
240 
241 	unittest
242 	{
243 		QueryBuilder qb;
244 		qb.update("sometable");
245 
246 		assert(qb._table == "sometable");
247 		assert(qb._type == QueryType.update);
248 
249 		struct Test {}
250 
251 		qb.update!Test;
252 		assert(qb._type == QueryType.update);
253 		assert(qb._table == relationName!Test);
254 	}
255 
256 	ref QueryBuilder set(Value[string] params)
257 	{
258 		foreach (col, val; params)
259 			set(col, val);
260 
261 		return this;
262 	}
263 
264 	ref QueryBuilder set(T)(string col, T val)
265 	{
266 		assert(_type == QueryType.update, "QueryBuilder.set() can only be used on UPDATE queries");
267 
268 		_params[col] = val;
269 		_set ~= "\"%s\" = {%s}".format(col, col);
270 
271 		return this;
272 	}
273 
274 	ref QueryBuilder set(string set)
275 	{
276 		_set ~= set;
277 
278 		return this;
279 	}
280 
281 	unittest
282 	{
283 		writeln("\t * set");
284 
285 		QueryBuilder qb;
286 		qb.update("foo")
287 			.set("some_col", 1);
288 
289 		assert(qb._params["some_col"] == Value(1));
290 		assert(qb._set.length == 1);
291 		assert(qb._set[0] == "\"some_col\" = {some_col}");
292 
293 		qb.set([
294 				"col1": Value(1),
295 				"col2": Value(2)]);
296 
297 		assert(qb._params.length == 3);
298 		assert(qb._set.length == 3);
299 		assert(qb._set[1] == "\"col1\" = {col1}");
300 		assert(qb._set[2] == "\"col2\" = {col2}");
301 
302 		string str = "asd = $1";
303 		qb.set(str);
304 		assert(qb._params.length == 3);
305 		assert(qb._set.length == 4);
306 		assert(qb._set[3] == str);
307 	}
308 	
309 	// INSERT methods
310 	ref QueryBuilder insert(string table, string[] cols...)
311 	{
312 		_table = table;
313 		_columns = cols;
314 		_type = QueryType.insert;
315 		return this;
316 	}
317 
318 
319 	ref QueryBuilder insert(string table, Column[] cols...)
320 	{
321 		import std.array;
322 		return insert(table, array(cols.map!(c => c.column)));
323 	}
324 
325 	unittest
326 	{
327 		writeln("\t * insert");
328 
329 		QueryBuilder qb;
330 		qb.insert("table", "col1", "col2");
331 
332 		assert(qb._type == QueryType.insert);
333 		assert(qb._table == "table");
334 		assert(qb._columns == ["col1", "col2"]);
335 
336 		Column[] cs = [
337 			Column("some_col", "stupid_as_name"),
338 			Column("qwe")];
339 
340 		qb.insert("table2", cs);
341 		assert(qb._table == "table2");
342 		assert(qb._columns.length == 2);
343 		assert(qb._columns == ["some_col", "qwe"]);
344 	}
345 
346 	ref QueryBuilder values(T...)(T vals)
347 	{
348 		assert(_type == QueryType.insert, "QueryBuilder.values() can only be used on INSERT queries");
349 
350 		foreach (val; vals)
351 			addValue(val);
352 
353 		return this;
354 	}
355 
356 	ref QueryBuilder values(Value[] vals)
357 	{
358 		assert(_type == QueryType.insert, "QueryBuilder.values() can only be used on INSERT queries");
359 
360 		foreach (val; vals)
361 			addValue(val);
362 
363 		return this;
364 	}
365 
366 	unittest
367 	{
368 		writeln("\t * values");
369 
370 		QueryBuilder qb;
371 		qb.insert("table", "col")
372 			.values(1, 2, 3);
373 
374 		assert(qb._type == QueryType.insert);
375 		assert(qb._indexParams.length == 3);
376 		assert(qb._indexParams == [Value(1), Value(2), Value(3)]);
377 
378 		qb.values([Value(4), Value(5)]);
379 		assert(qb._indexParams.length == 5);
380 		assert(qb._indexParams == [Value(1), Value(2), Value(3), Value(4), Value(5)]);
381 	}
382 
383 	ref QueryBuilder remove()
384 	{
385 		_type = QueryType.delete_;
386 		return this;
387 	}
388 
389 	ref QueryBuilder remove(string table)
390 	{
391 		from(table);
392 		return remove();
393 	}
394 
395 	ref QueryBuilder remove(T)()
396 	{
397 		return remove(relationName!T);
398 	}
399 
400 	unittest
401 	{
402 		writeln("\t * remove");
403 
404 		struct Test {}
405 		QueryBuilder qb;
406 		qb.remove!Test;
407 
408 		assert(qb._type == QueryType.delete_);
409 		assert(qb._table == relationName!Test);
410 	}
411 
412 	ref QueryBuilder addValue(T)(T val)
413 	{
414 		_indexParams ~= Value(val);
415 		return this;
416 	}
417 
418 	// Other stuff
419 
420 	private string replaceParams(string str)
421 	{
422 		int index = _paramIndex;
423 
424 		foreach (param, val; _params)
425 			str = str.replace("{" ~ param ~ "}", "$%d".format(++index));
426 
427 		return str;
428 	}
429 
430 	unittest
431 	{
432 		writeln("\t * replaceParams");
433 		QueryBuilder qb;
434 		string str = "SELECT {foo} FROM table WHERE id = {bar} AND name = '{baz}'";
435 		qb["foo"] = "a";
436 		qb["bar"] = "b";
437 
438 		str = qb.replaceParams(str);
439 
440 		// No idea what the order might be
441 		assert(
442 				str == "SELECT $1 FROM table WHERE id = $2 AND name = '{baz}'" ||
443 				str == "SELECT $2 FROM table WHERE id = $1 AND name = '{baz}'");
444 	}
445 
446 	private string selectCommand()
447 	{
448 		string cols;
449 		if (_columns.length == 0)
450 			cols = "*";
451 		else
452 			cols = _columns.join(", ");
453 
454 		string str = "SELECT %s FROM \"%s\"".format(cols, _table);
455 
456 		if (_filter.length > 0)
457 			str ~= " WHERE " ~ _filter;
458 
459 		if (_orderBy.length > 0)
460 		{
461 			str ~= " ORDER BY ";
462 			for (int i = 0; i < _orderBy.length; ++i)
463 				str ~= "\"" ~ _orderBy[i] ~ "\" " ~ _orders[i] ~ ", ";
464 			str = str[0 .. $ - 2];
465 		}
466 
467 		if (_limit != -1)
468 			str ~= " LIMIT %d".format(_limit);
469 
470 		if (!_offset.isNull)
471 			str ~= " OFFSET %d".format(_offset);
472 
473 		return replaceParams(str);
474 	}
475 
476 	unittest
477 	{
478 		writeln("\t * selectCommand");
479 
480 		QueryBuilder qb;
481 		qb.select("col")
482 			.from("table")
483 			.where("id", 1)
484 			.limit(1)
485 			.offset(1);
486 
487 		string str = qb.command();
488 		assert(str == `SELECT col FROM "table" WHERE id = $1 LIMIT 1 OFFSET 1`, str);
489 	}
490 
491 	private string insertCommand()
492 	{
493 		int index = 0;
494 		string str = "INSERT INTO \"%s\" (%s) VALUES (%s)".format(
495 				_table,
496 				_columns.join(","),
497 				_indexParams.map!(v => "$%d".format(++index)).join(", ")
498 				);
499 
500 		return str;
501 	}
502 
503 	unittest
504 	{
505 		writeln("\t * insertCommand");
506 
507 		QueryBuilder qb;
508 		qb.insert("table", "col")
509 			.values(1);
510 
511 		string str = qb.command();
512 		assert(str == `INSERT INTO "table" (col) VALUES ($1)`);
513 	}
514 
515 	private string updateCommand()
516 	{
517 		string str = "UPDATE \"%s\" SET %s".format(
518 				_table,
519 				_set.join(", "));
520 
521 		if (_filter.length > 0)
522 			str ~= " WHERE " ~ _filter;
523 
524 		return replaceParams(str);
525 	}
526 
527 	unittest
528 	{
529 		writeln("\t * updateCommand");
530 
531 		QueryBuilder qb;
532 		qb.update("table")
533 			.set("col", 1)
534 			.where("foo", 2);
535 
536 		string str = qb.command();
537 		assert(
538 				str == `UPDATE "table" SET "col" = $1 WHERE foo = $2` ||
539 				str == `UPDATE "table" SET "col" = $2 WHERE foo = $1`);
540 	}
541 
542 	private string deleteCommand()
543 	{
544 		string str = "DELETE FROM \"%s\"".format(_table);
545 
546 		if (_filter.length > 0)
547 			str ~= " WHERE " ~ _filter;
548 
549 		return replaceParams(str);
550 	}
551 
552 	unittest
553 	{
554 		writeln("\t * deleteCommand");
555 
556 		QueryBuilder qb;
557 		qb.remove("table")
558 			.where("id", 1);
559 
560 		string str = qb.command();
561 		assert(str == `DELETE FROM "table" WHERE id = $1`);
562 	}
563 
564 	@property string command()
565 	{
566 		final switch (_type)
567 		{
568 			case QueryType.select:
569 				return selectCommand();
570 			case QueryType.update:
571 				return updateCommand();
572 			case QueryType.insert:
573 				return insertCommand();
574 			case QueryType.delete_:
575 				return deleteCommand();
576 		}
577 	}
578 
579 	@property private Value[] paramsArr()
580 	{
581 		Value[] res = _indexParams;
582 		//foreach (param; _indexParams)
583 		//	res ~= param;
584 
585 		foreach (param, val; _params)
586 			res ~= val;
587 
588 		return res;
589 	}
590 
591 	unittest
592 	{
593 		writeln("\t * paramsArr");
594 
595 		QueryBuilder qb;
596 		qb.addParams("1", "2", "3");
597 		qb["foo"] = 1;
598 		qb["bar"] = 2;
599 
600 		auto ps = qb.paramsArr();
601 		assert(
602 				ps == [Value("1"), Value("2"), Value("3"), Value(1), Value(2)] ||
603 				ps == [Value("1"), Value("2"), Value("3"), Value(2), Value(1)]);
604 	}
605 
606 	void addParam(T)(T val)
607 	{
608 		_indexParams ~= Value(val);
609 		++_paramIndex;
610 	}
611 
612 	unittest
613 	{
614 		writeln("\t * addParam");
615 
616 		QueryBuilder qb;
617 
618 		assert(qb._paramIndex == 0);
619 
620 		qb.addParam(1);
621 		assert(qb._paramIndex == 1);
622 		assert(qb._indexParams.length == 1);
623 		assert(qb._indexParams[0] == Value(1));
624 
625 		qb.addParam(2);
626 		assert(qb._paramIndex == 2);
627 		assert(qb._indexParams.length == 2);
628 		assert(qb._indexParams[1] == Value(2));
629 	}
630 
631 	ref QueryBuilder addParams(T...)(T vals)
632 	{
633 		foreach (val; vals)
634 			addParam(val);
635 
636 		return this;
637 	}
638 	
639 	unittest
640 	{
641 		writeln("\t * addParams");
642 
643 		QueryBuilder qb;
644 		qb.addParams(1, 2, 3);
645 
646 		assert(qb._indexParams.length == 3);
647 		assert(qb._paramIndex == 3);
648 	}
649 
650 	ref QueryBuilder opBinary(string op, T)(T val)
651 			if (op == "<<")
652 	{
653 		return addParam(val);
654 	}
655 
656 
657 	Query query()
658 	{
659 		if (_connection != null)
660 			return Query(*_connection, command, paramsArr);
661 
662 		return Query(command, paramsArr);
663 	}
664 
665 	Query query(ref Connection conn)
666 	{
667 		return Query(conn, command, paramsArr);
668 	}
669 	
670 }
671 
672 void param(alias P)(QueryBuilder b)
673 {
674 	b[P.stringof] = P;
675 }