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