1 ///
2 module dpq.querybuilder;
3 
4 import dpq.value;
5 import dpq.query;
6 import dpq.connection;
7 import dpq.attributes;
8 import dpq.column;
9 
10 import std.typecons;
11 import std..string;
12 
13 import std.algorithm : map, sum;
14 import std.conv : to;
15 
16 version (unittest) import std.stdio;
17 
18 enum Order : string
19 {
20    none = "",
21    asc = "ASC",
22    desc = "DESC"
23 };
24 
25 private enum QueryType
26 {
27    select = "SELECT",
28    update = "UPDATE",
29    insert = "INSERT",
30    delete_ = "DELETE"
31 }
32 
33 /**
34    A filter builder struct meant for internal usage.
35 
36    Simplifies building a combination of AND/OR filters and makes the code more
37    readable.
38  */
39 private struct FilterBuilder
40 {
41    private string[][] _filters;
42 
43    ref FilterBuilder and(string filter) return
44    {
45       if (_filters.length == 0)
46          _filters.length++;
47 
48       _filters[$ - 1] ~= '(' ~ filter ~ ')';
49 
50       return this;
51    }
52 
53    ref FilterBuilder or() return
54    {
55       _filters ~= [];
56 
57       return this;
58    }
59 
60    /// Returns the actual number of lowest-level filters
61    long length()
62    {
63       return _filters.map!(f => f.length).sum;
64    }
65 
66 
67    string toString()
68    {
69       // Join inner filters by AND, outer by OR
70       return _filters.map!(innerFilter =>
71             innerFilter.join(" AND ")
72             ).join(" OR ");
73    }
74 }
75 
76 /**
77    Provides a nice way of writing queries in D, as well as some handy shortcuts
78    to working with D structures in the DB.
79 
80    Most method names are synonimous with the same keyword in SQL, but their order
81    does not matter.
82 
83    All of the methods can be chained.
84 
85    Examples:
86    ---------------------
87    auto qb = QueryBuilder()
88          .select("id")
89          .from!User
90          .where("posts > {posts}") // placeholders can be used
91          .order("posts", Order.desc)
92          .limit(5);
93 
94    // Placeholders will be replaced ONLY if they are specified.
95    // No need to escape anything, as it sent with execParams
96    qb["posts"] = 50;
97    ---------------------
98  */
99 struct QueryBuilder
100 {
101    private
102    {
103       // Columns to select
104       string[] _columns;
105 
106       // Table to select from
107       string _table;
108 
109       // A list of filters, lowest-level representing AND, and OR between those
110       FilterBuilder _filters;
111 
112       // List of ORDER BY columns and list of orders (ASC/DESC)
113       string[] _orderBy;
114       Order[] _orders;
115 
116       // Limit and offset values, using -1 is null value (not set)
117       Nullable!(int, -1) _limit = -1;
118       Nullable!(int, -1) _offset = -1;
119 
120       // Params to be used in the filters
121       Value[string] _params;
122 
123       // Params to be be used as positional
124       Value[] _indexParams;
125 
126       // Columns to list in RETURNING
127       string[] _returning;
128 
129       // UPDATE's SET
130       string[] _set;
131 
132       // Current index for positional params, needed because we allow mixing
133       // placeholders and positional params
134       int _paramIndex = 0;
135 
136       // Type of the query, (SELECT, UPDATE, ...)
137       QueryType _type;
138 
139       Connection* _connection;
140    }
141 
142    @property QueryBuilder dup()
143    {
144       // It's a struct, I'll copy anyway
145       return this;
146    }
147 
148    private string escapeIdentifier(string identifier)
149    {
150       if (_connection != null)
151          return _connection.escapeIdentifier(identifier);
152 
153       // This could potentionally be dangerous, I don't like it.
154       return `"%s"`.format(identifier);
155    }
156 
157    /**
158       Constructs a new QueryBuilder with the Connection, so we can directly
159       run queries with it.
160     */
161    this(ref Connection connection)
162    {
163       _connection = &connection;
164    }
165 
166    /**
167       Remembers the given key value pair, replacing the placeholders in the query
168       before running it.
169 
170       If the same key is set multiple times, the last value will apply.
171     */
172    void opIndexAssign(T)(T val, string key)
173    {
174       _params[key] = val;
175    }
176 
177    unittest
178    {
179       writeln(" * QueryBuilder");
180       writeln("\t * opIndexAssign");
181 
182       QueryBuilder qb;
183       qb["foo"] = 123;
184       qb["bar"] = "456";
185 
186       assert(qb._params["foo"] == Value(123));
187       assert(qb._params["bar"] == Value("456"));
188    }
189 
190    /**
191       Sets the builder's type to SELECT, a variadic array of column names to select
192     */
193    ref QueryBuilder select(string[] cols...) return
194    {
195       _columns = cols;
196       _type = QueryType.select;
197       return this;
198    }
199 
200    /**
201       Same as above, except it accepts a variadic array of Column type. Mostly
202       used internally.
203     */
204    ref QueryBuilder select(Column[] cols...) return
205    {
206       _type = QueryType.select;
207       _columns = [];
208       foreach(col; cols)
209       {
210          if (col.column != col.asName)
211             _columns ~= "%s AS %s".format(col.column, col.asName);
212          else
213             _columns ~= col.column;
214       }
215 
216       return this;
217    }
218 
219    /**
220       Selects all the given relation's properties
221 
222       Examples:
223       -----------------
224       struct User { @PK @serial int id; }
225       auto qb = QueryBuilder()
226             .select!User
227             .from!User
228             .where( ... );
229       -----------------
230 
231     */
232    ref QueryBuilder select(T)()
233    {
234       return select(AttributeList!T);
235    }
236 
237    unittest
238    {
239       writeln("\t * select");
240 
241       QueryBuilder qb;
242       qb.select("foo", "bar", "baz");
243       assert(qb._columns == ["foo", "bar", "baz"]);
244 
245       Column[] cs = [Column("foo", "foo_test"), Column("bar")];
246       qb.select(cs);
247       assert(qb._columns == ["foo AS foo_test", "bar"]);
248    }
249 
250    /**
251       Sets the builder's FROM value to the given string.
252     */
253    ref QueryBuilder from(string from) return
254    {
255       assert(
256             _type == QueryType.select || _type == QueryType.delete_,
257             "QueryBuilder.from() can only be used for SELECT or DELETE queries.");
258 
259       _table = from;
260       return this;
261    }
262 
263    /**
264       Same as above, but instead of accepting a string parameter, it instead
265       accepts a type as a template parameter, then sets the value to that
266       type's relation name. Preferred over the above version.
267     */
268    ref QueryBuilder from(T)()
269    {
270       return from(relationName!T);
271    }
272 
273    unittest
274    {
275       writeln("\t\t * from");
276       QueryBuilder qb;
277 
278       qb.from("sometable");
279       assert(qb._table == "sometable");
280 
281       struct Test {}
282       qb.from!Test;
283       assert(qb._table == "test");
284    }
285 
286    /**
287       Generates a placeholder that should be unique every time.
288 
289       This is required because we might filter by the same column twice (e.g.
290       where(["id": 1])).or.where(["id": 2]), in which case the second value for
291       ID would overwrite the first one.
292     */
293    private string safePlaceholder(string key)
294    {
295       /*
296          Because we only really need to be unique within this specific QB, just
297          a simple static counter is good enough. It could be put on the QB
298          instance instead, but this works just as well no need to complicate for
299          now.
300        */
301       static int count = 0;
302       return "%s_%d".format(key, ++count);
303    }
304 
305    /**
306       Adds new filter(s). Param placeholders are used, with the same names as
307       the AA keys. Calling this multiple times will AND the filters.
308 
309       Internally, a value placeholder will be used for each of the values, with
310       the same name as the column itself. Be careful not to overwrite these
311       before running the query.
312     */
313    ref QueryBuilder where(T)(T[string] filters)
314    {
315       foreach (key, value; filters)
316       {
317          auto placeholder = safePlaceholder(key);
318          _filters.and("%s = {%s}".format(escapeIdentifier(key), placeholder));
319          _params[placeholder] = value;
320       }
321 
322       return this;
323    }
324 
325    /**
326       Adds a new custom filter.
327 
328       Useful for filters that are not simple equality comparisons, or usage psql
329       functions. Nothing is escaped, make sure you properly escape the reserved
330       keywords if they are used as identifier names.
331 
332       Placeholders can be used with this, and even
333       positional params, since the order is predictable. Read addParam for
334       more information about that.
335     */
336    ref QueryBuilder where(T...)(string filter, T params)
337    {
338       _filters.and("%s".format(filter));
339 
340       foreach (param; params)
341          addParam(param);
342 
343       return this;
344    }
345 
346    /// Alias and to where, to allow stuff like User.where( ... ).and( ... )
347    alias and = where;
348 
349    /**
350       Once called, all additional parameters will be placed into their own group,
351       OR placed between each group of ANDs
352 
353       Examples:
354       --------------------
355       auto qb = QueryBuilder()
356          .select!User
357          .from!User
358          .where(["id ": 1])
359          .or
360          .where(["id": 2]);
361 
362          // Which will produce a filter like "... WHERE (id = $1) OR (id = $2)"
363       --------------------
364     */
365    ref QueryBuilder or() return
366    {
367       _filters.or();
368 
369       return this;
370    }
371 
372    unittest
373    {
374       writeln("\t\t * where");
375 
376       auto qb = QueryBuilder();
377 
378       qb.where(["something": "asd"]);
379       assert(qb._filters.length == 1);
380 
381       qb.where(["two": 2, "three": 3]);
382       assert(qb._filters.length == 3);
383    }
384 
385    /**
386       Sets the ORDER part of the query. Accepts a column name and an Order value.
387     */
388    ref QueryBuilder order(string col, Order order) return
389    {
390       assert(_type == QueryType.select, "QueryBuilder.order() can only be used for SELECT queries.");
391       _orderBy ~= col;
392       _orders ~= order;
393       return this;
394    }
395 
396    unittest
397    {
398       writeln("\t\t * order");
399 
400       QueryBuilder qb;
401 
402       qb.order("some_col", Order.asc);
403 
404       assert(qb._orderBy[0] == "some_col");
405       assert(qb._orders[0] == Order.asc);
406 
407       qb.order("some_other_col", Order.desc);
408 
409       assert(qb._orderBy[1] == "some_other_col");
410       assert(qb._orders[1] == Order.desc);
411    }
412 
413    /**
414       Sets the LIMIT in the query. Only for SELECT queries, obviously.
415     */
416    ref QueryBuilder limit(int limit) return
417    {
418       assert(_type == QueryType.select, "QueryBuilder.limit() can only be used for SELECT queries.");
419 
420       _limit = limit;
421       return this;
422    }
423 
424    unittest
425    {
426       writeln("\t\t * limit");
427 
428       QueryBuilder qb;
429       qb.limit(1);
430       assert(qb._limit == 1);
431    }
432 
433    /// OFFSET for queries
434    ref QueryBuilder offset(int offset) return
435    {
436       assert(_type == QueryType.select, "QueryBuilder.offset() can only be used for SELECT queries.");
437       _offset = offset;
438       return this;
439    }
440 
441    unittest
442    {
443       writeln("\t\t * offset");
444 
445       QueryBuilder qb;
446       qb.offset(1);
447       assert(qb._offset == 1);
448    }
449 
450    // UPDATE methods
451    ref QueryBuilder update(string table) return
452    {
453       _table = table;
454       _type = QueryType.update;
455       return this;
456    }
457 
458    ref QueryBuilder update(T)()
459    {
460       return update(relationName!T);
461    }
462 
463    unittest
464    {
465       QueryBuilder qb;
466       qb.update("sometable");
467 
468       assert(qb._table == "sometable");
469       assert(qb._type == QueryType.update);
470 
471       struct Test {}
472 
473       qb.update!Test;
474       assert(qb._type == QueryType.update);
475       assert(qb._table == relationName!Test);
476    }
477 
478    ref QueryBuilder set(T)(T[string] params)
479    {
480       foreach (col, val; params)
481          set(col, val);
482 
483       return this;
484    }
485 
486    ref QueryBuilder set(T)(string col, T val)
487    {
488       assert(_type == QueryType.update, "QueryBuilder.set() can only be used on UPDATE queries");
489 
490       _params[col] = val;
491       _set ~= "%s = {%s}".format(escapeIdentifier(col), col);
492 
493       return this;
494    }
495 
496    ref QueryBuilder set(string set) return
497    {
498       _set ~= set;
499 
500       return this;
501    }
502 
503    unittest
504    {
505       writeln("\t * set");
506 
507       QueryBuilder qb;
508       qb.update("foo")
509          .set("some_col", 1);
510 
511       assert(qb._params["some_col"] == Value(1));
512       assert(qb._set.length == 1);
513       assert(qb._set[0] == "\"some_col\" = {some_col}");
514 
515       qb.set([
516             "col1": Value(1),
517             "col2": Value(2)]);
518 
519       assert(qb._params.length == 3);
520       assert(qb._set.length == 3);
521       assert(qb._set[1] == "\"col1\" = {col1}");
522       assert(qb._set[2] == "\"col2\" = {col2}");
523 
524       string str = "asd = $1";
525       qb.set(str);
526       assert(qb._params.length == 3);
527       assert(qb._set.length == 4);
528       assert(qb._set[3] == str);
529    }
530 
531    // INSERT methods
532    ref QueryBuilder insert(string table, string[] cols...) return
533    {
534       _table = table;
535       _columns = cols;
536       _type = QueryType.insert;
537       return this;
538    }
539 
540 
541    ref QueryBuilder insert(string table, Column[] cols...) return
542    {
543       import std.array;
544       return insert(table, array(cols.map!(c => c.column)));
545    }
546 
547    unittest
548    {
549       writeln("\t * insert");
550 
551       QueryBuilder qb;
552       qb.insert("table", "col1", "col2");
553 
554       assert(qb._type == QueryType.insert);
555       assert(qb._table == "table");
556       assert(qb._columns == ["col1", "col2"]);
557 
558       Column[] cs = [
559          Column("some_col", "stupid_as_name"),
560          Column("qwe")];
561 
562       qb.insert("table2", cs);
563       assert(qb._table == "table2");
564       assert(qb._columns.length == 2);
565       assert(qb._columns == ["some_col", "qwe"]);
566    }
567 
568    ref QueryBuilder values(T...)(T vals)
569    {
570       assert(_type == QueryType.insert, "QueryBuilder.values() can only be used on INSERT queries");
571 
572       foreach (val; vals)
573          addValue(val);
574 
575       return this;
576    }
577 
578    ref QueryBuilder values(Value[] vals) return
579    {
580       assert(_type == QueryType.insert, "QueryBuilder.values() can only be used on INSERT queries");
581 
582       foreach (val; vals)
583          addValue(val);
584 
585       return this;
586    }
587 
588    unittest
589    {
590       writeln("\t * values");
591 
592       QueryBuilder qb;
593       qb.insert("table", "col")
594          .values(1, 2, 3);
595 
596       assert(qb._type == QueryType.insert);
597       assert(qb._indexParams.length == 3);
598       assert(qb._indexParams == [Value(1), Value(2), Value(3)]);
599 
600       qb.values([Value(4), Value(5)]);
601       assert(qb._indexParams.length == 5);
602       assert(qb._indexParams == [Value(1), Value(2), Value(3), Value(4), Value(5)]);
603    }
604 
605    ref QueryBuilder remove() return
606    {
607       _type = QueryType.delete_;
608       return this;
609    }
610 
611    ref QueryBuilder remove(string table) return
612    {
613       from(table);
614       return remove();
615    }
616 
617    ref QueryBuilder remove(T)()
618    {
619       return remove(relationName!T);
620    }
621 
622    ref QueryBuilder returning(string[] ret...) return
623    {
624       foreach (r; ret)
625          _returning ~= r;
626 
627       return this;
628    }
629 
630    unittest
631    {
632       writeln("\t * remove");
633 
634       struct Test {}
635       QueryBuilder qb;
636       qb.remove!Test;
637 
638       assert(qb._type == QueryType.delete_);
639       assert(qb._table == relationName!Test);
640    }
641 
642    ref QueryBuilder addValue(T)(T val)
643    {
644       _indexParams ~= Value(val);
645       return this;
646    }
647 
648    ref QueryBuilder addValues(T, U)(U val)
649    {
650       import std.traits;
651       import dpq.meta;
652       import dpq.serialisation;
653 
654       if (isAnyNull(val))
655          addValue(null);
656       else
657       {
658          foreach (m; serialisableMembers!(NoNullable!T))
659          {
660             static if (isPK!(T, m) || hasUDA!(mixin("T." ~ m), IgnoreAttribute))
661                continue;
662             else
663                addValue(__traits(getMember, val, m));
664          }
665       }
666 
667       return this;
668    }
669 
670    // Other stuff
671 
672    private string replaceParams(string str)
673    {
674       int index = _paramIndex;
675 
676       foreach (param, val; _params)
677          str = str.replace("{" ~ param ~ "}", "$%d".format(++index));
678 
679       return str;
680    }
681 
682    unittest
683    {
684       writeln("\t * replaceParams");
685       QueryBuilder qb;
686       string str = "SELECT {foo} FROM table WHERE id = {bar} AND name = '{baz}'";
687       qb["foo"] = "a";
688       qb["bar"] = "b";
689 
690       str = qb.replaceParams(str);
691 
692       // No idea what the order might be
693       assert(
694             str == "SELECT $1 FROM table WHERE id = $2 AND name = '{baz}'" ||
695             str == "SELECT $2 FROM table WHERE id = $1 AND name = '{baz}'");
696    }
697 
698    private string selectCommand()
699    {
700       string cols;
701       if (_columns.length == 0)
702          cols = "*";
703       else
704          cols = _columns
705             //.map!(c => escapeIdentifier(c))
706             .join(", ");
707 
708       string table = escapeIdentifier(_table);
709       string str = "SELECT %s FROM %s".format(cols, table);
710 
711       if (_filters.length > 0)
712          str ~= " WHERE " ~ _filters.to!string;
713 
714       if (_orderBy.length > 0)
715       {
716          str ~= " ORDER BY ";
717          for (int i = 0; i < _orderBy.length; ++i)
718          {
719             if (_orders[i] == Order.none)
720                continue;
721 
722             str ~= "\"" ~ _orderBy[i] ~ "\" " ~ _orders[i] ~ ", ";
723          }
724          str = str[0 .. $ - 2];
725       }
726 
727       if (!_limit.isNull)
728          str ~= " LIMIT %d".format(_limit);
729 
730       if (!_offset.isNull)
731          str ~= " OFFSET %d".format(_offset);
732 
733       return replaceParams(str);
734    }
735 
736    unittest
737    {
738       writeln("\t * selectCommand");
739 
740       QueryBuilder qb;
741       qb.select("col")
742          .from("table")
743          .where(["id": 1])
744          .limit(1)
745          .offset(1);
746 
747       string str = qb.command();
748       assert(str == `SELECT col FROM "table" WHERE ("id" = $1) LIMIT 1 OFFSET 1`, str);
749    }
750 
751    private string insertCommand()
752    {
753       int index = 0;
754 
755       string params = "(";
756       foreach (i, v; _indexParams)
757       {
758          params ~= "$%d".format(i + 1);
759          if ((i + 1) % _columns.length)
760             params ~= ", ";
761          else if ( (i + 1) < _indexParams.length)
762             params ~= "),(";
763       }
764       params ~= ")";
765 
766       string str = "INSERT INTO \"%s\" (%s) VALUES %s".format(
767             _table,
768             _columns.join(","),
769             params
770             );
771 
772       if (_returning.length > 0)
773       {
774          str ~= " RETURNING ";
775          str ~= _returning.join(", ");
776       }
777 
778       return str;
779    }
780 
781    unittest
782    {
783       writeln("\t * insertCommand");
784 
785       QueryBuilder qb;
786       qb.insert("table", "col")
787          .values(1, 2)
788          .returning("id");
789 
790       string str = qb.command();
791       assert(str == `INSERT INTO "table" (col) VALUES ($1),($2) RETURNING id`);
792    }
793 
794    private string updateCommand()
795    {
796       string str = "UPDATE \"%s\" SET %s".format(
797             _table,
798             _set.join(", "));
799 
800       if (_filters.length > 0)
801          str ~= " WHERE " ~ _filters.to!string;
802 
803       if (_returning.length > 0)
804       {
805          str ~= " RETURNING ";
806          str ~= _returning.join(", ");
807       }
808 
809       return replaceParams(str);
810    }
811 
812    unittest
813    {
814       writeln("\t * updateCommand");
815 
816       QueryBuilder qb;
817       qb.update("table")
818          .set("col", 1)
819          .where(["foo": 2])
820          .returning("id");
821 
822       string str = qb.command();
823       assert(
824             str == `UPDATE "table" SET "col" = $1 WHERE ("foo" = $2) RETURNING id` ||
825             str == `UPDATE "table" SET "col" = $2 WHERE ("foo" = $1) RETURNING id`);
826    }
827 
828    private string deleteCommand()
829    {
830       string str = "DELETE FROM \"%s\"".format(_table);
831 
832       if (_filters.length > 0)
833          str ~= " WHERE " ~ _filters.to!string;
834 
835       if (_returning.length > 0)
836       {
837          str ~= " RETURNING ";
838          str ~= _returning.join(", ");
839       }
840 
841       return replaceParams(str);
842    }
843 
844    unittest
845    {
846       writeln("\t * deleteCommand");
847 
848       QueryBuilder qb;
849       qb.remove("table")
850          .where(["id": 1])
851          .returning("id");
852 
853       string str = qb.command();
854       assert(str == `DELETE FROM "table" WHERE ("id" = $1) RETURNING id`, str);
855    }
856 
857    @property string command()
858    {
859       final switch (_type)
860       {
861          case QueryType.select:
862             return selectCommand();
863          case QueryType.update:
864             return updateCommand();
865          case QueryType.insert:
866             return insertCommand();
867          case QueryType.delete_:
868             return deleteCommand();
869       }
870    }
871 
872    @property private Value[] paramsArr()
873    {
874       Value[] res = _indexParams;
875       //foreach (param; _indexParams)
876       // res ~= param;
877 
878       foreach (param, val; _params)
879          res ~= val;
880 
881       return res;
882    }
883 
884    unittest
885    {
886       writeln("\t * paramsArr");
887 
888       QueryBuilder qb;
889       qb.addParams("1", "2", "3");
890       qb["foo"] = 1;
891       qb["bar"] = 2;
892 
893       auto ps = qb.paramsArr();
894       assert(
895             ps == [Value("1"), Value("2"), Value("3"), Value(1), Value(2)] ||
896             ps == [Value("1"), Value("2"), Value("3"), Value(2), Value(1)]);
897    }
898 
899    void addParam(T)(T val)
900    {
901       _indexParams ~= Value(val);
902       ++_paramIndex;
903    }
904 
905    unittest
906    {
907       writeln("\t * addParam");
908 
909       QueryBuilder qb;
910 
911       assert(qb._paramIndex == 0);
912 
913       qb.addParam(1);
914       assert(qb._paramIndex == 1);
915       assert(qb._indexParams.length == 1);
916       assert(qb._indexParams[0] == Value(1));
917 
918       qb.addParam(2);
919       assert(qb._paramIndex == 2);
920       assert(qb._indexParams.length == 2);
921       assert(qb._indexParams[1] == Value(2));
922    }
923 
924    ref QueryBuilder addParams(T...)(T vals)
925    {
926       foreach (val; vals)
927          addParam(val);
928 
929       return this;
930    }
931 
932    unittest
933    {
934       writeln("\t * addParams");
935 
936       QueryBuilder qb;
937       qb.addParams(1, 2, 3);
938 
939       assert(qb._indexParams.length == 3);
940       assert(qb._paramIndex == 3);
941    }
942 
943    ref QueryBuilder opBinary(string op, T)(T val)
944          if (op == "<<")
945    {
946       return addParam(val);
947    }
948 
949 
950    Query query()
951    {
952       if (_connection != null)
953          return Query(*_connection, command, paramsArr);
954 
955       return Query(command, paramsArr);
956    }
957 
958    Query query(ref Connection conn)
959    {
960       return Query(conn, command, paramsArr);
961    }
962 }