1 module sqlbuilder.types; 2 import sqlbuilder.traits; 3 4 @safe: 5 6 enum Spec : char 7 { 8 none = '\0', 9 id = 'i', 10 tableid = 't', 11 param = 'p', 12 leftJoin = 'L', 13 innerJoin = 'I', 14 rightJoin = 'R', 15 outerJoin = 'O', 16 objend = 'e', // denotes the end of an object 17 // separator between clauses. 18 separator = ';', 19 20 // clause starts and ends. Using these can allow for nested ands or ors 21 beginAnd = '&', 22 beginOr = '|', 23 endGroup = '$', 24 } 25 26 enum joinSpec = "\\" ~ Spec.leftJoin; 27 28 enum paramSpec = "\\" ~ Spec.param; 29 30 enum objEndSpec = "\\" ~ Spec.objend; 31 32 enum andSpec = "\\" ~ Spec.beginAnd; 33 34 enum orSpec = "\\" ~ Spec.beginOr; 35 36 enum sepSpec = "\\" ~ Spec.separator; 37 38 enum endGroupSpec = "\\" ~ Spec.endGroup; 39 40 Spec getSpec(const(char)[] s) 41 { 42 if(s.length >= 2 && s[0] == '\\') 43 { 44 return cast(Spec)(s[1]); 45 } 46 return Spec.none; 47 } 48 49 bool isJoin(Spec s) 50 { 51 return s == Spec.leftJoin || 52 s == Spec.rightJoin || 53 s == Spec.innerJoin || 54 s == Spec.outerJoin; 55 } 56 57 bool isGroup(Spec s) 58 { 59 return s == Spec.beginOr || s == Spec.beginAnd; 60 } 61 62 package bool isKeyLiteral(string val) 63 { 64 // returns true if any character inside val cannot be an identifier 65 if(val.length == 0) 66 return false; 67 import std.utf; 68 import std.uni; 69 auto checkme = val.byDchar; 70 if(!(checkme.front == '_' || checkme.front.isAlpha)) 71 return true; 72 checkme.popFront; 73 foreach(c; checkme) 74 { 75 if(!(c == '_' || c.isAlphaNum)) 76 return true; 77 } 78 return false; 79 } 80 81 struct ExprString 82 { 83 string[] data; 84 85 this(const(string)[] input...) 86 { 87 data = input.dup; 88 } 89 90 this(string[] input) 91 { 92 data = input; 93 } 94 95 ExprString opBinary(string op: "~")(auto ref const(ExprString) other) const 96 { 97 return ExprString(data ~ other.data); 98 } 99 100 ExprString opBinary(string op: "~")(string other) const 101 { 102 if(other.length) 103 return ExprString(data ~ other); 104 return ExprString(data); 105 } 106 107 ExprString opBinaryRight(string op: "~")(string other) const 108 { 109 if(other.length) 110 return ExprString(other ~ data); 111 return ExprString(data); 112 } 113 114 ref ExprString opOpAssign(string op: "~")(auto ref const(ExprString) other) 115 { 116 if(other.data.length) 117 data ~= other.data; 118 return this; 119 } 120 121 ref ExprString opOpAssign(string op: "~")(string other) 122 { 123 // only add non-empty strings 124 if(other.length) 125 data ~= other; 126 return this; 127 } 128 129 private import std.range; 130 ref ExprString opOpAssign(string op: "~", R)(R other) if (isInputRange!R && is(ElementType!R == string)) 131 { 132 // append only non-empty strings 133 foreach(s; other) 134 if(s.length > 0) 135 data ~= s; 136 return this; 137 } 138 139 bool opCast(T: bool)() 140 { 141 return data.length > 0; 142 } 143 144 void toString(Out)(Out outputRange) 145 { 146 import std.format : formattedWrite; 147 put(outputRange, "sql{"); 148 foreach(d; data) 149 { 150 auto spec = getSpec(d); 151 if(spec != Spec.none) 152 { 153 put(outputRange, "@"); 154 formattedWrite(outputRange, "%s", spec); 155 if(d.length > 2) 156 { 157 put(outputRange, "("); 158 put(outputRange, d[2 .. $]); 159 put(outputRange, ")"); 160 } 161 } 162 else 163 put(outputRange, d); 164 } 165 put(outputRange, "}"); 166 } 167 } 168 169 void addSep(ref ExprString expr) 170 { 171 if(expr) 172 { 173 auto lastSpec = expr.data[$-1].getSpec; 174 175 if(lastSpec != Spec.beginAnd && lastSpec != Spec.beginOr) 176 expr ~= sepSpec; 177 } 178 } 179 180 string makeSpec(string value, Spec spec) 181 { 182 return "\\" ~ spec ~ value; 183 } 184 185 string makeSpec(Spec spec) 186 { 187 return "\\" ~ spec; 188 } 189 190 struct TableDef 191 { 192 @property Spec joinType() const 193 { 194 if(joinExpr.data.length) 195 { 196 auto s = getSpec(joinExpr.data[0]); 197 return s.isJoin ? s : Spec.none; 198 } 199 return Spec.none; 200 } 201 202 string as; // table name used in the expression 203 ExprString joinExpr; // join expression defines the relationship and the table name 204 const(TableDef)[] dependencies; // tables that must be included first 205 } 206 207 // used to designate a field as a relation (for when a relation doesn't have a 208 // dedicated field). 209 struct Relation 210 { 211 } 212 213 214 // An SQLFragment has an expression used to generate a portion of the SQL 215 // statement, along with a list of parameters that are used to pass custom data 216 // to the server. 217 struct SQLFragment(Item) 218 { 219 ExprString expr; 220 static if(!is(Item == void)) 221 Item[] params; 222 } 223 224 struct Joins(Item) 225 { 226 SQLFragment!Item joinFragment; 227 alias joinFragment this; 228 229 bool hasJoin(const TableDef def) 230 { 231 // linear search. It's backwards because we only search for 232 // dependencies if the leaf is not present. And leaves go on the 233 // end. 234 import std.algorithm : canFind; 235 import std.range : retro; 236 return joinFragment.expr.data.retro.canFind(def.joinExpr.data.retro); 237 } 238 } 239 240 // a query is a dynamic structure designed to contain all the things needed to 241 // generate an SQL query. The function sql will fetch the current query 242 // string based on the Dialect. 243 struct Query(Item, RowT...) 244 { 245 import std.meta : staticMap; 246 SQLFragment!(Item) fields; 247 SQLFragment!(Item) conditions; 248 SQLFragment!(Item) groups; 249 SQLFragment!(Item) orders; 250 Joins!Item joins; 251 size_t limitQty = 0; 252 size_t limitOffset = 0; 253 254 // used by the serialization system to determine which rows this will 255 // fetch. This is only valid if fetch was used to generate the query. 256 alias RowTypes = staticMap!(getFetchType, RowT); 257 alias QueryTypes = RowT; 258 259 // convenience to avoid having to use traits tricks. 260 package alias ItemType = Item; 261 262 // allow forgetting all the row types. 263 static if(RowT.length == 0 || RowT.length > 1 || (RowT.length == 1 && !is(RowT[0] == void))) 264 { 265 ref .Query!(Item, void) basicQuery() return @trusted 266 { 267 return *cast(.Query!(Item, void)*)&this; 268 } 269 270 alias basicQuery this; 271 } 272 else 273 ref Query basicQuery() return { return this; } 274 } 275 276 // UFCS method to fetch all the parameters from the given item. 277 package template paramsImpl(FieldNames...) 278 { 279 string paramStr() 280 { 281 string result; 282 foreach(n; FieldNames) 283 result ~= "t." ~ n ~ ".params,"; 284 return result; 285 } 286 auto paramsImpl(T)(T t) 287 { 288 static if(is(t.ItemType == void)) 289 { 290 import std.range : only; 291 return only(); 292 } 293 else 294 { 295 import std.range : chain; 296 mixin("return chain(" ~ paramStr() ~ ");"); 297 } 298 } 299 } 300 301 struct Insert(Item) 302 { 303 alias ItemType = Item; 304 305 // table for insertion. 306 string tableid; 307 308 // the items to set. 309 SQLFragment!Item colNames; 310 SQLFragment!Item colValues; 311 } 312 313 struct Update(Item) 314 { 315 SQLFragment!Item settings; 316 SQLFragment!Item conditions; 317 Joins!Item joins; 318 319 alias ItemType = Item; 320 } 321 322 struct Delete(Item) 323 { 324 // relations, possibly used for "where" clause, but not deleted from 325 Joins!Item joins; 326 SQLFragment!Item conditions; 327 328 alias ItemType = Item; 329 } 330 331 struct AllowNullType(T, T defaultVal) 332 { 333 alias type = T; 334 enum nullVal = defaultVal; 335 } 336 337 struct ColumnDef(T) 338 { 339 const TableDef table; 340 ExprString expr; 341 alias type = T; 342 } 343 344 struct ConcatDef 345 { 346 const(TableDef)[] tables; 347 ExprString expr; 348 alias type = string; 349 } 350 351 // a change struct 352 struct Changed(ColTypes...) 353 { 354 ColTypes val; 355 package bool _changed; 356 357 bool opCast(T : bool)() { return _changed; } 358 } 359 360 // this specialized struct is something that is used as a placeholder for a 361 // type that is actually a row. This happens when you select an entire row from 362 // the DB instead of a single column. This expects an _objEnd column to define 363 // where the row pieces stop. 364 // This never should be seen by user code. 365 package struct RowObj(T) 366 { 367 } 368 369 // basic expression for strings. Used to provide literal SQL to ExprString. 370 struct Expr 371 { 372 string expr; 373 } 374 375 // Deserialization game plan: 376 // 377 // 1. A RowObj of a Nullable!T: 378 // a. T.deserializeRowNull exists? use it 379 // b. T.deserializeRow exists? use it, catch any exception and set to null 380 // c. Process all fields in the struct, if any of the fields are null, but are not Nullable themselves, the whole object is null. 381 // 2. A RowObj of a non-Nullable T: 382 // a. T.deserializeRow exists? use it 383 // b. Process all fields in the struct. 384 // 3. Changes struct 385 // a. do the deserialization, compare with previous version. Set up struct 386 // 387 // The rest are for SINGLE COLUMN types (leaf types): 388 // 4. Nullable!T 389 // a. Is the value null? return Nullable!T.init 390 // b. Else, deserialize T 391 // 5. T that has dbValue/fromDbValue 392 // a. return T.fromDbValue; 393 // 6. other dialect specific hooked types (e.g. DateTime, values that can marshal to/from strings) 394 // a. Deserialize according to the dialect rules. 395 // 7. primitive types: 396 // a. Deserialize according to the dialect rules.