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