├── .gitignore ├── LICENSE ├── README.md ├── dub.json └── source └── dquery ├── attribute.d ├── attributes.d ├── d.d ├── element.d ├── helper.d ├── overload.d ├── query.d └── tests ├── regression.d └── validation.d /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | *.o 5 | *.obj 6 | __test__library__ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Mihail K 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | dquery - Chainable compile-time metaprogramming 3 | =============================================== 4 | 5 | dquery is a light helper for processing types, traits, and other information at compile time. It provides filtering, mapping, iteration, and validation functions for queried types and members. 6 | 7 | Example Code 8 | ------------ 9 | 10 | Here's a little demo class we're going to query in these examples. Assume the types for `@Id` and `@Column` are structs defined elsewhere. 11 | 12 | ```d 13 | class User 14 | { 15 | @Id 16 | @Column 17 | ulong id; 18 | 19 | @Column 20 | string username; 21 | 22 | @Column("email_address") 23 | string email; 24 | } 25 | ``` 26 | 27 | ### Queries 28 | 29 | The `query!()` template function produces a query for a type. This is the starting point for every dquery chain. 30 | 31 | ```d 32 | auto elements = query!User; 33 | ``` 34 | 35 | You can also query a type from a value via the `query()` function, shown here using UFCS. 36 | 37 | ```d 38 | User user = new User; 39 | 40 | auto elements = user.query; 41 | ``` 42 | 43 | ### Simple Filters 44 | 45 | dquery provides filters for the 5 common types of members defined in types; fields, functions, constructors, destructors, and aggregates. 46 | 47 | ```d 48 | // Filter fields in User. 49 | auto fields = query!User.fields; 50 | 51 | // Filter functions in User. 52 | auto functions = query!User.functions; 53 | 54 | // Filter constructors in User. 55 | auto constructors = query!User.constructors; 56 | 57 | // Filter destructors in User. 58 | auto destructors = query!User.destructors; 59 | 60 | // Filter inner types in User. 61 | auto aggregates = query!User.aggregates; 62 | ``` 63 | 64 | You also get a number of filters for common information about elements of a types, such as filters for names, types, return types, arity, parameter lists, etc. 65 | 66 | ``` 67 | query!Type 68 | 69 | // Filter only names, 70 | .names!("foo", "bar") 71 | 72 | // Filter types, 73 | .types!(int, string, bool) 74 | 75 | // Filter return types, 76 | .returns!(int, string, bool) 77 | 78 | // Filter accessible elements, 79 | .accessible 80 | 81 | // Filter arities, 82 | .arities!(0, 1, 2) 83 | 84 | // Filter functions that take (int, string, bool), 85 | .parameters!(int, string, bool) 86 | 87 | // Filter elements that have @A and @B and @C, 88 | .allOf!(A, B, C) 89 | 90 | // Filter elements that have @A or @B or @C, 91 | .anyOf!(A, B, C) 92 | 93 | // Filter elements that don't have @A or @B or @C, 94 | .noneOf!(A, B, C); 95 | ``` 96 | 97 | ### Simple Attribute Information 98 | 99 | Easily check for attributes on any type or element. 100 | 101 | ```d 102 | auto userQuery = query!User; 103 | 104 | // True if type User has @A and @B and @C. 105 | bool hasAll = userQuery.hasAllOf!(A, B, C); 106 | 107 | // True if type User has @A or @B or @C. 108 | bool hasAny = userQuery.hasAnyOf!(A, B, C); 109 | 110 | // True if type User has none of @A or @B or @C. 111 | bool hasNone = userQuery.hasNoneOf!(A, B, C); 112 | 113 | // Returns attributes attached to the User type. 114 | auto attributes = query!User.attributes; 115 | ``` 116 | 117 | ### Transformations 118 | 119 | dquery includes a `filter!()` for custom or advanced filtering in chains. 120 | 121 | ```d 122 | auto elements = 123 | query!User 124 | 125 | // Custom filter. 126 | .filter!(element => 127 | element.isTypeOf!string || 128 | element.hasAttribute!Id 129 | ); 130 | ``` 131 | 132 | dquery also includes a `map!()` transform function for transforming the result of a chain. 133 | 134 | ```d 135 | string[] names = 136 | query!User 137 | 138 | // Filter fields, 139 | .fields 140 | 141 | // That have @Id or @Column, 142 | .anyOf!(Id, Column) 143 | 144 | // Map to their names. 145 | .map!(field => field.name); 146 | ``` 147 | 148 | All dquery transform functions can take a function or delegate, or a template. 149 | 150 | ### Simple Validations 151 | 152 | dquery also provides simple functions to perform validations without breaking chains. 153 | 154 | ```d 155 | auto elements = 156 | query!User 157 | 158 | // Filter fields, 159 | .fields 160 | 161 | // That have @Id and @Column, 162 | .allOf!(Id, Column) 163 | 164 | // Ensure exactly 1 exists. 165 | .ensure!"length" 166 | .exactly!(1, "User needs exactly 1 @Id."); 167 | ``` 168 | 169 | ### Chaining Logic 170 | 171 | dquery focuses on chaining so that complex logic can be broken down into simple sequences like, 172 | 173 | ```d 174 | auto elements = 175 | query!User 176 | 177 | // Filter constructors, 178 | .constructors 179 | 180 | // With arity 0, 181 | .arity!(0) 182 | 183 | // Ensure at least 1 exists, 184 | .ensure!"length" 185 | .minimum!(1, "User needs a 0 argument constructor.") 186 | 187 | // Clear filters, 188 | .reset 189 | 190 | // Filter constructors, 191 | .constructors 192 | 193 | // That accept User, 194 | .parameters!(User) 195 | 196 | // Ensure none exist. 197 | .ensure!"length" 198 | .maximum!(0, "User must not define a copy constructor."); 199 | ``` 200 | 201 | ### Loops and Iteration 202 | 203 | Query results can be iterated over with a `foreach`. 204 | 205 | ```d 206 | foreach(element; elements) 207 | { 208 | static if(element.isTypeOf!string) 209 | { 210 | // Handle fields. 211 | } 212 | else 213 | { 214 | // Do something else. 215 | } 216 | } 217 | ``` 218 | 219 | You can also use the `each!()` template function to iterate over results without breaking a chain. 220 | 221 | ```d 222 | auto elements = 223 | query!User 224 | 225 | // Filter fields, 226 | .fields 227 | 228 | // That have @Id or @Column, 229 | .anyOf!(Id, Column) 230 | 231 | // Ensure at least 1 exists. 232 | .ensure!"length" 233 | .minimum!(1, "User needs at least 1 @Id or @Column.") 234 | 235 | // Do something for each, 236 | .each!( 237 | field => doSomething(field) 238 | ) 239 | 240 | // Keep going... 241 | .reset; 242 | ``` 243 | 244 | ### Joining Results 245 | 246 | Mutliple chains can be joined together to produce even more complex queries easily. 247 | 248 | ```d 249 | auto elements = 250 | query!User 251 | 252 | // Filter fields, 253 | .fields 254 | 255 | // That have @Id or @Column, 256 | .anyOf!(Id, Column) 257 | 258 | // And are a string, 259 | .types!string 260 | 261 | // Join with, 262 | .join( 263 | query!User 264 | 265 | // Filter functions, 266 | .functions 267 | 268 | // With both @Column and @Mappable, 269 | .allOf!(Column, Mappable) 270 | 271 | // And arity 0, 272 | .arity!0 273 | 274 | // That return a string. 275 | .returns!string 276 | ); 277 | ``` 278 | 279 | You can also use a query's `unique` property to remove any duplicate elements. 280 | 281 | ### Attributes 282 | 283 | dquery also provides functions for handling attributes attached to queried types and elements. 284 | 285 | ```d 286 | // Iterate over the list of elements, 287 | foreach(element; elements) 288 | { 289 | // Iterate over each attribute that is a @Column, 290 | foreach(attribute; element.attributes!Column) 291 | { 292 | // Get value of attribute, or use a fallback if it's a type. 293 | Column column = attribute.getOrElse!(Column(element.name)); 294 | 295 | // . . . 296 | } 297 | } 298 | ``` 299 | 300 | In addition to `getOrElse!(Default)`, attributes provide a `get` method (defined only for expressions), and a `getOrThrow!(Exception)` which throws an exception at runtime if the attribute is not an expression. 301 | 302 | Limitations 303 | ----------- 304 | 305 | Because of how traits are setup in D, dquery can't operate on private or protected types, fields, or functions. Inaccessible members only provide limited information. 306 | 307 | License 308 | ------- 309 | 310 | MIT 311 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dquery", 3 | "license": "MIT", 4 | "description": "A neat little wrapper for convenient type information in D.", 5 | "copyright": "Copyright © 2015, Mihail-K", 6 | "authors": [ 7 | "Mihail K." 8 | ], 9 | "configurations": [ 10 | { 11 | "name": "demo", 12 | "targetType": "executable" 13 | }, 14 | { 15 | "name": "library", 16 | "targetType": "library" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /source/dquery/attribute.d: -------------------------------------------------------------------------------- 1 | 2 | module dquery.attribute; 3 | 4 | import std.typetuple; 5 | 6 | struct DQueryAttribute(alias Attribute) 7 | { 8 | 9 | /++ 10 | + Property that returns the value of the attribute. 11 | ++/ 12 | @property 13 | alias attribute = Attribute; 14 | 15 | /++ 16 | + Proprety that returns true if the attribute is a type. 17 | ++/ 18 | @property 19 | enum isType = !is(typeof(Attribute)); 20 | 21 | /++ 22 | + Property that returns true if the attribute is an expression. 23 | ++/ 24 | @property 25 | enum isExpression = is(typeof(Attribute)); 26 | 27 | // Determine how to get the type. 28 | static if(isExpression) 29 | { 30 | /++ 31 | + Property that returns the type of the attribute. 32 | ++/ 33 | @property 34 | alias type = typeof(Attribute); 35 | 36 | /++ 37 | + Property that returns the value of the attributes. 38 | + Only defined for attributes that can produce a value. 39 | ++/ 40 | @property 41 | alias get = Attribute; 42 | 43 | /++ 44 | + Property that returns the value of the attribute if it's an expression, 45 | + else a default value if the attribute if a type. 46 | ++/ 47 | @property 48 | alias getOrElse(alias Default) = Attribute; 49 | 50 | /++ 51 | + Property that returns the value of the attribute if it's an expression, 52 | + else throws an exception (at runtime) if it's a type. 53 | ++/ 54 | @property 55 | static type getOrThrow(ExcType = Exception)(string message) 56 | { 57 | return Attribute; 58 | } 59 | } 60 | else 61 | { 62 | /++ 63 | + Property that returns the type of the attribute. 64 | ++/ 65 | @property 66 | alias type = Attribute; 67 | 68 | /++ 69 | + Property that returns the value of the attribute if it's an expression, 70 | + else a default value if the attribute if a type. 71 | ++/ 72 | @property 73 | alias getOrElse(alias Default) = Default; 74 | 75 | /++ 76 | + Property that returns the value of the attribute if it's an expression, 77 | + else throws an exception (at runtime) if it's a type. 78 | ++/ 79 | @property 80 | static type getOrThrow(ExcType = Exception)(string message) 81 | { 82 | throw new ExcType(message); 83 | } 84 | } 85 | 86 | /++ 87 | + Property that returns true if a given type matches the attribute's 88 | + type exactly. 89 | ++/ 90 | @property 91 | enum isTypeOf(Type) = is(type == Type); 92 | 93 | /++ 94 | + Property that returns true if a given type can be assigned to a 95 | + variable of the attribute's type. 96 | ++/ 97 | @property 98 | enum isTypeAssignableTo(Type) = __traits(compiles, { 99 | type t1 = void; 100 | Type t2 = t1; 101 | }); 102 | 103 | /++ 104 | + Property that returns true if a given type can be assigned from a 105 | + variable of the attribute's type. 106 | ++/ 107 | @property 108 | enum isTypeAssignableFrom(Type) = __traits(compiles, { 109 | Type t1 = void; 110 | type t2 = t1; 111 | }); 112 | 113 | @property 114 | static auto opCall() 115 | { 116 | DQueryAttribute!(Attribute) attribute = void; 117 | return attribute; 118 | } 119 | 120 | /++ 121 | + Queries the type of the attribute. 122 | ++/ 123 | @property 124 | static auto query()() 125 | { 126 | import dquery.d; 127 | return query!type; 128 | } 129 | 130 | static string toString() 131 | { 132 | return Attribute.stringof; 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /source/dquery/attributes.d: -------------------------------------------------------------------------------- 1 | 2 | module dquery.attributes; 3 | 4 | import std.typetuple; 5 | 6 | import dquery.attribute; 7 | import dquery.helper; 8 | 9 | struct DQueryAttributes(QueryType, Attributes...) 10 | { 11 | 12 | alias attributes this; 13 | 14 | /++ 15 | + Returns the type being queried. 16 | ++/ 17 | @property 18 | alias type = QueryType; 19 | 20 | /++ 21 | + Returns a tuple of attributes in the query. 22 | ++/ 23 | @property 24 | alias attributes = Attributes; 25 | 26 | /++ 27 | + Returns true if the list of attributes is empty. 28 | ++/ 29 | @property 30 | enum empty = length == 0; 31 | 32 | /++ 33 | + Returns the number of attributes in the query. 34 | ++/ 35 | @property 36 | enum length = Attributes.length; 37 | 38 | /++ 39 | + Returns a transformed list of attributes with all duplicateds removed. 40 | ++/ 41 | @property 42 | enum unique = DQueryAttributes!(QueryType, NoDuplicates!Attributes)(); 43 | 44 | /++ 45 | + Returns an uninitialized value of the query's type. 46 | ++/ 47 | @property 48 | static auto opCall() 49 | { 50 | DQueryAttributes!(QueryType, Attributes) attributes = void; 51 | return attributes; 52 | } 53 | 54 | /++ 55 | + Returns a query for the type that produced this attribute query. 56 | ++/ 57 | @property 58 | static auto parent()() 59 | { 60 | import dquery.d; 61 | return query!QueryType; 62 | } 63 | 64 | /++ 65 | + Returns the first attribute in the query. 66 | ++/ 67 | @property 68 | static auto first()() 69 | { 70 | static assert(!empty, "Attributes query is empty."); 71 | return Attributes[0]; 72 | } 73 | 74 | /++ 75 | + Returns the first attribute in the query, or a fallback if empty. 76 | ++/ 77 | @property 78 | static auto firstOr(alias Fallback)() 79 | if(!is(typeof(Fallback) == void)) 80 | { 81 | // Deprecate in a future version. 82 | return firstOrElse!Fallback; 83 | } 84 | 85 | /++ 86 | + Returns the first attribute in the query, or a fallback if empty. 87 | ++/ 88 | @property 89 | static auto firstOrElse(alias Fallback)() 90 | if(!is(typeof(Fallback) == void)) 91 | { 92 | static if(!empty) 93 | { 94 | return Attributes[0]; 95 | } 96 | else 97 | { 98 | return DQueryAttribute!Fallback(); 99 | } 100 | } 101 | 102 | /++ 103 | + Returns the first attribute in the query, or throw if empty. 104 | ++/ 105 | @property 106 | static auto firstOrThrow()(lazy Throwable t) 107 | { 108 | static if(!empty) 109 | { 110 | return Attributes[0]; 111 | } 112 | else 113 | { 114 | throw t; 115 | } 116 | } 117 | 118 | /++ 119 | + Returns true if all of the given attributes are present. 120 | ++/ 121 | @property 122 | static auto hasAllOf(TList...)() 123 | if(TList.length > 0) 124 | { 125 | auto query = DQueryAttributes!(QueryType, Attributes)(); 126 | 127 | static if(TList.length > 1) 128 | { 129 | return query.hasAnyOf!(TList[0]) && query.hasAllOf!(TList[1 .. $]); 130 | } 131 | else 132 | { 133 | return query.hasAnyOf!(TList[0]); 134 | } 135 | } 136 | 137 | /++ 138 | + Returns true if any of the given attributes are present. 139 | ++/ 140 | @property 141 | static auto hasAnyOf(TList...)() 142 | if(TList.length > 0) 143 | { 144 | auto query = DQueryAttributes!(QueryType, Attributes)(); 145 | return !query.anyOf!TList.empty; 146 | } 147 | 148 | /++ 149 | + Returns true if none of the given attributes are present. 150 | ++/ 151 | @property 152 | static auto hasNoneOf(TList...)() 153 | if(TList.length > 0) 154 | { 155 | auto query = DQueryAttributes!(QueryType, Attributes)(); 156 | return query.anyOf!TList.empty; 157 | } 158 | 159 | /++ 160 | + Returns a subset of the list of attributes which match 161 | + at least one of the given types. 162 | ++/ 163 | @property 164 | static auto anyOf(TList...)() 165 | if(TList.length > 0) 166 | { 167 | alias AnyOfFilter(Type) = Alias!( 168 | UnaryToPred!(attribute => attribute.isTypeOf!Type) 169 | ); 170 | 171 | auto query = DQueryAttributes!(QueryType, Attributes)(); 172 | 173 | static if(Attributes.length > 0) 174 | { 175 | alias Pred = templateOr!(staticMap!(AnyOfFilter, TList)); 176 | return query.filter!Pred; 177 | } 178 | else 179 | { 180 | return query; 181 | } 182 | } 183 | 184 | /++ 185 | + Property that returns a subset of the list of attributes 186 | + which match none of the given types. 187 | ++/ 188 | @property 189 | static auto noneOf(TList...)() 190 | if(TList.length > 0) 191 | { 192 | alias NoneOfFilter(Type) = Alias!( 193 | UnaryToPred!(attribute => !attribute.isTypeOf!Type) 194 | ); 195 | 196 | auto query = DQueryAttributes!(QueryType, Attributes)(); 197 | 198 | static if(Attributes.length > 0) 199 | { 200 | alias Pred = templateAnd!(staticMap!(NoneOfFilter, TList)); 201 | return query.filter!Pred; 202 | } 203 | else 204 | { 205 | return query; 206 | } 207 | } 208 | 209 | /++ 210 | + Provides query validation functions tied to length. 211 | ++/ 212 | @property 213 | template ensure(string Attr : "length") 214 | { 215 | import std.conv : text; 216 | 217 | @property 218 | static auto minimum(size_t Min, 219 | string Message = "Length cannot be less than " ~ Min.text)() 220 | { 221 | static assert(length >= Min, Message); 222 | return DQueryAttributes!(QueryType, Attributes)(); 223 | } 224 | 225 | @property 226 | static auto maximum(size_t Max, 227 | string Message = "Length cannot be greater than " ~ Max.text)() 228 | { 229 | static assert(length <= Max, Message); 230 | return DQueryAttributes!(QueryType, Attributes)(); 231 | } 232 | 233 | @property 234 | static auto between(size_t Min, size_t Max, 235 | string Message = "Length must be between " ~ Min.text ~ " and " ~ Max.text)() 236 | { 237 | static assert(length >= Min && length <= Max, Message); 238 | return DQueryAttributes!(QueryType, Attributes)(); 239 | } 240 | 241 | @property 242 | static auto exactly(size_t Length, 243 | string Message = "Length must be exactly " ~ Length.text)() 244 | { 245 | static assert(length == Length, Message); 246 | return DQueryAttributes!(QueryType, Attributes)(); 247 | } 248 | } 249 | 250 | } 251 | 252 | template filter(alias Pred) 253 | { 254 | @property 255 | auto filter(QueryType, Attributes...)(DQueryAttributes!(QueryType, Attributes) attributes) 256 | if(Attributes.length == 0) 257 | { 258 | return attributes; 259 | } 260 | 261 | @property 262 | auto filter(QueryType, Attributes...)(DQueryAttributes!(QueryType, Attributes) attributes) 263 | if(Attributes.length > 0 && __traits(compiles, { 264 | DQueryAttributes!(QueryType, Filter!(UnaryToPred!Pred, Attributes)) result = void; 265 | })) 266 | { 267 | DQueryAttributes!(QueryType, Filter!(UnaryToPred!Pred, Attributes)) result = void; 268 | return result; 269 | } 270 | 271 | @property 272 | auto filter(QueryType, Attributes...)(DQueryAttributes!(QueryType, Attributes) attributes) 273 | if(Attributes.length > 0 && __traits(compiles, { 274 | DQueryAttributes!(QueryType, Filter!(Pred, Attributes)) result = void; 275 | })) 276 | { 277 | DQueryAttributes!(QueryType, Filter!(Pred, Attributes)) result = void; 278 | return result; 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /source/dquery/d.d: -------------------------------------------------------------------------------- 1 | 2 | module dquery.d; 3 | 4 | import std.traits; 5 | import std.typetuple; 6 | 7 | public import dquery.attribute; 8 | public import dquery.attributes; 9 | public import dquery.element; 10 | public import dquery.helper; 11 | public import dquery.overload; 12 | public import dquery.query; 13 | 14 | /++ 15 | + Produces a query over the given type. 16 | + 17 | + Returns: 18 | + A query over the supplied type. 19 | ++/ 20 | auto query(QueryType, bool Safe = false)() 21 | { 22 | template MapToElement(string Name) 23 | { 24 | // Check for functions; each overload is kept as an element. 25 | static if(is(typeof(GetMember!(QueryType, Name)) == function)) 26 | { 27 | alias MapToOverload(alias Overload) = Alias!( 28 | DQueryElement!( 29 | QueryType, Name, DQueryOverload!( 30 | arity!Overload, ReturnType!Overload, ParameterTypeTuple!Overload 31 | )() 32 | )() 33 | ); 34 | 35 | alias MapToElement = staticMap!( 36 | MapToOverload, __traits(getOverloads, QueryType, Name) 37 | ); 38 | } 39 | // Normal members. 40 | else 41 | { 42 | alias MapToElement = Alias!(DQueryElement!(QueryType, Name)()); 43 | } 44 | } 45 | 46 | template SafeMap(string Name) 47 | { 48 | static if(__traits(compiles, MapToElement!Name) || Safe) 49 | { 50 | alias SafeMap = MapToElement!Name; 51 | } 52 | else 53 | { 54 | alias SafeMap = TypeTuple!(); 55 | } 56 | } 57 | 58 | enum Elements = __traits(allMembers, QueryType); 59 | return DQuery!(QueryType, staticMap!(SafeMap, Elements))(); 60 | } 61 | 62 | /++ 63 | + Produces a query over the type of the supplied parameter. 64 | + 65 | + Params: 66 | + value = A parameter to type query. 67 | + 68 | + Returns: 69 | + A query over the parameter's type. 70 | ++/ 71 | auto query(QueryType)(QueryType value) 72 | { 73 | return query!QueryType; 74 | } 75 | -------------------------------------------------------------------------------- /source/dquery/element.d: -------------------------------------------------------------------------------- 1 | 2 | module dquery.element; 3 | 4 | import std.traits; 5 | import std.typetuple; 6 | 7 | import dquery.attribute; 8 | import dquery.attributes; 9 | import dquery.helper; 10 | 11 | struct DQueryElement(QueryType, string Name, alias Overload = null) 12 | { 13 | 14 | /++ 15 | + Returns the type being queried. 16 | ++/ 17 | @property 18 | alias type = QueryType; 19 | 20 | /++ 21 | + Returns the name of the element. 22 | ++/ 23 | @property 24 | enum name = Name; 25 | 26 | // Check if the element is accessible. 27 | static if(__traits(compiles, GetMember!(QueryType, Name))) 28 | { 29 | /++ 30 | + Returns true if the element is accessible. 31 | ++/ 32 | @property 33 | enum isAccessible = true; 34 | 35 | /++ 36 | + Returns the value of the element. 37 | ++/ 38 | @property 39 | alias value = Alias!( 40 | GetMember!(QueryType, Name) 41 | ); 42 | } 43 | else 44 | { 45 | /++ 46 | + Returns true if the element is accessible. 47 | ++/ 48 | @property 49 | enum isAccessible = false; 50 | 51 | /++ 52 | + Returns void for inaccessible elements. 53 | ++/ 54 | @property 55 | alias value = Alias!(void); 56 | } 57 | 58 | /++ 59 | + Returns true if the name matches. 60 | ++/ 61 | @property 62 | enum isName(string Name) = name == Name; 63 | 64 | /++ 65 | + Returns true if the element has an attribute of the given type. 66 | ++/ 67 | @property 68 | enum hasAttribute(Type) = attributes.anyOf!Type.length > 0; 69 | 70 | /++ 71 | + Returns true if the element has all of the given attributes. 72 | ++/ 73 | @property 74 | enum hasAllOf(TList...) = attributes.hasAllOf!TList; 75 | 76 | /++ 77 | + Returns true if the element has any of the given attributes. 78 | ++/ 79 | @property 80 | enum hasAnyOf(TList...) = attributes.hasAnyOf!TList; 81 | 82 | /++ 83 | + Returns true if the element has none of the given attributes. 84 | ++/ 85 | @property 86 | enum hasNoneOf(TList...) = attributes.hasNoneOf!TList; 87 | 88 | /++ 89 | + Returns the element's access protection. 90 | ++/ 91 | @property 92 | enum protection = GetProtection!(QueryType, Name); 93 | 94 | /++ 95 | + Returns true if the element refers to a field. 96 | ++/ 97 | @property 98 | alias isField = Alias!( 99 | is(typeof(GetMember!(QueryType, Name))) && !isFunction 100 | ); 101 | 102 | /++ 103 | + Returns true if a given type matches the element's type exactly. 104 | ++/ 105 | @property 106 | enum isTypeOf(Type) = is(typeof(GetMember!(QueryType, Name)) == Type); 107 | 108 | /++ 109 | + Returns true if a given type can be assigned to a variable 110 | + of the element's type. 111 | ++/ 112 | @property 113 | enum isTypeAssignableTo(Type) = __traits(compiles, { 114 | typeof(GetMember!(QueryType, Name)) t1 = void; 115 | Type t2 = t1; 116 | }); 117 | 118 | /++ 119 | + Returns true if a given type can be assigned from a variable 120 | + of the element's type. 121 | ++/ 122 | @property 123 | enum isTypeAssignableFrom(Type) = __traits(compiles, { 124 | Type t1 = void; 125 | typeof(GetMember!(QueryType, Name)) t2 = t1; 126 | }); 127 | 128 | /++ 129 | + Returns true if the element refers to a function. 130 | ++/ 131 | @property 132 | enum isFunction = 133 | is(typeof(GetMember!(QueryType, Name)) == function) && 134 | !is(typeof(Overload) == typeof(null)); 135 | 136 | /++ 137 | + Returns true if the element refers to a constructor. 138 | ++/ 139 | @property 140 | enum isConstructor = isFunction && isName!"__ctor"; 141 | 142 | /++ 143 | + Returns true if the element refers to a destructor. 144 | ++/ 145 | @property 146 | enum isDestructor = isFunction && isName!"__dtor"; 147 | 148 | /++ 149 | + Return true if the element's arity matches the given number of parameters. 150 | ++/ 151 | @property 152 | enum isArity(int Count) = isFunction && __traits(compiles, { 153 | static assert(Overload.arity == Count); 154 | }); 155 | 156 | /++ 157 | + Returns true if a given type matches the element's return type exactly. 158 | ++/ 159 | @property 160 | enum isReturnTypeOf(Type) = isFunction && is(Overload.returnType == Type); 161 | 162 | /++ 163 | + Returns true if a given type can be assigned to a variable of the 164 | + element's return type. 165 | ++/ 166 | @property 167 | enum isReturnAssignableTo(Type) = isFunction && __traits(compiles, { 168 | Overload.returnType t1 = void; 169 | Type t2 = t1; 170 | }); 171 | 172 | /++ 173 | + Returns true if a given type can be assigned from a variable 174 | + of the element's return type. 175 | ++/ 176 | @property 177 | enum isReturnAssignableFrom(Type) = isFunction && __traits(compiles, { 178 | Type t1 = void; 179 | Overload.returnType t2 = t1; 180 | }); 181 | 182 | /++ 183 | + Returns true if the element's parameter types match the given type list. 184 | ++/ 185 | @property 186 | enum isParameterTypesOf(TList...) = isFunction && __traits(compiles, { 187 | static assert(Compare!(Overload.parameters).With!(TList)); 188 | }); 189 | 190 | /++ 191 | + Returns true if the element refers to an aggregate type. 192 | ++/ 193 | @property 194 | enum isAggregate = __traits(compiles, { 195 | static assert(isAggregateType!(GetMember!(QueryType, Name))); 196 | }); 197 | 198 | /++ 199 | + Returns true if a given type matches the element's aggregate type exactly. 200 | ++/ 201 | @property 202 | enum isAggregateTypeOf(Type) = isAggregate && is(GetMember!(QueryType, Name) == Type); 203 | 204 | /++ 205 | + Returns true if a given type can be assigned to a variable of the 206 | + element's aggregate type. 207 | ++/ 208 | @property 209 | enum isAggregateAssignableTo(Type) = isAggregate && __traits(compiles, { 210 | GetMember!(QueryType, Name) t1 = void; 211 | Type t2 = t1; 212 | }); 213 | 214 | /++ 215 | + Returns true if a given type can be assigned from a variable of the 216 | + element's aggregate type. 217 | ++/ 218 | @property 219 | enum isAggregateAssignableFrom(Type) = isAggregate && __traits(compiles, { 220 | Type t1 = void; 221 | GetMember!(QueryType, Name) t2 = t1; 222 | }); 223 | 224 | /++ 225 | + Returns true if the element refers to a class. 226 | ++/ 227 | @property 228 | enum isClass = isAggregate && __traits(compiles, { 229 | alias Element = Alias!(GetMember!(QueryType, Name)); 230 | static assert(is(Element == class)); 231 | }); 232 | 233 | /++ 234 | + Returns true if the element refers to a struct. 235 | ++/ 236 | @property 237 | enum isStruct = isAggregate && __traits(compiles, { 238 | alias Element = Alias!(GetMember!(QueryType, Name)); 239 | static assert(is(Element == struct)); 240 | }); 241 | 242 | /++ 243 | + Returns true if the element refers to an enum. 244 | ++/ 245 | @property 246 | enum isEnum = isAggregate && __traits(compiles, { 247 | alias Element = Alias!(GetMember!(QueryType, Name)); 248 | static assert(is(Element == enum)); 249 | 250 | }); 251 | 252 | /++ 253 | + Returns true is the element is a template of the given type. 254 | ++/ 255 | @property 256 | enum isTemplateOf(alias Template) = __traits(compiles, { 257 | alias Element = Alias!(GetMember!(QueryType, Name)); 258 | static assert(is(TemplateOf!Element == Template)); 259 | }); 260 | 261 | /++ 262 | + Returns true if the element's template arguments match. 263 | ++/ 264 | @property 265 | enum isTemplateArgsOf(TemplateArgs...) = __traits(compiles, { 266 | alias Element = Alias!(GetMember!(QueryType, Name)); 267 | static assert(Compare!(TemplateArgsOf!Element).With!TemplateArgs); 268 | }); 269 | 270 | /++ 271 | + Returns an uninitialized value of the element's type. 272 | ++/ 273 | @property 274 | static auto opCall() 275 | { 276 | DQueryElement!(QueryType, Name, Overload) element = void; 277 | return element; 278 | } 279 | 280 | /++ 281 | + Returns a query for the type the element refers to. 282 | ++/ 283 | @property 284 | static auto query()() 285 | if(isAggregate) 286 | { 287 | import dquery.d; 288 | return query!(GetMember!(QueryType, Name)); 289 | } 290 | 291 | /++ 292 | + Returns a query for the parent type of the element. 293 | ++/ 294 | @property 295 | static auto parent()() 296 | { 297 | import dquery.d; 298 | return query!QueryType; 299 | } 300 | 301 | /++ 302 | + Returns the element's attributes. 303 | ++/ 304 | @property 305 | static auto attributes()() 306 | { 307 | static if(isAccessible) 308 | { 309 | alias MapToAttribute(alias Attribute) = Alias!( 310 | DQueryAttribute!Attribute() 311 | ); 312 | 313 | return DQueryAttributes!( 314 | QueryType, 315 | staticMap!( 316 | MapToAttribute, 317 | GetAttributes!(QueryType, Name) 318 | ) 319 | )(); 320 | } 321 | else 322 | { 323 | return DQueryAttributes!( 324 | QueryType, TypeTuple!() 325 | )(); 326 | } 327 | } 328 | 329 | /++ 330 | + Returns the element's allowed attributes. 331 | ++/ 332 | @property 333 | static auto attributes(Allow...)() 334 | if(Allow.length > 0) 335 | { 336 | return attributes.allow!Allow; 337 | } 338 | 339 | /++ 340 | + Returns the name of element. 341 | ++/ 342 | static string toString() 343 | { 344 | return Name; 345 | } 346 | 347 | } 348 | -------------------------------------------------------------------------------- /source/dquery/helper.d: -------------------------------------------------------------------------------- 1 | 2 | module dquery.helper; 3 | 4 | import std.functional; 5 | import std.typetuple; 6 | 7 | alias GetMember(Type, string Name) = Alias!( 8 | __traits(getMember, Type, Name) 9 | ); 10 | 11 | alias GetProtection(Type, string Name) = Alias!( 12 | __traits(getProtection, __traits(getMember, Type, Name)) 13 | ); 14 | 15 | alias GetAttributes(alias Target) = Alias!( 16 | __traits(getAttributes, Target) 17 | ); 18 | 19 | alias GetAttributes(Type, string Name) = Alias!( 20 | __traits(getAttributes, __traits(getMember, Type, Name)) 21 | ); 22 | 23 | /++ 24 | + Returns a template predicate from a unary function. 25 | ++/ 26 | template UnaryToPred(alias Pred) 27 | { 28 | alias UnaryToPred(alias Element) = Alias!(unaryFun!Pred(Element)); 29 | } 30 | 31 | template Compare(XList...) 32 | { 33 | template With(YList...) 34 | if(XList.length > 0 && XList.length == YList.length) 35 | { 36 | enum With = ( 37 | __traits(compiles, { 38 | static assert(is(XList[0] == YList[0])); 39 | }) || 40 | __traits(compiles, { 41 | static assert(XList[0] == YList[0]); 42 | }) 43 | ) && 44 | Compare!(XList[1 .. $]).With!(YList[1 .. $]); 45 | } 46 | 47 | template With(YList...) 48 | if(XList.length == 0 && YList.length == 0) 49 | { 50 | enum With = true; 51 | } 52 | 53 | template With(YList...) 54 | if(XList.length != YList.length) 55 | { 56 | enum With = false; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /source/dquery/overload.d: -------------------------------------------------------------------------------- 1 | 2 | module dquery.overload; 3 | 4 | import std.typetuple; 5 | 6 | struct DQueryOverload(size_t Arity, ReturnType, ParamTypes...) 7 | { 8 | 9 | /++ 10 | + Returns the arity of the overload. 11 | ++/ 12 | @property 13 | alias arity = Arity; 14 | 15 | /++ 16 | + Returns the return type of the overload. 17 | ++/ 18 | @property 19 | alias returnType = ReturnType; 20 | 21 | /++ 22 | + Returns the parameter types of the overload. 23 | ++/ 24 | @property 25 | alias parameters = ParamTypes; 26 | 27 | /++ 28 | + Returns an uninitialized value of the overload's type. 29 | ++/ 30 | @property 31 | static auto opCall() 32 | { 33 | DQueryOverload!(Arity, ReturnType, ParamTypes) overload = void; 34 | return overload; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /source/dquery/query.d: -------------------------------------------------------------------------------- 1 | 2 | module dquery.query; 3 | 4 | import std.traits; 5 | import std.typetuple; 6 | 7 | import dquery.attribute; 8 | import dquery.attributes; 9 | import dquery.element; 10 | import dquery.helper; 11 | 12 | struct DQuery(QueryType, QueryElements...) 13 | { 14 | 15 | alias elements this; 16 | 17 | /++ 18 | + Returns the type being queried. 19 | ++/ 20 | @property 21 | alias type = QueryType; 22 | 23 | /++ 24 | + Returns the elements in the query. 25 | ++/ 26 | @property 27 | alias elements = QueryElements; 28 | 29 | /++ 30 | + Returns true if the query has no elements. 31 | ++/ 32 | @property 33 | enum empty = length == 0; 34 | 35 | /++ 36 | + Returns the number of elements in the query. 37 | ++/ 38 | @property 39 | enum length = elements.length; 40 | 41 | /++ 42 | + Returns the query with all duplicate elements removed. 43 | ++/ 44 | @property 45 | enum unique = DQuery!(QueryType, NoDuplicates!QueryElements)(); 46 | 47 | /++ 48 | + Return an uninitialized value of the query's type. 49 | ++/ 50 | @property 51 | static auto opCall() 52 | { 53 | DQuery!(QueryType, QueryElements) query = void; 54 | return query; 55 | } 56 | 57 | /++ 58 | + Returns the query with all filters removed. 59 | ++/ 60 | @property 61 | static auto reset()() 62 | { 63 | import dquery.d; 64 | return query!QueryType; 65 | } 66 | 67 | /++ 68 | + Returns the first element in the query. 69 | ++/ 70 | @property 71 | static auto first()() 72 | if(!empty) 73 | { 74 | return QueryElements[0]; 75 | } 76 | 77 | @property 78 | static auto firstOr(alias Fallback)() 79 | if(!is(typeof(Fallback) == void)) 80 | { 81 | static if(!empty) 82 | { 83 | return QueryElements[0]; 84 | } 85 | else 86 | { 87 | return Fallback; 88 | } 89 | } 90 | 91 | /++ 92 | + Returns the type's attributes. 93 | ++/ 94 | @property 95 | static auto attributes()() 96 | { 97 | alias MapToAttribute(alias Attribute) = Alias!( 98 | DQueryAttribute!Attribute() 99 | ); 100 | 101 | return DQueryAttributes!( 102 | QueryType, 103 | staticMap!( 104 | MapToAttribute, 105 | GetAttributes!QueryType 106 | ) 107 | )(); 108 | } 109 | 110 | /++ 111 | + Returns the type's allowed attributes. 112 | ++/ 113 | @property 114 | static auto attributes(Allow...)() 115 | if(Allow.length > 0) 116 | { 117 | return attributes.anyOf!Allow; 118 | } 119 | 120 | /++ 121 | + Returns true if the type has all of the given attributes. 122 | ++/ 123 | @property 124 | static auto hasAllOf(TList...)() 125 | if(TList.length > 0) 126 | { 127 | return attributes.hasAllOf!TList; 128 | } 129 | 130 | /++ 131 | + Returns true if the type has any of the given attributes. 132 | ++/ 133 | @property 134 | static auto hasAnyOf(TList...)() 135 | if(TList.length > 0) 136 | { 137 | return attributes.hasAnyOf!TList; 138 | } 139 | 140 | /++ 141 | + Returns true if the type has none of the given attributes. 142 | ++/ 143 | @property 144 | static auto hasNoneOf(TList...)() 145 | if(TList.length > 0) 146 | { 147 | return attributes.hasNoneOf!TList; 148 | } 149 | 150 | /++ 151 | + Filters elements that match the given name. 152 | ++/ 153 | @property 154 | static auto name(string Name)() 155 | { 156 | return names!Name; 157 | } 158 | 159 | /++ 160 | + Filters elements that match one of the given names. 161 | ++/ 162 | @property 163 | static auto names(Names...)() 164 | { 165 | template NameFilter(alias Name) 166 | { 167 | alias NameFilter(alias Element) = Alias!( 168 | Element.isName!Name 169 | ); 170 | } 171 | 172 | auto query = DQuery!(QueryType, QueryElements)(); 173 | 174 | static if(query.length > 0) 175 | { 176 | return query.filter!(templateOr!(staticMap!(NameFilter, Names))); 177 | } 178 | else 179 | { 180 | return query; 181 | } 182 | } 183 | 184 | /++ 185 | + Filters elements that are accessible. 186 | ++/ 187 | @property 188 | static auto accessible()() 189 | { 190 | auto query = DQuery!(QueryType, QueryElements)(); 191 | 192 | static if(query.length > 0) 193 | { 194 | return query.filter!(f => f.isAccessible); 195 | } 196 | else 197 | { 198 | return query; 199 | } 200 | } 201 | 202 | /++ 203 | + Filters elements that match one of the given types. 204 | ++/ 205 | @property 206 | static auto types(Types...)() 207 | { 208 | template TypeFilter(Type) 209 | { 210 | alias TypeFilter(alias Element) = Alias!( 211 | Element.isTypeOf!Type 212 | ); 213 | } 214 | 215 | auto query = DQuery!(QueryType, QueryElements)(); 216 | 217 | static if(query.length > 0) 218 | { 219 | return query.filter!(templateOr!(staticMap!(TypeFilter, Types))); 220 | } 221 | else 222 | { 223 | return query; 224 | } 225 | } 226 | 227 | /++ 228 | + Filters elements that return one of the given types. 229 | ++/ 230 | @property 231 | static auto returns(Types...)() 232 | { 233 | template ReturnFilter(Type) 234 | { 235 | alias ReturnFilter(alias Element) = Alias!( 236 | Element.isReturnTypeOf!Type 237 | ); 238 | } 239 | 240 | auto query = DQuery!(QueryType, QueryElements)(); 241 | 242 | static if(query.length > 0) 243 | { 244 | return query.filter!(templateOr!(staticMap!(ReturnFilter, Types))); 245 | } 246 | else 247 | { 248 | return qeury; 249 | } 250 | } 251 | 252 | /++ 253 | + Filters elements that match the given arity value. 254 | ++/ 255 | @property 256 | static auto arity(int Arity)() 257 | { 258 | return arities!Arity; 259 | } 260 | 261 | /++ 262 | + Filters elements that match one of the given arity values. 263 | ++/ 264 | @property 265 | static auto arities(Arities...)() 266 | { 267 | template ArityFilter(int Arity) 268 | { 269 | alias ArityFilter(alias Element) = Alias!( 270 | Element.isArity!Arity 271 | ); 272 | } 273 | 274 | auto query = DQuery!(QueryType, QueryElements)(); 275 | 276 | static if(query.length > 0) 277 | { 278 | return query.filter!(templateOr!(staticMap!(ArityFilter, Arities))); 279 | } 280 | else 281 | { 282 | return query; 283 | } 284 | } 285 | 286 | /++ 287 | + Filters elements that match the given parameter list. 288 | ++/ 289 | @property 290 | static auto parameters(Parameters...)() 291 | { 292 | auto query = DQuery!(QueryType, QueryElements)(); 293 | 294 | static if(query.length > 0) 295 | { 296 | return query.filter!(f => f.isParameterTypesOf!Parameters); 297 | } 298 | else 299 | { 300 | return query; 301 | } 302 | } 303 | 304 | /++ 305 | + Filters elements that have all of the given attributes. 306 | ++/ 307 | @property 308 | static auto allOf(Attributes...)() 309 | { 310 | template AllOfFilter(alias Attribute) 311 | { 312 | alias AllOfFilter(alias Element) = Alias!( 313 | Element.hasAttribute!Attribute 314 | ); 315 | } 316 | 317 | auto query = DQuery!(QueryType, QueryElements)(); 318 | 319 | static if(query.length > 0) 320 | { 321 | return query.filter!(templateAnd!(staticMap!(AllOfFilter, Attributes))); 322 | } 323 | else 324 | { 325 | return query; 326 | } 327 | } 328 | 329 | /++ 330 | + Filters elements that have any of the given attributes. 331 | ++/ 332 | @property 333 | static auto anyOf(Attributes...)() 334 | { 335 | template AnyOfFilter(alias Attribute) 336 | { 337 | alias AnyOfFilter(alias Element) = Alias!( 338 | Element.hasAttribute!Attribute 339 | ); 340 | } 341 | 342 | auto query = DQuery!(QueryType, QueryElements)(); 343 | 344 | static if(query.length > 0) 345 | { 346 | return query.filter!(templateOr!(staticMap!(AnyOfFilter, Attributes))); 347 | } 348 | else 349 | { 350 | return query; 351 | } 352 | } 353 | 354 | /++ 355 | + Filters elements that have none of the given attributes. 356 | ++/ 357 | @property 358 | static auto noneOf(Attributes...)() 359 | { 360 | template NoneOfFilter(alias Attribute) 361 | { 362 | alias NoneOfFilter(alias Element) = Alias!( 363 | !Element.hasAttribute!Attribute 364 | ); 365 | } 366 | 367 | auto query = DQuery!(QueryType, QueryElements)(); 368 | 369 | static if(query.length > 0) 370 | { 371 | return query.filter!(templateAnd!(staticMap!(NoneOfFilter, Attributes))); 372 | } 373 | else 374 | { 375 | return query; 376 | } 377 | } 378 | 379 | /++ 380 | + Provides validations regarding the query's length. 381 | ++/ 382 | @property 383 | template ensure(string Attr : "length") 384 | { 385 | import std.conv : text; 386 | 387 | @property 388 | static auto minimum(size_t Min, 389 | string Message = "Length cannot be less than " ~ Min.text)() 390 | { 391 | static assert(length >= Min, Message); 392 | return DQuery!(QueryType, QueryElements)(); 393 | } 394 | 395 | @property 396 | static auto maximum(size_t Max, 397 | string Message = "Length cannot be greater than " ~ Max.text)() 398 | { 399 | static assert(length <= Max, Message); 400 | return DQuery!(QueryType, QueryElements)(); 401 | } 402 | 403 | @property 404 | static auto between(size_t Min, size_t Max, 405 | string Message = "Length must be between " ~ Min.text ~ " and " ~ Max.text)() 406 | { 407 | static assert(length >= Min && length <= Max, Message); 408 | return DQuery!(QueryType, QueryElements)(); 409 | } 410 | 411 | @property 412 | static auto exactly(size_t Length, 413 | string Message = "Length must be exactly " ~ Length.text)() 414 | { 415 | static assert(length == Length, Message); 416 | return DQuery!(QueryType, QueryElements)(); 417 | } 418 | } 419 | 420 | /++ 421 | + Filters elements that are fields. 422 | ++/ 423 | @property 424 | static auto fields()() 425 | { 426 | auto query = DQuery!(QueryType, QueryElements)(); 427 | 428 | static if(query.length > 0) 429 | { 430 | return query.filter!(f => f.isField); 431 | } 432 | else 433 | { 434 | return query; 435 | } 436 | } 437 | 438 | /++ 439 | + Filters elements that are fields and match any of the given names. 440 | ++/ 441 | @property 442 | static auto fields(Names...)() 443 | if(Names.length > 0) 444 | { 445 | return fields.names!Names; 446 | } 447 | 448 | /++ 449 | + Filters elements that are functions. 450 | ++/ 451 | @property 452 | static auto functions()() 453 | { 454 | auto query = DQuery!(QueryType, QueryElements)(); 455 | 456 | static if(query.length > 0) 457 | { 458 | return query.filter!(f => f.isFunction); 459 | } 460 | else 461 | { 462 | return query; 463 | } 464 | } 465 | 466 | /++ 467 | + Filters elements that are functions and match any of the given names 468 | ++/ 469 | @property 470 | static auto functions(Names...)() 471 | if(Names.length > 0) 472 | { 473 | return functions.names!Names; 474 | } 475 | 476 | /++ 477 | + Filters elements that are constructors. 478 | ++/ 479 | @property 480 | static auto constructors()() 481 | { 482 | auto query = DQuery!(QueryType, QueryElements)(); 483 | 484 | static if(query.length > 0) 485 | { 486 | return query.filter!(f => f.isConstructor); 487 | } 488 | else 489 | { 490 | return query; 491 | } 492 | } 493 | 494 | /++ 495 | + Filters elements that are destructors. 496 | ++/ 497 | @property 498 | static auto destructors()() 499 | { 500 | auto query = DQuery!(QueryType, QueryElements)(); 501 | 502 | static if(query.length > 0) 503 | { 504 | return query.filter!(f => f.isDestructor); 505 | } 506 | else 507 | { 508 | return query; 509 | } 510 | } 511 | 512 | /++ 513 | + Filters elements that are aggregate types. 514 | ++/ 515 | @property 516 | static auto aggregates()() 517 | { 518 | auto query = DQuery!(QueryType, QueryElements)(); 519 | 520 | static if(query.length > 0) 521 | { 522 | return query.filter!(f => f.isAggregate); 523 | } 524 | else 525 | { 526 | return query; 527 | } 528 | } 529 | 530 | /++ 531 | + Filters elements that are aggregate types and match any of the given names. 532 | ++/ 533 | @property 534 | static auto aggregates(Names...)() 535 | if(Names.length > 0) 536 | { 537 | return aggregates.names!Names; 538 | } 539 | 540 | /++ 541 | + Returns a union between this query and another one. 542 | + 543 | + Params: 544 | + query = The other query being joined with this one. 545 | ++/ 546 | @property 547 | static auto join(OType, OElements...)(DQuery!(OType, OElements) query) 548 | { 549 | return DQuery!(QueryType, TypeTuple!(QueryElements, OElements))(); 550 | } 551 | 552 | } 553 | 554 | /++ 555 | + Tests if all elements in a query satisfy a predicate template or function. 556 | ++/ 557 | template all(alias Pred) 558 | { 559 | /++ 560 | + An empty query always produces true. 561 | ++/ 562 | @property 563 | bool all(QueryType, QueryElements...)(DQuery!(QueryType, QueryElements) query) 564 | if(QueryElements.length == 0) 565 | { 566 | return true; 567 | } 568 | 569 | @property 570 | bool all(QueryType, QueryElements...)(DQuery!(QueryType, QueryElements) query) 571 | if(QueryElements.length > 0 && __traits(compiles, { 572 | foreach(Element; QueryElements) 573 | { 574 | if(!Pred(Element)) 575 | { 576 | return false; 577 | } 578 | } 579 | 580 | return true; 581 | })) 582 | { 583 | foreach(Element; QueryElements) 584 | { 585 | if(!Pred(Element)) 586 | { 587 | return false; 588 | } 589 | } 590 | 591 | return true; 592 | } 593 | 594 | @property 595 | bool all(QueryType, QueryElements...)(DQuery!(QueryType, QueryElements) query) 596 | if(QueryElements.length > 0 && __traits(compiles, { 597 | foreach(Element; QueryElements) 598 | { 599 | static if(!Pred!(Element)) 600 | { 601 | return false; 602 | } 603 | } 604 | 605 | return true; 606 | })) 607 | { 608 | foreach(Element; QueryElements) 609 | { 610 | static if(!Pred!(Element)) 611 | { 612 | return false; 613 | } 614 | } 615 | 616 | return true; 617 | } 618 | } 619 | 620 | /++ 621 | + Tests if any elements in a query statisfy a template predicate or function. 622 | ++/ 623 | template any(alias Pred) 624 | { 625 | /++ 626 | + An empty query always produces true. 627 | ++/ 628 | @property 629 | bool any(QueryType, QueryElements...)(DQuery!(QueryType, QueryElements) query) 630 | if(QueryElements.length == 0) 631 | { 632 | return true; 633 | } 634 | 635 | @property 636 | bool any(QueryType, QueryElements...)(DQuery!(QueryType, QueryElements) query) 637 | if(QueryElements.length > 0 && __traits(compiles, { 638 | foreach(Element; QueryElements) 639 | { 640 | if(Pred(Element)) 641 | { 642 | return true; 643 | } 644 | } 645 | 646 | return false; 647 | })) 648 | { 649 | foreach(Element; QueryElements) 650 | { 651 | if(Pred(Element)) 652 | { 653 | return true; 654 | } 655 | } 656 | 657 | return false; 658 | } 659 | 660 | @property 661 | bool any(QueryType, QueryElements...)(DQuery!(QueryType, QueryElements) query) 662 | if(QueryElements.length > 0 && __traits(compiles, { 663 | foreach(Element; QueryElements) 664 | { 665 | if(Pred!(Element)) 666 | { 667 | return true; 668 | } 669 | } 670 | 671 | return false; 672 | })) 673 | { 674 | foreach(Element; QueryElements) 675 | { 676 | if(Pred!(Element)) 677 | { 678 | return true; 679 | } 680 | } 681 | 682 | return false; 683 | } 684 | } 685 | 686 | /++ 687 | + Iterates over elements in a query using a unary template or function. 688 | ++/ 689 | template each(alias Pred) 690 | { 691 | @property 692 | auto each(QueryType, QueryElements...)(DQuery!(QueryType, QueryElements) query) 693 | if(QueryElements.length == 0) 694 | { 695 | return query; 696 | } 697 | 698 | @property 699 | auto each(QueryType, QueryElements...)(DQuery!(QueryType, QueryElements) query) 700 | if(QueryElements.length > 0 && __traits(compiles, { 701 | foreach(element; QueryElements) 702 | { 703 | Pred(element); 704 | } 705 | })) 706 | { 707 | foreach(element; QueryElements) 708 | { 709 | Pred(element); 710 | } 711 | 712 | return query; 713 | } 714 | 715 | @property 716 | auto each(QueryType, QueryElements...)(DQuery!(QueryType, QueryElements) query) 717 | if(QueryElements.length > 0 && __traits(compiles, { 718 | foreach(Element; QueryElements) 719 | { 720 | Pred!Element; 721 | } 722 | })) 723 | { 724 | foreach(Element; QueryElements) 725 | { 726 | Pred!Element; 727 | } 728 | 729 | return query; 730 | } 731 | } 732 | 733 | /++ 734 | + Applies a map transformation to a query using a unary template or function. 735 | ++/ 736 | template map(alias Pred) 737 | { 738 | @property 739 | auto map(QueryType, QueryElements...)(DQuery!(QueryType, QueryElements) query) 740 | if(QueryElements.length == 0) 741 | { 742 | return []; 743 | } 744 | 745 | @property 746 | auto map(QueryType, QueryElements...)(DQuery!(QueryType, QueryElements) query) 747 | if(QueryElements.length > 0 && __traits(compiles, { 748 | return [staticMap!(UnaryToPred!Pred, QueryElements)]; 749 | })) 750 | { 751 | return [staticMap!(UnaryToPred!Pred, QueryElements)]; 752 | } 753 | 754 | @property 755 | auto map(QueryType, QueryElements...)(DQuery!(QueryType, QueryElements) query) 756 | if(QueryElements.length > 0 && __traits(compiles, { 757 | return [staticMap!(Pred, QueryElements)]; 758 | })) 759 | { 760 | return [staticMap!(Pred, QueryElements)]; 761 | } 762 | } 763 | 764 | /++ 765 | + Applies a filter transformation to a query using a unary template or function. 766 | ++/ 767 | template filter(alias Pred) 768 | { 769 | @property 770 | auto filter(QueryType, QueryElements...)(DQuery!(QueryType, QueryElements) query) 771 | if(QueryElements.length == 0) 772 | { 773 | return query; 774 | } 775 | 776 | @property 777 | auto filter(QueryType, QueryElements...)(DQuery!(QueryType, QueryElements) query) 778 | if(QueryElements.length > 0 && __traits(compiles, { 779 | return DQuery!(QueryType, Filter!(UnaryToPred!Pred, QueryElements))(); 780 | })) 781 | { 782 | return DQuery!(QueryType, Filter!(UnaryToPred!Pred, QueryElements))(); 783 | } 784 | 785 | @property 786 | auto filter(QueryType, QueryElements...)(DQuery!(QueryType, QueryElements) query) 787 | if(QueryElements.length > 0 && __traits(compiles, { 788 | return DQuery!(QueryType, Filter!(Pred, QueryElements))(); 789 | })) 790 | { 791 | return DQuery!(QueryType, Filter!(Pred, QueryElements))(); 792 | } 793 | } 794 | -------------------------------------------------------------------------------- /source/dquery/tests/regression.d: -------------------------------------------------------------------------------- 1 | 2 | module dquery.tests.regression; 3 | 4 | import dquery.d; 5 | 6 | version(unittest) 7 | { 8 | struct Coord 9 | { 10 | 11 | private 12 | { 13 | struct Attr 14 | { 15 | } 16 | 17 | struct Limit 18 | { 19 | int value; 20 | } 21 | 22 | struct None 23 | { 24 | } 25 | } 26 | 27 | @Attr 28 | @Limit(10) 29 | int x; 30 | 31 | @Attr 32 | @Limit(10) 33 | int y; 34 | 35 | @Attr 36 | @Limit(10) 37 | int z; 38 | 39 | int tmp; 40 | 41 | @disable this(); 42 | 43 | this(int x, int y, int z) 44 | { 45 | this.x = x; 46 | this.y = y; 47 | this.z = z; 48 | } 49 | 50 | void reset() 51 | { 52 | x = y = z = 0; 53 | } 54 | 55 | int[3] getCoords() 56 | { 57 | return [x, y, z]; 58 | } 59 | } 60 | } 61 | 62 | unittest 63 | { 64 | alias Attr = Coord.Attr; 65 | alias Limit = Coord.Limit; 66 | alias None = Coord.None; 67 | 68 | Coord coord = Coord(1, 2, 3); 69 | auto query = coord.query; 70 | 71 | // Should not be empty. 72 | static assert(!query.empty); 73 | 74 | // Should have 11 elements. 75 | static assert(query.length == 11); 76 | 77 | auto fields = query.fields; 78 | 79 | // Should match parent type. 80 | static assert(is(fields.type == query.type)); 81 | 82 | // Should not be empty. 83 | static assert(!fields.empty); 84 | 85 | // Should have 4 elements. 86 | static assert(fields.length == 4); 87 | 88 | foreach(element; fields) 89 | { 90 | // Should match parent type. 91 | static assert(is(element.type == fields.type)); 92 | 93 | static if(!element.isName!"tmp") 94 | { 95 | // Should have attribute Attr. 96 | static assert(element.hasAttribute!Attr); 97 | 98 | // Should have attribute Limit. 99 | static assert(element.hasAttribute!Limit); 100 | 101 | // Should not have attribute None. 102 | static assert(!element.hasAttribute!None); 103 | 104 | // Should not have nonexistent elements. 105 | static assert(element.attributes.anyOf!None.empty); 106 | 107 | // Should not have noneOfden elements. 108 | static assert(element.attributes.noneOf!(Attr, Limit).empty); 109 | 110 | foreach(attribute; element.attributes.anyOf!Attr) 111 | { 112 | // Should be a type attribute. 113 | static assert(attribute.isType); 114 | 115 | // Should not be an expression attribute. 116 | static assert(!attribute.isExpression); 117 | 118 | // Should be type Attr. 119 | static assert(attribute.isTypeOf!Attr); 120 | 121 | // Should not be type None. 122 | static assert(!attribute.isTypeOf!None); 123 | 124 | // Should be assignable to Attr. 125 | static assert(attribute.isTypeAssignableTo!Attr); 126 | 127 | // Should not be assignable to None. 128 | static assert(!attribute.isTypeAssignableTo!None); 129 | 130 | // Should be assignable from Attr. 131 | static assert(attribute.isTypeAssignableFrom!Attr); 132 | 133 | // Should not be assignable from None. 134 | static assert(!attribute.isTypeAssignableFrom!None); 135 | } 136 | 137 | foreach(attribute; element.attributes.anyOf!Limit) 138 | { 139 | // Should be a type attribute. 140 | static assert(!attribute.isType); 141 | 142 | // Should not be an expression attribute. 143 | static assert(attribute.isExpression); 144 | 145 | // Should be type Limit. 146 | static assert(attribute.isTypeOf!Limit); 147 | 148 | // Should not be type None. 149 | static assert(!attribute.isTypeOf!None); 150 | 151 | // Should be assignable to Limit. 152 | static assert(attribute.isTypeAssignableTo!Limit); 153 | 154 | // Should not be assignable to None. 155 | static assert(!attribute.isTypeAssignableTo!None); 156 | 157 | // Should be assignable from Limit. 158 | static assert(attribute.isTypeAssignableFrom!Limit); 159 | 160 | // Should not be assignable from None. 161 | static assert(!attribute.isTypeAssignableFrom!None); 162 | } 163 | } 164 | else 165 | { 166 | // Should not have attribute Attr. 167 | static assert(!element.hasAttribute!Attr); 168 | 169 | // Should not have attribute Limit. 170 | static assert(!element.hasAttribute!Limit); 171 | 172 | // Should not have attribute None. 173 | static assert(!element.hasAttribute!None); 174 | 175 | // Should not have nonexistent elements. 176 | static assert(element.attributes.anyOf!None.empty); 177 | 178 | // Should not have anyOfed but nonexistent elements. 179 | static assert(element.attributes.anyOf!(Attr, Limit).empty); 180 | 181 | // Should not have noneOfden elements. 182 | static assert(element.attributes.noneOf!(Attr, Limit).empty); 183 | } 184 | 185 | // Should have public protection. 186 | static assert(element.protection == "public"); 187 | 188 | // Should be a field. 189 | static assert(element.isField); 190 | 191 | // Should be an int. 192 | static assert(element.isTypeOf!int); 193 | 194 | // Should be not a ulong. 195 | static assert(!element.isTypeOf!ulong); 196 | 197 | // Should be assignable to int. 198 | static assert(element.isTypeAssignableTo!int); 199 | 200 | // Should be assignable to ulong. 201 | static assert(element.isTypeAssignableTo!ulong); 202 | 203 | // Should be assignable from int. 204 | static assert(element.isTypeAssignableFrom!int); 205 | 206 | // Should not be assignable from ulong. 207 | static assert(!element.isTypeAssignableFrom!ulong); 208 | 209 | // Should not be a function. 210 | static assert(!element.isFunction); 211 | 212 | // Should not have arity 0. 213 | static assert(!element.isArity!0); 214 | 215 | // Should not return an int. 216 | static assert(!element.isReturnTypeOf!int); 217 | 218 | // Should not be an aggregate. 219 | static assert(!element.isAggregate); 220 | 221 | // Should not be a class. 222 | static assert(!element.isClass); 223 | 224 | // Should not be a struct. 225 | static assert(!element.isStruct); 226 | } 227 | 228 | auto namedFields = fields.names!("x", "z"); 229 | 230 | // Should match parent type. 231 | static assert(is(namedFields.type == fields.type)); 232 | 233 | // Should not be empty. 234 | static assert(!namedFields.empty); 235 | 236 | // Should have 2 elements. 237 | static assert(namedFields.length == 2); 238 | 239 | foreach(element; namedFields) 240 | { 241 | // Should match parent type. 242 | static assert(is(element.type == namedFields.type)); 243 | 244 | // Should not be field y. 245 | static assert(!element.isName!"y"); 246 | 247 | // Should be one of fields x or z. 248 | static assert( 249 | element.isName!"x" || 250 | element.isName!"z" 251 | ); 252 | } 253 | 254 | auto anyOfedFields = fields.anyOf!Attr; 255 | 256 | // Should match parent type. 257 | static assert(is(anyOfedFields.type == fields.type)); 258 | 259 | // Should not be empty. 260 | static assert(!anyOfedFields.empty); 261 | 262 | // Should have 3 elements. 263 | static assert(anyOfedFields.length == 3); 264 | 265 | foreach(element; anyOfedFields) 266 | { 267 | // Should match parent type. 268 | static assert(is(element.type == anyOfedFields.type)); 269 | 270 | // Should not be field tmp. 271 | static assert(!element.isName!"tmp"); 272 | 273 | // Should have attribute Attr. 274 | static assert(element.hasAttribute!Attr); 275 | } 276 | 277 | auto noneOfdenFields = fields.noneOf!Limit; 278 | 279 | // Should match parent type. 280 | static assert(is(noneOfdenFields.type == fields.type)); 281 | 282 | // Should not be empty. 283 | static assert(!noneOfdenFields.empty); 284 | 285 | // Should have 1 element. 286 | static assert(noneOfdenFields.length == 1); 287 | 288 | foreach(element; noneOfdenFields) 289 | { 290 | // Should match parent type. 291 | static assert(is(element.type == noneOfdenFields.type)); 292 | 293 | // Should be field tmp. 294 | static assert(element.isName!"tmp"); 295 | 296 | // Should have attribute Other. 297 | static assert(!element.hasAttribute!Limit); 298 | 299 | // Should have no attributes. 300 | static assert(element.attributes.empty); 301 | } 302 | 303 | auto functions = query.functions; 304 | 305 | // Should not be empty. 306 | static assert(!functions.empty); 307 | 308 | // Should have 4 elements. 309 | static assert(functions.length == 4); 310 | 311 | foreach(element; functions) 312 | { 313 | // Should match parent type. 314 | static assert(is(element.type == fields.type)); 315 | 316 | // Should not have attribute Attr. 317 | static assert(!element.hasAttribute!Attr); 318 | 319 | // Should not have attribute Limit. 320 | static assert(!element.hasAttribute!Limit); 321 | 322 | // Should not have attribute None. 323 | static assert(!element.hasAttribute!None); 324 | 325 | // Should not have nonexistent elements. 326 | static assert(element.attributes.anyOf!None.empty); 327 | 328 | // Should not have noneOfden elements. 329 | static assert(element.attributes.noneOf!(Attr, Limit).empty); 330 | 331 | // Should have public protection. 332 | static assert(element.protection == "public"); 333 | 334 | // Should not be a field. 335 | static assert(!element.isField); 336 | 337 | // Should be an int. 338 | static assert(!element.isTypeOf!int); 339 | 340 | // Should be not a ulong. 341 | static assert(!element.isTypeOf!ulong); 342 | 343 | // Should be assignable to int. 344 | static assert(!element.isTypeAssignableTo!int); 345 | 346 | // Should be assignable to ulong. 347 | static assert(!element.isTypeAssignableTo!ulong); 348 | 349 | // Should be assignable from int. 350 | static assert(!element.isTypeAssignableFrom!int); 351 | 352 | // Should not be assignable from ulong. 353 | static assert(!element.isTypeAssignableFrom!ulong); 354 | 355 | // Should be a function. 356 | static assert(element.isFunction); 357 | 358 | static if(element.isName!"__ctor") 359 | { 360 | static if(element.isArity!0) 361 | { 362 | // Should have parameters (). 363 | static assert(element.isParameterTypesOf!()); 364 | 365 | // Should not have parameters (int). 366 | static assert(!element.isParameterTypesOf!(int)); 367 | 368 | // Should not have parameters (int, int, int). 369 | static assert(!element.isParameterTypesOf!(int, int, int)); 370 | 371 | // Should not return an int. 372 | static assert(!element.isReturnTypeOf!int); 373 | 374 | // Should return a Coord. 375 | static assert(element.isReturnTypeOf!Coord); 376 | } 377 | else static if(element.isArity!3) 378 | { 379 | // Should not have parameters (). 380 | static assert(!element.isParameterTypesOf!()); 381 | 382 | // Should not have parameters (int). 383 | static assert(!element.isParameterTypesOf!(int)); 384 | 385 | // Should have parameters (int, int, int). 386 | static assert(element.isParameterTypesOf!(int, int, int)); 387 | 388 | // Should not return an int. 389 | static assert(!element.isReturnTypeOf!int); 390 | 391 | // Should return a Coord. 392 | static assert(element.isReturnTypeOf!Coord); 393 | } 394 | else 395 | { 396 | static assert(0); 397 | } 398 | } 399 | else static if(element.isName!"reset") 400 | { 401 | // Should have arity 0. 402 | static assert(element.isArity!0); 403 | 404 | // Should not have arity 3. 405 | static assert(!element.isArity!3); 406 | 407 | // Should have parameters (). 408 | static assert(element.isParameterTypesOf!()); 409 | 410 | // Should not have parameters (int). 411 | static assert(!element.isParameterTypesOf!(int)); 412 | 413 | // Should not have parameters (int, int, int). 414 | static assert(!element.isParameterTypesOf!(int, int, int)); 415 | 416 | // Should not return an int. 417 | static assert(!element.isReturnTypeOf!int); 418 | 419 | // Should not return an int array. 420 | static assert(!element.isReturnTypeOf!(int[3])); 421 | 422 | // Should return void. 423 | static assert(element.isReturnTypeOf!void); 424 | } 425 | else static if(element.isName!"getCoords") 426 | { 427 | // Should have arity 0. 428 | static assert(element.isArity!0); 429 | 430 | // Should not have arity 3. 431 | static assert(!element.isArity!3); 432 | 433 | // Should have parameters (). 434 | static assert(element.isParameterTypesOf!()); 435 | 436 | // Should not have parameters (int). 437 | static assert(!element.isParameterTypesOf!(int)); 438 | 439 | // Should not have parameters (int, int, int). 440 | static assert(!element.isParameterTypesOf!(int, int, int)); 441 | 442 | // Should not return an int. 443 | static assert(!element.isReturnTypeOf!int); 444 | 445 | // Should return an int array. 446 | static assert(element.isReturnTypeOf!(int[3])); 447 | } 448 | else 449 | { 450 | // Should not have any other elements. 451 | static assert(0, "Unexpected element " ~ element.name); 452 | } 453 | 454 | // Should not be an aggregate. 455 | static assert(!element.isAggregate); 456 | 457 | // Should not be a class. 458 | static assert(!element.isClass); 459 | 460 | // Should not be a struct. 461 | static assert(!element.isStruct); 462 | } 463 | 464 | auto aggregates = query.aggregates; 465 | 466 | // Should not be empty. 467 | static assert(!aggregates.empty); 468 | 469 | // Should have 3 elements. 470 | static assert(aggregates.length == 3); 471 | 472 | foreach(element; aggregates) 473 | { 474 | // Should match parent type. 475 | static assert(is(element.type == aggregates.type)); 476 | 477 | // Should not have attribute Attr. 478 | static assert(!element.hasAttribute!Attr); 479 | 480 | // Should not have attribute Limit. 481 | static assert(!element.hasAttribute!Limit); 482 | 483 | // Should not have attribute None. 484 | static assert(!element.hasAttribute!None); 485 | 486 | // Should not have nonexistent elements. 487 | static assert(element.attributes.anyOf!None.empty); 488 | 489 | // Should not have noneOfden elements. 490 | static assert(element.attributes.noneOf!(Attr, Limit).empty); 491 | 492 | // Should have private protection. 493 | static assert(element.protection == "private"); 494 | 495 | // Should not be a field. 496 | static assert(!element.isField); 497 | 498 | // Should be an int. 499 | static assert(!element.isTypeOf!int); 500 | 501 | // Should be not a ulong. 502 | static assert(!element.isTypeOf!ulong); 503 | 504 | // Should be assignable to int. 505 | static assert(!element.isTypeAssignableTo!int); 506 | 507 | // Should be assignable to ulong. 508 | static assert(!element.isTypeAssignableTo!ulong); 509 | 510 | // Should be assignable from int. 511 | static assert(!element.isTypeAssignableFrom!int); 512 | 513 | // Should not be assignable from ulong. 514 | static assert(!element.isTypeAssignableFrom!ulong); 515 | 516 | // Should not be a function. 517 | static assert(!element.isFunction); 518 | 519 | // Should not have arity 0. 520 | static assert(!element.isArity!0); 521 | 522 | // Should not return an int. 523 | static assert(!element.isReturnTypeOf!int); 524 | 525 | // Should not return void. 526 | static assert(!element.isReturnTypeOf!void); 527 | 528 | // Should be an aggregate. 529 | static assert(element.isAggregate); 530 | 531 | // Should not be a class. 532 | static assert(!element.isClass); 533 | 534 | // Should be a struct. 535 | static assert(element.isStruct); 536 | 537 | auto subQuery = element.query; 538 | 539 | // Should not match parent type. 540 | static assert(!is(subQuery.type == element.type)); 541 | 542 | static if(is(subQuery.type == Limit)) 543 | { 544 | // Should not be empty. 545 | static assert(!subQuery.empty); 546 | 547 | // Should have 1 element. 548 | static assert(subQuery.length == 1); 549 | } 550 | else 551 | { 552 | // Should be empty. 553 | static assert(subQuery.empty); 554 | } 555 | } 556 | } 557 | -------------------------------------------------------------------------------- /source/dquery/tests/validation.d: -------------------------------------------------------------------------------- 1 | 2 | module dquery.tests.validation; 3 | 4 | import dquery.d; 5 | 6 | version(unittest) 7 | { 8 | struct Entity 9 | { 10 | } 11 | 12 | struct Id 13 | { 14 | } 15 | 16 | struct Column 17 | { 18 | string value; 19 | } 20 | 21 | @Entity 22 | class User 23 | { 24 | 25 | @Id 26 | @Column 27 | ulong id; 28 | 29 | @Column("user_name") 30 | string username; 31 | 32 | @Column 33 | string email; 34 | 35 | this() 36 | { 37 | } 38 | 39 | this(User user) 40 | { 41 | 42 | } 43 | 44 | this(string username, string email) 45 | { 46 | this.username = username; 47 | this.email = email; 48 | } 49 | 50 | this(ulong id, string username, string email) 51 | { 52 | this.id = id; 53 | this.username = username; 54 | this.email = email; 55 | } 56 | 57 | string getCleanUsername() 58 | { 59 | import std.string : toUpper; 60 | return username.toUpper; 61 | } 62 | 63 | } 64 | } 65 | 66 | unittest 67 | { 68 | auto query = query!User 69 | 70 | .attributes 71 | .anyOf!Entity 72 | .ensure!"length" 73 | .exactly!(1) 74 | .parent 75 | 76 | .fields 77 | .anyOf!Id 78 | .ensure!"length" 79 | .exactly!(1) 80 | .reset 81 | 82 | .fields 83 | .noneOf!Id 84 | .anyOf!Column 85 | .ensure!"length" 86 | .exactly!(2) 87 | .reset 88 | 89 | .constructors 90 | .parameters!() 91 | .ensure!"length" 92 | .exactly!(1) 93 | .reset 94 | 95 | .constructors 96 | .parameters!User 97 | .ensure!"length" 98 | .exactly!(1) 99 | .reset; 100 | } 101 | --------------------------------------------------------------------------------