1 2 3 module mars.alasql; 4 5 import std.algorithm; 6 import std.array; 7 import std.conv; 8 import std.format; 9 import std.range; 10 11 import mars.defs; 12 13 14 string insertIntoParameter(const(Table) table) 15 { 16 return "insert into %s values (%s)" 17 .format(table.name, table.columns.map!( (c) => "$" ~ c.name).join(", ")); 18 } 19 unittest { 20 auto sql = Table("bar", [Col("foo", Type.text, false), Col("baz", Type.text, false)],[],[]).insertIntoParameter; 21 assert( sql == "insert into bar values ($foo, $baz)", sql ); 22 } 23 24 string updateParameter(const(Table) table) 25 { 26 if( table.pkCols.length >0 ){ 27 return "update %s set %s where %s" 28 .format( 29 table.name, 30 table.columns.map!( (c) => c.name ~ " = $" ~ c.name).join(", "), 31 table.pkCols.map!( (c) => c.name ~ " = $key" ~ c.name).join(" AND "), 32 ); 33 } 34 else { 35 return "update %s set %s where %s" 36 .format( 37 table.name, 38 table.columns.map!( (c) => c.name ~ " = $" ~ c.name).join(", "), 39 table.columns.map!( (c) => c.name ~ " = $key" ~ c.name).join(" AND "), 40 ); 41 } 42 } 43 unittest { 44 auto sql = Table("bar", [Col("foo", Type.text, false), Col("baz", Type.text, false)],[0],[]).updateParameter; 45 assert( sql == "update bar set foo = $foo, baz = $baz where foo = $keyfoo", sql ); 46 } 47 48 string deleteFromParameter(const(Table) table) 49 { 50 if( table.pkCols.length >0 ){ 51 return "delete from %s where %s" 52 .format( 53 table.name, 54 table.pkCols.map!( (c) => c.name ~ " = $" ~ c.name).join(" AND "), 55 ); 56 } 57 else { 58 return "delete from %s where %s" 59 .format( 60 table.name, 61 table.columns.map!( (c) => c.name ~ " = $" ~ c.name).join(" AND "), 62 ); 63 } 64 } 65 unittest { 66 auto sql = Table("bar", [Col("foo", Type.text, false), Col("baz", Type.text, false)],[0],[]).deleteFromParameter; 67 assert( sql == "delete from bar where foo = $foo", sql ); 68 } 69 70 string createDatabase(const(Schema) schema) 71 { 72 return schema.tables.map!( t => createTable(schema, t) )().join("; "); 73 } 74 75 /** 76 * Passing the schema is necessary for being able to refer to foreign table columns names. 77 */ 78 string createTable(const(Schema) schema, const(Table) table) 79 { 80 81 auto primaryKey(T)(T t){ 82 ulong i = t.index; 83 string v = t.value; 84 85 string postfix = ""; 86 if( canFind(table.primaryKey, i) ) // table.primaryKey.length ==1 && table.primaryKey[0] == i ) 87 postfix = " primary key"; 88 return typeof(t)(i, v ~ postfix); 89 } 90 91 string references(T)(T t){ 92 string r = t.value; 93 ulong i = t.index; 94 95 foreach(reference; table.references){ 96 // ... alasql 0.3.6 does not support references with multiple columns, so skip them. 97 if( reference.referenceCols.length > 1) continue; 98 99 assert(reference.referenceCols.length == 1); // only one col in alasql references 100 assert(reference.referencedCols.length == 1); // idem 101 if( reference.referenceCols[0] == i ){ 102 r ~= " references " ~ reference.referencedTable ~ "("; 103 import std.algorithm : find; import std.range : front; 104 auto tbs = schema.tables.find!( t => t.name == reference.referencedTable ); 105 if( tbs.empty ) assert(false, "table "~ table.name ~ " reference table " ~ reference.referencedTable ~ " that does not exists"); 106 auto tb = tbs.front; 107 r ~= tb.columns[reference.referencedCols[0]].name ~ ")"; 108 } 109 } 110 return r; 111 } 112 113 string cols = table.columns 114 .map!( (c){ return c.asNameTypeNull; }) 115 .ctfeEnumerate 116 //.map!(primaryKey!(EnumerateResult!(ulong, string))) 117 .map!(references!(EnumerateResult!(ulong, string))) 118 .join(", "); 119 120 string primaryKeys = ""; 121 if( table.primaryKey.length > 0 ){ 122 primaryKeys = ", primary key (" ~ table.primaryKey.map!( i => table.columns[i].name ).join(", ") ~ ")"; 123 } 124 125 string sql = "create table " ~ table.name ~ " (" ~ cols ~ primaryKeys ~ ")"; 126 return sql; 127 128 } 129 unittest { 130 auto sc = Schema("testschema", [ 131 immutable Table("bar1", [ Col("foo", Type.text, false) ], [], [] ), 132 immutable Table("bar2", [Col("foo", Type.text, false), Col("poo", Type.text, false)], [1], []), 133 immutable Table("bar3", [Col("foo", Type.text, false)], [], [Reference([0], "bar2", [1])]), 134 immutable Table("bar4", [Col("foo", Type.text, false), Col("bar", Type.text, false)], [], [Reference([0,1], "bar1", [0,1])]), 135 immutable Table("bar5", [Col("foo", Type.text, false), Col("bar", Type.text, false)], [0, 1], []), 136 ]); 137 138 string sql = sc.createTable(sc.tables[0]); 139 assert(sql == "create table bar1 (foo text not null)", sql); 140 sql = sc.createTable(sc.tables[1]); 141 assert(sql == "create table bar2 (foo text not null, poo text not null, primary key (poo))", sql); 142 sql = sc.createTable(sc.tables[2]); 143 assert(sql == "create table bar3 (foo text not null references bar2(poo))", sql); 144 sql = sc.createTable(sc.tables[3]); 145 assert(sql == "create table bar4 (foo text not null, bar text not null)", sql); 146 sql = sc.createTable(sc.tables[4]); 147 assert(sql == "create table bar5 (foo text not null, bar text not null, primary key (foo, bar))", sql); 148 } 149 150 string asNameTypeNull(const(Col) col) 151 { 152 return col.name ~ " " ~ col.type.toSql ~ (col.null_? "" : " not null"); 153 } 154 unittest { 155 assert( Col("foo", Type.text, false).asNameTypeNull == "foo text not null" ); 156 } 157 158 /** 159 * See_Also: https://github.com/agershun/alasql/wiki/Data%20Types 160 */ 161 string toSql(Type t){ 162 import std.conv : to; 163 164 final switch(t) with(Type) { 165 case boolean: return "boolean"; 166 case integer: return "integer"; 167 case bigint: return "integer"; 168 case smallint: return "smallint"; 169 case text: return "text"; 170 case real_: return "real"; 171 case doublePrecision: return "double precision"; 172 // ... this is not really supported by alasql, as in javascript we are using 'Buffer', but actually the javascript code 173 // doesn't perform any check for unknown type, so ... 174 case bytea: return "bytea"; 175 176 // right now handle as a smallint, insert client side not implemented right now 177 case smallserial: return "smallint"; 178 179 case unknown: 180 case date: 181 case serial: 182 case varchar: // varchar(n), tbd as column 183 assert(false, t.to!string); // not implemented right now, catch at CT 184 } 185 } 186 unittest { 187 assert( Type.text.toSql == "text" ); 188 assert( Type.integer.toSql == "integer" ); 189 } 190 191 private 192 { 193 // BUG see https://issues.dlang.org/show_bug.cgi?id=15064 194 struct EnumerateResult(I,V) { I index; V value; } 195 struct enumerator(R) { 196 import std.range : ElementType; 197 R r; 198 ulong j = 0; 199 @property EnumerateResult!(ulong, ElementType!R) front() { return EnumerateResult!(ulong, ElementType!R)(j, r.front); } 200 auto empty() { return r.empty; } 201 void popFront() { j++; r.popFront(); } 202 } 203 auto ctfeEnumerate(R)(R r){ return enumerator!R(r); } 204 }