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 }