1 module taskdesigns.sqlite;
2 
3 T instanceof(T)(Object o) if (is(T == class)) {
4     return cast(T) o;
5 }
6 
7 string escapedString(T)(T original, string escapeCharacter = `'`) {
8     import std.conv, std.regex;
9     /+
10     if (cast(string)original) {
11         auto re = regex( r"[\\\\"~ escapeCharacter ~"]");
12         return to!string( original).replaceAll( re, "'");
13     } else
14     +/
15     return to!string( original);
16 }
17 
18 string quotedIdentifier(string value) {
19     import std.array: replace;
20     string ret = "";
21     if (auto castedValue = cast(string)value) {
22         ret = castedValue.replace( "\"", "\"\"");
23     } else {
24         throw new Exception( "Unsupported type in quotedIdentifier");
25     }
26 
27     return `"` ~ ret ~ `"`;
28 }
29 
30 //SQL Expression Interface
31 interface Expression {
32     //Expression rendered as a string
33     string render();
34 }
35 
36 /++
37  Scalar Expression
38 ++/
39 class ScalarExpression(T) : Expression {
40     private T value;
41     this (T _value) {
42         value = _value;
43     }
44 
45     /++
46         Overriden render from Expression interface
47     ++/
48     override string render() {
49         import std.conv;
50         if (is(T == int)) {
51             return to!string( value);
52         } else if (is(T == long)) {
53             return to!string( value);
54         } else if (is(T == bool)) {
55             if (value)
56                 return "1";
57             else
58                 return "0";
59         } else if (is(T == double)) {
60             return to!string( value);
61         } else if (is(T == float)) {
62             return to!string( value);
63         } else if (is(T == byte[])) {
64             // TODO Fix byte array to string
65             return "";
66             //return "X'" ~ (cast(immutable(char)*)value)[0..value.length] ~ "'";
67         } else if (is(T == string)) {
68             return `'` ~ escapedString!T( value) ~ `'`;
69         } else {
70             throw new Exception( `Type of value is unsupported`);
71         }
72     }
73 }
74 
75 class SQLiteFunction: Expression {
76     string name;
77     Expression[] arguments;
78     this(string _name, Expression _argument) {
79         name = _name;
80         arguments ~= _argument;
81     }
82     this(string _name, Expression[] _arguments) {
83         name = _name;
84         arguments = _arguments;
85     }
86     override string render() {
87         import std.algorithm, std.array, std.conv;
88         auto argumentString = "";
89         foreach (argument; arguments) {
90             argumentString ~= argument.render() ~ `,`;
91         }
92         argumentString = argumentString[0..$ - 1];
93         return `` ~ name ~ `(`~ argumentString ~`)`;
94     }
95 }
96 
97 class PatternExpression(T) : Expression {
98     import std.variant;
99     bool prefix;
100     bool suffix;
101     T value;
102     this(bool _prefix, bool _suffix, T _value) {
103         prefix = _prefix;
104         suffix = _suffix;
105         value = _value;
106     }
107     override string render(){
108         auto result = "";
109         if (prefix)
110             result ~= `%`;
111         result ~= escapedString( value);
112         if (suffix)
113             result ~= `%`;
114         return result;
115     }
116 
117 }
118 
119 class Star : Expression {
120     override string render() {
121         return "*";
122     }
123 }
124 
125 abstract class Predicate {
126     abstract string render();
127     Predicate and(Predicate other) {
128         return new ConjunctionPredicate( this, other);
129     }
130     Predicate or(Predicate other) {
131         return new DisjunctionPredicate( cast(Predicate)this, other);
132     }
133 }
134 
135 abstract class Sort {
136     abstract string render();
137 }
138 
139 class SortAscending: Sort {
140     Expression expression;
141     override string render(){
142         return `ORDER BY `~ expression.render() ~ `ASC`;
143     }
144 }
145 
146 class SortDescending: Sort {
147     Expression expression;
148     override string render(){
149         return `ORDER BY `~ expression.render() ~ `DESC`;
150     }
151 }
152 
153 class EqualityPredicate : Predicate {
154     Expression left;
155     Expression right;
156 
157     this(Expression _left, Expression _right) {
158         left = _left;
159         right = _right;
160     }
161 
162     override string render(){
163         return `(`~ left.render() ~ `=`~ right.render() ~`)`;
164     }
165 }
166 
167 class PatternPredicate : Predicate {
168     Expression left;
169     Expression right;
170 
171     this(Expression _left, Expression _right) {
172         left = _left;
173         right = _right;
174     }
175 
176     override string render()  {
177         return `( ` ~ left.render() ~ ` LIKE ` ~ quotedIdentifier(right.render()) ~ ` )`;
178     }
179 }
180 
181 class ConjunctionPredicate : Predicate {
182     Predicate p1;
183     Predicate p2;
184     this(Predicate _p1, Predicate _p2) {
185         p1 = _p1;
186         p2 = _p2;
187     }
188     override string render() {
189         return `(` ~ p1.render() ~ `) AND (` ~ p2.render() ~ `)`;
190     }
191 }
192 
193 class DisjunctionPredicate : Predicate {
194     Predicate p1;
195     Predicate p2;
196     this(Predicate _p1, Predicate _p2) {
197         p1 = _p1;
198         p2 = _p2;
199     }
200     override string render() {
201         return `(` ~ p1.render() ~ `) OR (` ~ p2.render() ~ `)`;
202     }
203 }
204 
205 class Column(T): Expression {
206     string name;
207     bool is_nullable;
208     ColumnType type;
209     this(string _name, bool _is_nullable = true) {
210         name = _name;
211         is_nullable = _is_nullable;
212         if (is(T == string)) {
213             type = ColumnType.TEXT;
214         } else if (is(T == double) || is(T == float)) {
215             type = ColumnType.REAL;
216         } else if (is(T == byte[])) {
217             type = ColumnType.BLOB;
218         } else if (is(T == int) || is(T == long) || is(T == bool)) {
219             type = ColumnType.INTEGER;
220         } else {
221             throw new Exception( `Column Type is not supported`);
222         }
223     }
224     ColumnType getType() {
225         return type;
226     }
227 
228     Predicate eq(T value) {
229         return new EqualityPredicate( this, new ScalarExpression!T( value));
230     }
231     Predicate contains(T value) {
232         return new PatternPredicate( this, new PatternExpression!T( true, true, value));
233     }
234     Predicate startsWith(T value) {
235         return new PatternPredicate( this, new PatternExpression!T( true, false, value));
236     }
237     Predicate endsWith(T value) {
238         return new PatternPredicate( this, new PatternExpression!T( true, false, value));
239     }
240     override string render() {
241         return quotedIdentifier( name);
242     }
243 }
244 
245 class Setter {
246     Expression field;
247     Expression value;
248     this(Expression _field, Expression _value) {
249         field = _field;
250         value = _value;
251     }
252 }
253 class Setters {
254     Setter[] setters;
255 
256     Setter opIndexAssign(T)(T value, Column!T field) {
257         foreach (i, setter; setters) {
258             if (setter.field.render() == field.render()) {
259                 auto replacementSetter = new Setter( field, new ScalarExpression!T( value));
260                 setters[i] = replacementSetter;
261                 return replacementSetter;
262             }
263         }
264         auto setter = new Setter( field, new ScalarExpression!T( value));
265         setters ~= setter;
266         return setter;
267     }
268 
269     void opAssign(Expression field, Expression value) {
270         setters ~= new Setter( field, value);
271     }
272 
273     void opAssign(T)(Column field, T value) {
274         setters ~= new Setter( field, new ScalarExpression!T( value));
275     }
276 }
277 
278 interface Statement {
279     string asSQL();
280 }
281 
282 class SelectStatement: Statement {
283     Expression[] expressions;
284     Expression table = null;
285     Predicate predicate = null;
286     Sort[] orders;
287 
288     this(Expression _expression) {
289         expressions ~= _expression;
290     }
291 
292     this(Expression[] _expressions) {
293         expressions = _expressions;
294     }
295 
296     SelectStatement from(Expression table) {
297         this.table = table;
298         return this;
299     }
300 
301     SelectStatement where(Predicate _predicate) {
302         predicate = _predicate;
303         return this;
304     }
305 
306     SelectStatement order_by(Sort _sort) {
307         this.orders ~= _sort;
308         return this;
309     }
310 
311     override string asSQL() {
312         auto fields = "";
313         foreach (expression; expressions)
314             fields ~= expression.render() ~ ",";
315         fields = fields[0..$-1];
316         auto where = "";
317         if (predicate)
318             where = ` WHERE ` ~ predicate.render() ~ ` `;
319         auto from = "";
320         if (table)
321             from = ` FROM `~ table.render() ~ ` `;
322         auto order = "";
323         if (orders.length > 0) {
324             foreach (expression; orders)
325                 order ~= expression.render() ~ ",";
326             order = ` ORDER BY ` ~ order;
327         }
328         return `SELECT ` ~ fields ~ from ~ where ~ order;
329     }
330 }
331 
332 class CreateStatement: Statement {
333     TableBuilder builder;
334 
335     this(TableBuilder _builder) {
336         builder = _builder;
337     }
338 
339     override string asSQL() {
340         auto columns = "";
341         foreach (column; builder.columns) {
342             columns ~= column.render() ~ `,`;
343         }
344         columns = columns[0..$ - 1];
345         return `CREATE TABLE IF NOT EXISTS ` ~ builder.table.render() ~ ` (` ~ columns ~`);`;
346     }
347 }
348 
349 class DropStatement: Statement {
350     Expression table = null;
351 
352     this(Expression _table) {
353         table = _table;
354     }
355 
356     override string asSQL() {
357         return `DROP TABLE IF EXISTS ` ~ table.render() ~ `;`;
358     }
359 }
360 
361 class InsertStatement: Statement {
362     Setter[] setters;
363     Expression table;
364 
365     this(Setter[] _setters) {
366         setters = _setters;
367     }
368     override string asSQL() {
369         import std.array: replace;
370         auto fields = "";
371         foreach (setter; setters) {
372             fields ~= setter.field.render() ~ ",";
373         }
374         if (fields.length > 0) {
375             fields = fields[0..$ - 1];
376             auto values = "";
377             foreach (setter; setters) {
378                 //Adding a check for single quotes
379                 if (setter.value.render().length > 1) {
380                     import std.string: indexOf;
381                     if (setter.value.render()[1..$ - 1].indexOf( `'`) != -1) {
382                         auto editedSetter = setter.value.render()[1..$ - 1].replace( `'`, `''`);
383                         values ~= `'` ~ editedSetter ~ "',";
384                     } else {
385                         values ~= `'` ~ setter.value.render() ~ "',";
386                     }
387                 } else {
388                     values ~= `'` ~ setter.value.render() ~ "',";
389                 }
390             }
391             values = values[0..$ - 1];
392             return `INSERT INTO ` ~ table.render() ~ `(` ~ fields ~ `)`~` VALUES (` ~ values ~ `)`;
393         } else {
394             throw new Exception( "No fields provided to INSERT INTO");
395         }
396     }
397 
398     InsertStatement into(Expression table) {
399         this.table = table;
400         return this;
401     }
402 }
403 
404 class InsertOrReplaceStatement: Statement {
405     Setter[] setters;
406     Expression table;
407 
408     this(Setter[] _setters) {
409         setters = _setters;
410     }
411     override string asSQL() {
412         import std.array: replace;
413         import std.stdio;
414         import std.string: isNumeric;
415         auto fields = "";
416         foreach (setter; setters) {
417             fields ~= setter.field.render() ~ ",";
418         }
419         if (fields.length > 0) {
420             fields = fields[0..$ - 1];
421             auto values = "";
422             foreach (setter; setters) {
423                 //Adding a check for single quotes
424                 if (setter.value.render().length > 1 && !isNumeric(setter.value.render())) {
425                     auto editedSetter = setter.value.render()[1..$ - 1].replace( `'`, `''`);
426                     //writeln( `Edited Setter: ` ~ editedSetter);
427                     values ~= `'` ~ editedSetter ~ "',";
428                 } else {
429                     auto originalValue = setter.value.render();
430                     //writeln(`Unedited Setter: ` ~ originalValue);
431                     values ~= `'` ~ originalValue ~ "',";
432                 }
433             }
434             values = values[0..$ - 1];
435             return `INSERT OR REPLACE INTO ` ~ table.render() ~ `(` ~ fields ~ `)`~` VALUES (` ~ values ~ `)`;
436         } else {
437             throw new Exception( "No fields provided to INSERT INTO");
438         }
439     }
440 
441     InsertOrReplaceStatement into(Expression table) {
442         this.table = table;
443         return this;
444     }
445 }
446 
447 class UpdateStatement: Statement {
448     Setter[] setters;
449     Expression _table;
450     Predicate predicate = null;
451 
452     this(Setter[] _setters) {
453         setters = _setters;
454     }
455 
456     override string asSQL() {
457         auto updates = "";
458         foreach (setter; setters) {
459             updates ~= setter.field.render() ~ "=" ~ setter.value.render() ~ ",";
460         }
461         updates = updates[0..$ - 1];
462         auto where = "";
463         if (predicate !is null) {
464             where = "WHERE " ~ predicate.render();
465         }
466         return `UPDATE ` ~ _table.render() ~` SET ` ~ updates ~ where;
467     }
468 
469     UpdateStatement table(Expression table) {
470         _table = table;
471         return this;
472     }
473 
474     UpdateStatement where(Predicate predicate) {
475         this.predicate = predicate;
476         return this;
477     }
478 }
479 
480 class DeleteStatement: Statement {
481     Expression table;
482     Predicate predicate = null;
483 
484     override string asSQL() {
485         auto where = "";
486         if (predicate) {
487             where = "WHERE " ~ predicate.render();
488         }
489         return `DELETE FROM ` ~ table.render() ~ ` ` ~ where;
490     }
491 
492     DeleteStatement from(Expression table) {
493         this.table = table;
494         return this;
495     }
496 
497     DeleteStatement where(Predicate predicate) {
498         this.predicate = predicate;
499         return this;
500     }
501 }
502 
503 /**
504  * Tables
505  */
506 class Table : Expression {
507     string name;
508     this(string _name) {
509         name = _name;
510     }
511     override string render() {
512         return quotedIdentifier( name);
513     }
514 
515     Statement create(void function(TableBuilder builder) lambda)  {
516         auto builder = new TableBuilder( this);
517         lambda( builder);
518         return new CreateStatement( builder);
519     }
520 
521     SelectStatement select(T)(T column) {
522         return new SelectStatement( * column).from( this);
523     }
524 
525     SelectStatement count() {
526         return new SelectStatement( new SQLiteFunction( "count", new Star())).from( this);
527     }
528 
529     SelectStatement select() {
530         return new SelectStatement( new Star()).from( this);
531     }
532 
533     InsertStatement insert(void delegate(Setters builder) lambda) {
534         auto builder = new Setters();
535         lambda( builder);
536         return new InsertStatement( builder.setters).into( this);
537     }
538 
539     InsertOrReplaceStatement insertOrReplace(void delegate(Setters builder) lambda) {
540         auto builder = new Setters();
541         lambda( builder);
542         return new InsertOrReplaceStatement( builder.setters).into( this);
543     }
544 
545     UpdateStatement update(void delegate(Setters builder) lambda) {
546         auto builder = new Setters();
547         lambda( builder);
548         return new UpdateStatement( builder.setters).table( this);
549     }
550 
551     DeleteStatement deleteStatement() {
552         return new DeleteStatement().from( this);
553     }
554 
555     DropStatement drop() {
556         return new DropStatement( this);
557     }
558 
559     Statement exists() {
560         return new SelectStatement( new SQLiteFunction( "count", new Star()))
561         .from( new Table( "sqlite_master"))
562         .where(
563         new ConjunctionPredicate(
564         new Column!string( "type").eq( "table"),
565         new Column!string( "name").eq( name))
566         );
567     }
568 }
569 
570 
571 /**
572  * Table schema maintenance
573  */
574 enum ColumnType {
575     INTEGER, TEXT, REAL, BLOB
576 }
577 
578 class TableBuilder {
579     Expression table;
580     ColumnDefinition[] columns;
581 
582     this(Expression _table) {
583         table = _table;
584     }
585 
586     ColumnDefinition column(T)(Column!T column, bool primaryKey = false, bool unique = false, bool autoincrement = false) {
587         auto c = new ColumnDefinition( column, column.type, primaryKey, unique, autoincrement, !column.is_nullable);
588         columns ~= c;
589         return c;
590     }
591 }
592 
593 class TransactionStatement: Statement {
594     Statement[] statements;
595     string transactions = "";
596 
597     TransactionStatement add(Statement statement) {
598         statements ~= statement;
599         return this;
600     }
601 
602     TransactionStatement add(string statementAsSQL) {
603         import std.stdio;
604         //writeln(`Statement added: ` ~ statementAsSQL ~ `;\n`);
605         transactions ~= statementAsSQL ~ `;\n`;
606         return this;
607     }
608 
609     override string asSQL() {
610         string transactionStart = `COMMIT; BEGIN TRANSACTION;\n`;
611         string transactionEnd = `COMMIT;`;
612         foreach(statement; statements) {
613             transactions ~= `\n` ~ statement.asSQL() ~ `;\n`;
614         }
615         return transactionStart ~ transactions ~ transactionEnd;
616     }
617 }
618 
619 class ColumnDefinition : Expression {
620     Expression name;
621     ColumnType type;
622     bool primaryKey;
623     bool unique;
624     bool autoincrement;
625     bool notNull;
626 
627     this(Expression _name, ColumnType _type, bool _primaryKey, bool _unique, bool _autoincrement, bool _notNull) {
628         name = _name;
629         type = _type;
630         primaryKey = _primaryKey;
631         unique = _unique;
632         autoincrement = _autoincrement;
633         notNull = _notNull;
634     }
635 
636     override string render() {
637         import std.conv;
638         auto pk = "";
639         if (primaryKey)
640             pk = " PRIMARY KEY";
641         auto nn = "";
642         if (notNull)
643             nn =" NOT NULL";
644         auto un = "";
645         if (unique)
646             un = " UNIQUE";
647         auto ai = "";
648         if (autoincrement)
649             ai = " AUTOINCREMENT";
650         if (type == ColumnType.INTEGER) {
651             if (pk && ai) {
652                 return `` ~ name.render() ~ ` ` ~ to!string( type) ~ pk ~ un ~ nn;
653             }
654         }
655         return `` ~ name.render() ~ ` ` ~ to!string( type) ~ pk ~ un ~ai ~ nn;
656     }
657 }