1 /** 2 * UDA items for creating model structures for your datasets. 3 */ 4 module sqlbuilder.uda; 5 import sqlbuilder.types : Spec; 6 7 /** 8 * Tag your structure to provide an alternative table name. Without this, the 9 * struct's name is used as the table name. 10 */ 11 struct tableName 12 { 13 string name; 14 } 15 16 /// 17 unittest 18 { 19 import sqlbuilder.uda; 20 import sqlbuilder.dialect.mysql; 21 import sqlbuilder.dataset; 22 23 @tableName("trec") static struct TableRecord 24 { 25 int id; 26 } 27 DataSet!TableRecord ds; 28 assert(select(ds).where(ds.id, " = ", 5.param).sql == 29 "SELECT `trec`.* FROM `trec` WHERE (`trec`.`id` = ?)"); 30 } 31 32 33 /// 34 struct AllowNull(T) 35 { 36 T nullValue; 37 } 38 39 /** 40 * Allow null for a specific field that is not typed as Nullable!T. 41 * 42 * One can either assign a value to use as the NULL value, or simply tag a 43 * member with the @allowNull function alias. In the latter case, the .init 44 * value of the column type is used as the NULL value. 45 * 46 * This attribute ONLY applies on reading data. Inserting data using a 47 * blueprint or model will not translate the NULL value sentinel to an insert 48 * of NULL. However, using standard `set` calls, you can still set the value to 49 * NULL. 50 */ 51 AllowNull!T allowNull(T)(T val) 52 { 53 return AllowNull!T(val); 54 } 55 56 /*enum RefType 57 { 58 One, 59 Many, 60 }*/ 61 62 enum Strong : bool 63 { 64 no = false, 65 yes = true 66 } 67 68 /** 69 * This attribute identifies the table that this column or relation refers to. 70 * Use the `mustReferTo` form to more intuitively indicate a strong relation 71 * (rather than pass a `true` as a third parameter to `refersTo`). 72 * 73 * The alias `foreign_table` identifies the other table type that this 74 * identifier refers to. 75 * 76 * The name identifies the name that should be used for a column only. A 77 * relation tagged with this UDA will ignore the name field. 78 * 79 * The joinType field identifies the join that should be used. By default, all 80 * joins are `LEFT` joins so as to not affect the data already selected by the 81 * join. You can override the join type when using the relation from a dataset. 82 * 83 * The strong field identifies that the relatable row must exist in the foreign 84 * table. 85 */ 86 struct refersTo(T) 87 { 88 alias foreign_table = T; 89 string name; 90 Spec joinType = Spec.none; 91 Strong strong = Strong.no; 92 } 93 94 /// ditto 95 refersTo!T mustReferTo(T)(string name = null, Spec joinType = Spec.none) 96 { 97 return refersTo!T(name, joinType, Strong.yes); 98 } 99 100 /** 101 * This defines a mapping for a relationship. Each item can either represent a 102 * field in the foreign table or a literal string to use as the mapping. The 103 * library distinguishes between the two by checking to see if the key could be 104 * a valid identifier. Typically, literals are not valid identifiers in any 105 * SQL. 106 * 107 * If one key is an identifier, and the other is a literal, then the identifier 108 * is always provided first, to allow any relationship to be expressed. 109 * 110 * The default key is "id", as this is the most common name to map tables with. 111 * 112 * Only when both fields are identifiers is the '=' operator inserted. 113 * Otherwise, the operator must be provided in the literal portion. 114 * 115 * If the key field is an identifier, and the mapping attribute is attached to 116 * an actual column, the field is ignored, and the column name is used instead 117 * for the local field. Relations that need multiple column mappings should be 118 * specified as Relation types. 119 * 120 * Examples (assuming ftable = foreign table and ltable = local table): 121 * 122 * mapping("foo", "bar") => ftable.foo = ltable.bar 123 * mapping("foo") => ftable.foo = ltable.id (or ftable.foo = ltable.field when this is attached to a field) 124 * mapping("foo", "IS NULL") => ftable.foo IS NULL 125 * mapping("foo", "= 7") => ftable.foo = 7 126 * 127 * note the following reverse the order of expression 128 * mapping("IS NULL", "bar") => ltable.bar IS NULL 129 * mapping(" = 1", "bar") => ltable.bar = 1 130 * 131 * mapping("1", " = 0") => 1 = 0 (i.e. never match) 132 */ 133 struct mapping 134 { 135 string foreign_key = "id"; 136 string key = "id"; 137 } 138 139 /// 140 unittest 141 { 142 import sqlbuilder.uda; 143 import sqlbuilder.types; 144 import sqlbuilder.dataset; 145 import sqlbuilder.dialect.mysql; 146 147 // foreign table 148 static struct FT 149 { 150 int id; 151 int foo; 152 } 153 154 // local table 155 static struct LT 156 { 157 int id; 158 @refersTo!FT("byColumnMap") @mapping("foo") int bar; 159 @refersTo!FT @mapping("foo", "bar") Relation byFieldMap; 160 @refersTo!FT @mapping("foo") Relation byFooWithId; 161 @refersTo!FT @mapping("foo", "IS NULL") Relation byFooIsNull; 162 @refersTo!FT @mapping("foo", "= 42") Relation byFoo42; 163 @refersTo!FT @mapping("IS NULL", "bar") Relation byBarIsNull; 164 @refersTo!FT @mapping("= 1", "bar") Relation byBar1; 165 @refersTo!FT @mapping("1", "= 0") Relation noMatch; 166 } 167 168 DataSet!LT ds; 169 assert(select(ds.byColumnMap).sql == 170 "SELECT `LT_L_byColumnMap`.* FROM `LT` LEFT JOIN `FT` AS `LT_L_byColumnMap` ON (`LT_L_byColumnMap`.`foo` = `LT`.`bar`)"); 171 assert(select(ds.byFieldMap).sql == 172 "SELECT `LT_L_byFieldMap`.* FROM `LT` LEFT JOIN `FT` AS `LT_L_byFieldMap` ON (`LT_L_byFieldMap`.`foo` = `LT`.`bar`)"); 173 assert(select(ds.byFooWithId).sql == 174 "SELECT `LT_L_byFooWithId`.* FROM `LT` LEFT JOIN `FT` AS `LT_L_byFooWithId` ON (`LT_L_byFooWithId`.`foo` = `LT`.`id`)"); 175 176 assert(select(ds.byFooIsNull).sql == 177 "SELECT `LT_L_byFooIsNull`.* FROM `LT` LEFT JOIN `FT` AS `LT_L_byFooIsNull` ON (`LT_L_byFooIsNull`.`foo` IS NULL)"); 178 assert(select(ds.byFoo42).sql == 179 "SELECT `LT_L_byFoo42`.* FROM `LT` LEFT JOIN `FT` AS `LT_L_byFoo42` ON (`LT_L_byFoo42`.`foo` = 42)"); 180 assert(select(ds.byBarIsNull).sql == 181 "SELECT `LT_L_byBarIsNull`.* FROM `LT` LEFT JOIN `FT` AS `LT_L_byBarIsNull` ON (`LT`.`bar` IS NULL)"); 182 assert(select(ds.byBar1).sql == 183 "SELECT `LT_L_byBar1`.* FROM `LT` LEFT JOIN `FT` AS `LT_L_byBar1` ON (`LT`.`bar` = 1)"); 184 assert(select(ds.noMatch).sql == 185 "SELECT `LT_L_noMatch`.* FROM `LT` LEFT JOIN `FT` AS `LT_L_noMatch` ON (1 = 0)"); 186 } 187 188 /** 189 * Specify a different column name for a field. By default, the field's name is 190 * used. 191 */ 192 struct colName 193 { 194 string name; 195 } 196 197 /// 198 unittest 199 { 200 import sqlbuilder.dataset; 201 import sqlbuilder.uda; 202 import sqlbuilder.dialect.mysql; 203 static struct table 204 { 205 @colName("password_hash") string pwHash; 206 } 207 208 DataSet!table ds; 209 assert(select(ds.pwHash).sql == 210 "SELECT `table`.`password_hash` FROM `table`"); 211 } 212 213 /** 214 * Specify an alternate column type. This is possibly specific to the database 215 * engine. By default the engine picks the type based on the field type. This 216 * only has any bearing when creating tables. 217 * 218 * This does NOT affect parameter types, and should still be something that 219 * properly serializes to/from the given D type. For example, a string field 220 * could be adjusted to be VARCHAR(10) instead of TEXT. Use a specialized type 221 * to convert between different D types. 222 */ 223 struct colType 224 { 225 string type; 226 } 227 228 /// 229 unittest 230 { 231 import sqlbuilder.uda; 232 import sqlbuilder.dialect.mysql; 233 static struct table 234 { 235 @colType("VARCHAR(10)") string name; 236 } 237 238 assert(createTableSql!table == 239 "CREATE TABLE `table` (`name` VARCHAR(10) NOT NULL)"); 240 } 241 242 /** 243 * Ignore a field, it is not considered part of the database data. 244 * 245 * This is useful for fields that only exist on the record in D, but not on the 246 * database. For instance a calculated field that is not in the database, or a 247 * network connection associated with a record. 248 */ 249 enum ignore; 250 251 /** 252 * Tag a column as part of the primary key. 253 * 254 * The primary key is used when generating keys for a type, and also for 255 * updating a record. sqlbuilder does not treat any fields by default as the 256 * primary key. 257 */ 258 enum primaryKey; 259 260 /** 261 * Mark a specific column as unique on the table. This only is used when 262 * generating the table and is not (currently) used in any queries or 263 * executions. 264 */ 265 enum unique; 266 267 /** 268 * Tag a column as receiving an automatic value incremented by the database 269 * (usually the id). 270 * 271 * When inserting whole items into the table, any autoincrement columns are 272 * given the value returned from the database. 273 * 274 * In many cases, database implementations only allow one autoIncrement value 275 * to be returned. In this case, the autoIncrement value will be assigned to 276 * ALL items in the model, even if they are different. 277 */ 278 enum autoIncrement; 279 280 // TODO: figure out how to do indexes besides primary key.