1 /// This module provides a typesafe querying framework. 2 /// For now only very basic queries are supported 3 module mongoschema.query; 4 5 import mongoschema; 6 7 import std.regex; 8 import std.traits; 9 10 /// Represents a field to compare 11 struct FieldQuery(T, Obj) 12 { 13 enum isCompatible(V) = is(V : T) || is(V == Bson); 14 15 Query!Obj* query; 16 string name; 17 18 @disable this(); 19 @disable this(this); 20 21 private this(string name, ref Query!Obj query) @trusted 22 { 23 this.name = name; 24 this.query = &query; 25 } 26 27 ref Query!Obj equals(V)(V other) if (isCompatible!V) 28 { 29 query._query[name] = memberToBson(other); 30 return *query; 31 } 32 33 alias equal = equals; 34 alias eq = equals; 35 36 ref Query!Obj ne(V)(V other) if (isCompatible!V) 37 { 38 query._query[name] = Bson(["$ne": memberToBson(other)]); 39 return *query; 40 } 41 42 alias notEqual = ne; 43 alias notEquals = ne; 44 45 ref Query!Obj gt(V)(V other) if (isCompatible!V) 46 { 47 query._query[name] = Bson(["$gt": memberToBson(other)]); 48 return *query; 49 } 50 51 alias greaterThan = gt; 52 53 ref Query!Obj gte(V)(V other) if (isCompatible!V) 54 { 55 query._query[name] = Bson(["$gte": memberToBson(other)]); 56 return *query; 57 } 58 59 alias greaterThanOrEqual = gt; 60 61 ref Query!Obj lt(V)(V other) if (isCompatible!V) 62 { 63 query._query[name] = Bson(["$lt": memberToBson(other)]); 64 return *query; 65 } 66 67 alias lessThan = lt; 68 69 ref Query!Obj lte(V)(V other) if (isCompatible!V) 70 { 71 query._query[name] = Bson(["$lte": memberToBson(other)]); 72 return *query; 73 } 74 75 alias lessThanOrEqual = lt; 76 77 ref Query!Obj oneOf(Args...)(Args other) 78 { 79 Bson[] arr = new Bson(Args.length); 80 static foreach (i, arg; other) 81 arr[i] = memberToBson(arg); 82 query._query[name] = Bson(["$in": Bson(arr)]); 83 return *query; 84 } 85 86 ref Query!Obj inArray(V)(V[] array) if (isCompatible!V) 87 { 88 query._query[name] = Bson(["$in": memberToBson(array)]); 89 return *query; 90 } 91 92 ref Query!Obj noneOf(Args...)(Args other) 93 { 94 Bson[] arr = new Bson(Args.length); 95 static foreach (i, arg; other) 96 arr[i] = memberToBson(arg); 97 query._query[name] = Bson(["$nin": Bson(arr)]); 98 return *query; 99 } 100 101 alias notOneOf = noneOf; 102 103 ref Query!Obj notInArray(V)(V[] array) if (isCompatible!V) 104 { 105 query._query[name] = Bson(["$nin": memberToBson(array)]); 106 return *query; 107 } 108 109 ref Query!Obj exists(bool exists = true) 110 { 111 query._query[name] = Bson(["$exists": Bson(exists)]); 112 return *query; 113 } 114 115 ref Query!Obj typeOf(Bson.Type type) 116 { 117 query._query[name] = Bson(["$type": Bson(cast(int) type)]); 118 return *query; 119 } 120 121 ref Query!Obj typeOfAny(Bson.Type[] types...) 122 { 123 Bson[] arr = new Bson[types.length]; 124 foreach (i, type; types) 125 arr[i] = Bson(cast(int) type); 126 query._query[name] = Bson(["$type": Bson(arr)]); 127 return *query; 128 } 129 130 ref Query!Obj typeOfAny(Bson.Type[] types) 131 { 132 query._query[name] = Bson(["$type": serializeToBson(types)]); 133 return *query; 134 } 135 136 static if (is(T : U[], U)) 137 { 138 ref Query!Obj containsAll(U[] values) 139 { 140 query._query[name] = Bson(["$all": serializeToBson(values)]); 141 return *query; 142 } 143 144 alias all = containsAll; 145 146 ref Query!Obj ofLength(size_t length) 147 { 148 query._query[name] = Bson(["$size": Bson(length)]); 149 return *query; 150 } 151 152 alias size = ofLength; 153 } 154 155 static if (isIntegral!T) 156 { 157 ref Query!Obj bitsAllClear(T other) 158 { 159 query._query[name] = Bson(["$bitsAllClear": Bson(other)]); 160 return *query; 161 } 162 163 ref Query!Obj bitsAllSet(T other) 164 { 165 query._query[name] = Bson(["$bitsAllSet": Bson(other)]); 166 return *query; 167 } 168 169 ref Query!Obj bitsAnyClear(T other) 170 { 171 query._query[name] = Bson(["$bitsAnyClear": Bson(other)]); 172 return *query; 173 } 174 175 ref Query!Obj bitsAnySet(T other) 176 { 177 query._query[name] = Bson(["$bitsAnySet": Bson(other)]); 178 return *query; 179 } 180 } 181 182 static if (isNumeric!T) 183 { 184 ref Query!Obj remainder(T divisor, T remainder) 185 { 186 query._query[name] = Bson(["$mod": Bson([Bson(divisor), Bson(remainder)])]); 187 return *query; 188 } 189 } 190 191 static if (isSomeString!T) 192 { 193 ref Query!Obj regex(string regex, string options = null) 194 { 195 if (options.length) 196 query._query[name] = Bson([ 197 "$regex": Bson(regex), 198 "$options": Bson(options) 199 ]); 200 else 201 query._query[name] = Bson(["$regex": Bson(regex)]); 202 return *query; 203 } 204 } 205 } 206 207 private string generateMember(string member, string name) 208 { 209 return `alias T_` ~ member ~ ` = typeof(__traits(getMember, T.init, "` ~ member ~ `")); 210 211 FieldQuery!(T_` ~ member 212 ~ `, T) ` ~ member ~ `() 213 { 214 return FieldQuery!(T_` ~ member ~ `, T)(` ~ '`' ~ name ~ '`' ~ `, this); 215 } 216 217 typeof(this) ` ~ member 218 ~ `(T_` ~ member ~ ` equals) 219 { 220 return ` ~ member ~ `.equals(equals); 221 }`; 222 } 223 224 private string generateMembers(T)(T obj) 225 { 226 string ret; 227 static foreach (memberName; getSerializableMembers!obj) 228 { 229 { 230 string name = memberName; 231 static if (hasUDA!(__traits(getMember, obj, memberName), schemaName)) 232 { 233 static assert(getUDAs!(__traits(getMember, obj, memberName), schemaName) 234 .length == 1, "Member '" ~ memberName ~ "' can only have one name!"); 235 name = getUDAs!(__traits(getMember, obj, memberName), schemaName)[0].name; 236 } 237 238 static if (!hasUDA!(__traits(getMember, obj, memberName), encode) 239 && !hasUDA!(__traits(getMember, obj, memberName), decode)) 240 ret ~= generateMember(memberName, name); 241 } 242 } 243 return ret; 244 } 245 246 struct Query(T) 247 { 248 Bson[string] _query; 249 250 mixin(generateMembers!T(T.init)); 251 252 static Bson toBson(Query!T query) 253 { 254 return Bson(query._query); 255 } 256 } 257 258 Query!T query(T)() 259 { 260 return Query!T.init; 261 } 262 263 Query!T and(T)(Query!T[] exprs...) 264 { 265 return Query!T(["$and": memberToBson(exprs)]); 266 } 267 268 Query!T not(T)(Query!T[] exprs) 269 { 270 return Query!T(["$not": memberToBson(exprs)]); 271 } 272 273 Query!T nor(T)(Query!T[] exprs...) 274 { 275 return Query!T(["$nor": memberToBson(exprs)]); 276 } 277 278 Query!T or(T)(Query!T[] exprs...) 279 { 280 return Query!T(["$or": memberToBson(exprs)]); 281 } 282 283 unittest 284 { 285 struct CoolData 286 { 287 int number; 288 bool boolean; 289 string[] array; 290 @schemaName("t") 291 string text; 292 } 293 294 assert(memberToBson(and(query!CoolData.number.gte(10), 295 query!CoolData.number.lte(20), query!CoolData.boolean(true) 296 .array.ofLength(10).text.regex("^yes"))).toString == Bson( 297 [ 298 "$and": Bson([ 299 Bson(["number": Bson(["$gte": Bson(10)])]), 300 Bson(["number": Bson(["$lte": Bson(20)])]), 301 Bson([ 302 "array": Bson(["$size": Bson(10)]), 303 "boolean": Bson(true), 304 "t": Bson(["$regex": Bson("^yes")]) 305 ]), 306 ]) 307 ]).toString); 308 }