1 module mars.sqldb; 2 3 import std.algorithm; 4 import std.array; 5 import std.format; 6 import std.range; 7 8 import mars.defs; 9 import mars.lexer; 10 11 class Statement {} 12 class Select : Statement 13 { 14 Col[] cols; 15 immutable(Table)[] tables; 16 17 string createClientTables() const { 18 return `create table %s (%s)`.format( 19 tables[0].name, 20 cols.map!createTableColumn 21 .joiner(", ") 22 .array 23 ); 24 } 25 26 string insertClientRow() const { 27 return `insert into %s (%s) values (%s)`.format( 28 tables[0].name, 29 cols.map!name().joiner(", ").array, 30 cols.map!`"?"`().joiner(", ").array 31 ); 32 } 33 34 string updateClientWithReturning() const { 35 auto filtered = cols.filter!( (a) => a.type == Type.serial || a.type == Type.smallserial ).array; 36 return `update %s set %s = $%s where %s = $optimistic_%s`.format( 37 tables[0].name, 38 filtered[0].name, 39 filtered[0].name, 40 filtered[0].name, 41 filtered[0].name); 42 } 43 44 string insertServerRow() const { 45 // insert statement, without serial primary keys... 46 auto filtered = cols.filter!( (a) => a.type != Type.serial && a.type != Type.smallserial ).array; 47 auto sql = `insert into quattro_graphql.%s (%s) values (%s)`.format( 48 tables[0].name, 49 filtered.map!name().joiner(", ").array, 50 iota(1, filtered.length+1).map!((a)=> "$%d".format(a))().joiner(", ").array 51 ); 52 auto returning = tables[0].returningCols(); 53 if(returning){ 54 sql ~= ` returning %s`.format( 55 returning.map!name().joiner(", ").array 56 ); 57 } 58 return sql; 59 } 60 61 Col colNamed(string name) const { 62 return cols.filter!((col) => col.name == name)().front; 63 } 64 65 override string toString() const { 66 import std.format : format; 67 return "select %s from %s".format(cols, tables); 68 } 69 } 70 class Insert : Statement { 71 immutable(Table)[] tables; 72 Col[] cols; 73 string[] params; 74 Col[] returning; 75 } 76 77 void fillFrom(ref Col c, immutable Col f){ c.name = f.name; c.type = f.type; c.null_ = f.null_; } 78 string name(const Col col) pure { return col.name; } 79 string createTableColumn(const Col col) pure { 80 enum alasqlTypes = ["unknown", "date", "smallint", "smallint", "int", "text", "varchar"]; 81 return `%s %s%s`.format(col.name, alasqlTypes[col.type], (col.null_)? "": " not null") ; 82 } 83 //static assert(createTableColumn(Col("foo", Type.smallint)) == "foo smallint not null"); 84 85 struct Parser 86 { 87 immutable Schema[] schemas; 88 Token[] tokens; 89 Token t; 90 91 Statement parse(){ 92 advance(); 93 if(t.t == TType.select){ 94 Select s = new Select; 95 parseCols(s); 96 parseFrom(s); 97 foreach(ref col; s.cols){ 98 auto c = s.tables[0].columns.find!"a.name ==b"(col.name); 99 if(c.length ==0) throw new Exception("no column named "~col.name~" in table "~s.tables[0].name); 100 col.fillFrom(c[0]); 101 } 102 return s; 103 } 104 else if(t.t == TType.insert){ 105 Insert s = new Insert; 106 advance(); parseInto(s); 107 parseCols(s); 108 parseValues(s); 109 if(tokens.empty) return s; 110 parseReturning(s); 111 return s; 112 } 113 114 if(!__ctfe){ import std.stdio; writeln(t); } 115 assert(false); 116 } 117 118 void parseCols(Select s){ 119 do { 120 advance(); 121 s.cols ~= Col(t.v); 122 advance(); 123 } while([TType.comma, TType.dot].canFind(t.t)); 124 } 125 126 void parseCols(Insert s){ 127 if(t.t != TType.lparen) throw new Exception("expected left parentesys after 'into'"); 128 do { 129 advance(); 130 auto col = s.tables[0].columns.filter!((c)=>c.name==t.v)(); 131 if(col.empty) throw new Exception("column "~t.v~" does not exists."); 132 s.cols ~= col.front; 133 advance(); 134 } while(t.t == TType.comma); 135 if(t.t != TType.rparen) throw new Exception("expected right paren after column list"); 136 advance(); 137 } 138 139 void parseInto(Insert s){ 140 assert(t.t == TType.into); 141 advance(); 142 string schemaName = "public"; 143 if( peek.t == TType.dot){ 144 schemaName = t.v; 145 advance(); advance(); 146 } 147 auto sc = schemas.find!"a.name == b"(schemaName); 148 if(sc.length ==0) throw new Exception("no schema named "~schemaName); 149 auto ta = sc[0].tables.find!"a.name == b"(t.v); 150 if(ta.length ==0) throw new Exception("no table "~t.v~" in schema "~schemaName); 151 s.tables ~= ta[0]; 152 advance(); 153 } 154 155 void parseValues(Insert s){ 156 if(t.t != TType.values) throw new Exception("expected 'values' after column list"); 157 advance(); 158 if(t.t != TType.lparen) throw new Exception("expected '(' after 'values'"); 159 do { 160 advance(); 161 if(t.t == TType.dollar){ 162 advance(); 163 s.params ~= t.v; 164 } 165 else { 166 throw new Exception("Limitation: insert is supported only via parameter binding"); 167 } 168 advance(); 169 } while(t.t == TType.comma); 170 if(t.t != TType.rparen) throw new Exception("expected ')' after values list"); 171 advance(); 172 } 173 174 void parseReturning(Insert s) 175 { 176 if(t.t != TType.returning) throw new Exception("expected 'returning' after values list"); 177 advance(); 178 auto col = s.tables[0].columns.filter!((a)=>a.name == t.v)(); 179 if(col.empty) throw new Exception("table "~s.tables[0].name~" has no column "~t.v); 180 s.returning ~= col.front; 181 advance(); 182 } 183 184 void parseFrom(Select s){ 185 assert(t.t == TType.from); 186 advance(); 187 string schemaName = "public"; 188 if( peek.t == TType.dot){ 189 schemaName = t.v; 190 advance(); advance(); 191 } 192 auto sc = schemas.find!"a.name == b"(schemaName); 193 if(sc.length ==0) throw new Exception("no schema named "~schemaName~". Available schemas are:"~schemas.map!("a.name")().join(" ")); 194 auto ta = sc[0].tables.find!"a.name == b"(t.v); 195 if(ta.length ==0) throw new Exception("no table "~t.v~" in schema "~schemaName); 196 s.tables ~= ta[0]; 197 } 198 void advance(){ 199 //if(! __ctfe){ import std.stdio; writeln(tokens); } 200 t = tokens[0]; tokens = tokens[1 .. $]; } 201 Token peek() const { return tokens[0]; } 202 }