1 /**
2  * This whole package is used for the declarative model descriptions. The
3  * declarative descriptions are automatically generated from the D source code
4  * and are used for the diff process for the migrations generator.
5  *
6  * The conversion from D classes/structs + UDAs into the declarative format
7  * described in this module is done inside the $(REF conversion, dorm,declarative)
8  * module.
9  */
10 module dorm.declarative;
11 
12 import dorm.annotations;
13 import dorm.model;
14 
15 import std.algorithm;
16 import std.array;
17 import std.sumtype;
18 import std.typecons : tuple;
19 
20 import mir.serde;
21 import mir.algebraic_alias.json;
22 
23 /**
24  * This is the root of a described models module. It contains a list of models
25  * as defined in the D source file.
26  *
27  * The `validators` and `valueConstructors` maps contain the global functions
28  * defined in the $(REF defaultValue, dorm,annotations) and $(REF validator,
29  * dorm,annotations) UDAs.
30  */
31 struct SerializedModels
32 {
33 	/// List of all the models defined in the full module file.
34 	@serdeKeys("Models")
35 	ModelFormat[] models;
36 }
37 
38 /** 
39  * Describes a single Model class (Database Table) in a generic format that is
40  * only later used by the drivers to actually convert to SQL statements.
41  */
42 struct ModelFormat
43 {
44 	/** 
45 	 * Describes a field inside the Model class, which corresponds to a column
46 	 * inside the actual database table later. It's using a generic format that
47 	 * is only later used by the drivers to actually convert to SQL statements.
48 	 */
49 	struct Field
50 	{
51 	@safe:
52 		/// List of different (generic) database column types.
53 		@serdeProxy!string
54 		enum DBType
55 		{
56 			varchar, /// inferred from `string`
57 			varbinary, /// inferred from `ubyte[]`
58 			int8, /// inferred from `byte`
59 			int16, /// inferred from `short`
60 			int32, /// inferred from `int`
61 			int64, /// inferred from `long`
62 			floatNumber, /// inferred from `float`
63 			doubleNumber, /// inferred from `double`
64 			boolean, /// inferred from `bool`
65 			date, /// inferred from `std.datetime : Date`
66 			datetime, /// inferred from `std.datetime : DateTime`, `std.datetime : SysTime`, `@AutoCreateTime ulong`, `@AutoUpdateTime ulong`, `@timestamp ulong` (always saved UTC)
67 			time, /// inferred from `std.datetime : TimeOfDay`
68 			choices, /// inferred from `@choices string`, `enum T`
69 			set, /// inferred from `BitFlags!enum`
70 		}
71 
72 		/// The exact name of the column later used in the DB, not neccessarily
73 		/// corresponding to the D field name anymore.
74 		@serdeKeys("Name")
75 		string columnName;
76 		/// Name of the field inside the D source code.
77 		@serdeIgnore
78 		string sourceColumn;
79 		/// D type stringof.
80 		@serdeIgnore
81 		string sourceType;
82 		/// The generic column type that is later translated to a concrete SQL
83 		/// type by a driver.
84 		@serdeKeys("Type")
85 		DBType type;
86 		/// List of different annotations defined in the source code, converted
87 		/// to a serializable format and also all implicit annotations such as
88 		/// `Choices` for enums.
89 		@serdeKeys("Annotations")
90 		DBAnnotation[] annotations;
91 		/// List of annotations only relevant for internal use.
92 		@serdeIgnore
93 		InternalAnnotation[] internalAnnotations;
94 		/// For debugging purposes this is the D source code location where this
95 		/// field is defined from. This can be used in error messages.
96 		@serdeKeys("SourceDefinedAt")
97 		SourceLocation definedAt;
98 
99 		@serdeIgnore
100 		string selectorColumnName(string tableName) const @property
101 		{
102 			return tableName ~ "." ~ columnName ~ " AS __" ~ columnName;
103 		}
104 
105 		/// Returns true if this field does not have the `notNull` AnnotationFlag
106 		/// assigned, otherwise false.
107 		@serdeIgnore
108 		bool isNullable() const @property
109 		{
110 			return !hasFlag(AnnotationFlag.notNull)
111 				&& !hasFlag(AnnotationFlag.primaryKey);
112 		}
113 
114 		/// Returns true iff this field has the `primaryKey` AnnotationFlag.
115 		@serdeIgnore
116 		bool isPrimaryKey() const @property
117 		{
118 			return hasFlag(AnnotationFlag.primaryKey);
119 		}
120 
121 		/// Returns true iff this field has the `ForeignKeyImpl` annotation.
122 		@serdeIgnore
123 		bool isForeignKey() const @property
124 		{
125 			foreach (annotation; annotations)
126 			{
127 				if (annotation.value.match!(
128 					(ForeignKeyImpl f) => true,
129 					_ => false
130 				))
131 					return true;
132 			}
133 			return false;
134 		}
135 
136 		/// Returns true iff this field has the given AnnotationFlag assigned.
137 		@serdeIgnore
138 		bool hasFlag(AnnotationFlag q) const @property
139 		{
140 			foreach (annotation; annotations)
141 			{
142 				if (annotation.value.match!(
143 					(AnnotationFlag f) => f == q,
144 					_ => false
145 				))
146 					return true;
147 			}
148 			return false;
149 		}
150 
151 		@serdeIgnore
152 		bool hasDefaultValue() const @property
153 		{
154 			import std.datetime;
155 
156 			foreach (annotation; annotations)
157 			{
158 				if (annotation.value.match!(
159 					(d) {
160 						static assert(is(typeof(d) : DefaultValue!T, T));
161 						return true;
162 					},
163 					_ => false
164 				))
165 					return true;
166 			}
167 			return false;
168 		}
169 
170 		/// Human-readable description how fields with auto-generated values
171 		/// (non-required values) can be specified.
172 		static immutable string humanReadableGeneratedDefaultValueTypes =
173 			`Annotations for automatic value generation: @defaultValue(v), `
174 			~ `@defaultFromInit,  @constructValue(() => v), @autoCreateTime, `
175 			~ `@autoIncrement or change type to Nullable!T for default null.`;
176 
177 		/**
178 		 * Returns true if:
179 		 * - this field has some annotation that auto-generates a value if it's
180 		 *   not provided in an insert statement,
181 		 *   (@defaultValue, @autoCreateTime, @autoIncrement)
182 		 * - has a `@constructValue` annotation (which is handled in db.d)
183 		 * - is nullable (e.g. of type `Nullable!T`), which implies that `null`
184 		 *   is the default value.
185 		 */
186 		@serdeIgnore
187 		bool hasGeneratedDefaultValue() const @property
188 		{
189 			return hasDefaultValue
190 				|| hasConstructValue
191 				|| isNullable
192 				|| hasFlag(AnnotationFlag.autoCreateTime)
193 				|| hasFlag(AnnotationFlag.autoIncrement);
194 		}
195 
196 		/**
197 		 * Returns true if this field has a $(REF constructValue, dorm.annotations)
198 		 * annotation.
199 		 */
200 		@serdeIgnore
201 		bool hasConstructValue() const @property
202 		{
203 			import std.datetime;
204 
205 			foreach (annotation; internalAnnotations)
206 			{
207 				if (annotation.match!(
208 					(const ConstructValueRef c) => true,
209 					_ => false
210 				))
211 					return true;
212 			}
213 			return false;
214 		}
215 
216 		/**
217 		 * Returns true if this field is the default `id` field defined in the
218 		 * $(REF Model, dorm.model) super-class.
219 		 */
220 		@serdeIgnore
221 		bool isBuiltinId() const @property
222 		{
223 			return sourceColumn == "_fallbackId";
224 		}
225 
226 		@serdeIgnore
227 		string sourceReferenceName(string modelName = null) const @property
228 		{
229 			if (modelName.length)
230 				return sourceType ~ " " ~ sourceColumn
231 					~ " in " ~ modelName ~ " (from "
232 					~ definedAt.toString ~ ")";
233 			else
234 				return sourceType ~ " " ~ sourceColumn
235 					~ " (from " ~ definedAt.toString ~ ")";
236 		}
237 
238 		@serdeIgnore
239 		DBAnnotation[] foreignKeyAnnotations() const @property
240 		{
241 			DBAnnotation[] ret;
242 			foreach (annotation; annotations)
243 				if (annotation.isForeignKeyInheritable)
244 					ret ~= annotation;
245 			return ret;
246 		}
247 
248 		@serdeIgnore
249 		string toPrettySourceString() const
250 		{
251 			string ret = sourceColumn ~ " : " ~ sourceType;
252 			if (isBuiltinId)
253 				ret ~= " [builtin ID]";
254 			else if (isPrimaryKey)
255 				ret ~= " [primary]";
256 
257 			if (isNullable)
258 				ret ~= " [nullable]";
259 			if (hasGeneratedDefaultValue)
260 				ret ~= " [has default]";
261 			return ret;
262 		}
263 	}
264 
265 	/// The exact name of the table later used in the DB, not neccessarily
266 	/// corresponding to the D class name anymore.
267 	@serdeKeys("Name")
268 	string tableName;
269 	/// For debugging purposes this is the D source code location where this
270 	/// field is defined from. This can be used in error messages.
271 	@serdeKeys("SourceDefinedAt")
272 	SourceLocation definedAt;
273 	/// List of fields, such as defined in the D source code, recursively
274 	/// including all fields from all inherited classes. This maps to the actual
275 	/// SQL columns later when it is generated into an SQL create statement by
276 	/// the actual driver implementation.
277 	@serdeKeys("Fields")
278 	Field[] fields;
279 	/// Lists the source field names for embedded structs, recursively.
280 	@serdeIgnore
281 	string[] embeddedStructs;
282 
283 	/// Perform checks if the model description seems valid (does not validate
284 	/// fields, only general model things)
285 	package string lint(string errorPrefix)
286 	{
287 		string errors;
288 
289 		bool hasPrimary;
290 		// https://github.com/myOmikron/drorm/issues/7
291 		Field[] autoIncrementFields;
292 		foreach (field; fields)
293 		{
294 			if (field.hasFlag(AnnotationFlag.autoIncrement))
295 				autoIncrementFields ~= field;
296 			if (field.hasFlag(AnnotationFlag.primaryKey))
297 				hasPrimary = true;
298 		}
299 
300 		if (autoIncrementFields.length > 1)
301 		{
302 			errors ~= errorPrefix ~ "Multiple fields with @autoIncrement defined, only one is allowed:";
303 			foreach (field; autoIncrementFields)
304 				errors ~= errorPrefix ~ "\t" ~ field.sourceReferenceName(tableName);
305 		}
306 
307 		if (!hasPrimary)
308 			errors ~= errorPrefix ~ "No primary key defined on this model. Consider adding a simple auto-increment integer:"
309 				~ errorPrefix ~ "\t`@Id long id;`";
310 
311 		return errors;
312 	}
313 }
314 
315 /**
316  * The source location where something is defined in D code.
317  *
318  * The implementation uses [__traits(getLocation)](https://dlang.org/spec/traits.html#getLocation)
319  */
320 struct SourceLocation
321 {
322 	/// The D filename, assumed to be of the same format as [__FILE__](https://dlang.org/spec/expression.html#specialkeywords).
323 	@serdeKeys("File")
324 	string sourceFile;
325 	/// The 1-based line number and column number where the symbol is defined.
326 	@serdeKeys("Line")
327 	int sourceLine;
328 	/// ditto
329 	@serdeKeys("Column")
330 	int sourceColumn;
331 
332 	string toString() const @safe
333 	{
334 		import std.conv : text;
335 	
336 		string ret = text(sourceFile, "(", sourceLine, ",", sourceColumn, ")");
337 		if (__ctfe)
338 			return ret.idup;
339 		else
340 			return ret;
341 	}
342 
343 	/// Same as toString, but bolds the string using ANSI escape codes
344 	string toErrorString() const @safe
345 	{
346 		return "\x1B[1m" ~ toString ~ ": \x1B[1;31mError: (DORM)\x1B[m";
347 	}
348 }
349 
350 /**
351  * This enum contains all no-argument flags that can be added as annotation to
352  * the fields. It's part of the $(LREF DBAnnotation) SumType.
353  */
354 enum AnnotationFlag
355 {
356 	/// corresponds to the $(REF autoCreateTime, dorm,annotations) UDA.
357 	autoCreateTime,
358 	/// corresponds to the $(REF autoUpdateTime, dorm,annotations) UDA.
359 	autoUpdateTime,
360 	/// corresponds to the $(REF autoIncrement, dorm,annotations) UDA.
361 	autoIncrement,
362 	/// corresponds to the $(REF primaryKey, dorm,annotations) UDA.
363 	primaryKey,
364 	/// corresponds to the $(REF unique, dorm,annotations) UDA.
365 	unique,
366 	/// corresponds to the $(REF notNull, dorm,annotations) UDA. Implicit for all types except Nullable!T and Model.
367 	notNull
368 }
369 
370 private bool isCompatibleFlags(AnnotationFlag a, AnnotationFlag b) @safe
371 {
372 	final switch (a) with (AnnotationFlag)
373 	{
374 		case autoCreateTime: return !b.among!(
375 			autoIncrement,
376 			primaryKey,
377 			unique,
378 		);
379 		case autoUpdateTime: return !b.among!(
380 			autoIncrement,
381 			primaryKey,
382 			unique,
383 		);
384 		case autoIncrement: return !b.among!(
385 			autoCreateTime,
386 			autoUpdateTime,
387 		);
388 		case primaryKey: return !b.among!(
389 			autoCreateTime,
390 			autoUpdateTime,
391 			notNull,
392 		);
393 		case unique: return !b.among!(
394 			autoCreateTime,
395 			autoUpdateTime,
396 		);
397 		case notNull: return !b.among!(
398 			primaryKey
399 		);
400 	}
401 }
402 
403 /**
404  * SumType combining all the different annotations (UDAs) that can be added to
405  * a model field, in a serializable format. (e.g. the lambdas are moved into a
406  * helper field in the model description and these annotations only contain an
407  * integer to reference it)
408  */
409 @serdeProxy!IonDBAnnotation
410 struct DBAnnotation
411 {
412 @safe:
413 	SumType!(
414 		AnnotationFlag,
415 		maxLength,
416 		PossibleDefaultValueTs,
417 		Choices,
418 		index,
419 		ForeignKeyImpl
420 	) value;
421 	alias value this;
422 
423 	this(T)(T v)
424 	{
425 		value = v;
426 	}
427 
428 	auto opAssign(T)(T v)
429 	{
430 		value = v;
431 		return this;
432 	}
433 
434 	/// Returns true if the other annotation can be used together with this one.
435 	/// Must not call on itself, only on other instances. (which may be the same
436 	/// attribute however)
437 	bool isCompatibleWith(DBAnnotation other, bool firstTry = true)
438 	{
439 		return match!(
440 			(AnnotationFlag lhs, AnnotationFlag rhs) => isCompatibleFlags(lhs, rhs),
441 			(maxLength lhs, AnnotationFlag rhs) => !rhs.among!(
442 				AnnotationFlag.autoCreateTime,
443 				AnnotationFlag.autoUpdateTime,
444 				AnnotationFlag.autoIncrement,
445 			),
446 			(maxLength lhs, Choices rhs) => false,
447 			(Choices lhs, AnnotationFlag rhs) => !rhs.among!(
448 				AnnotationFlag.autoCreateTime,
449 				AnnotationFlag.autoUpdateTime,
450 				AnnotationFlag.autoIncrement,
451 				AnnotationFlag.primaryKey,
452 				AnnotationFlag.unique,
453 			),
454 			(index lhs, AnnotationFlag rhs) => rhs != AnnotationFlag.primaryKey,
455 			(lhs, AnnotationFlag rhs) {
456 				static assert(is(typeof(lhs) : DefaultValue!T, T));
457 				return !rhs.among!(
458 					AnnotationFlag.autoCreateTime,
459 					AnnotationFlag.autoUpdateTime,
460 					AnnotationFlag.autoIncrement,
461 					AnnotationFlag.primaryKey,
462 					AnnotationFlag.unique,
463 				);
464 			},
465 			(lhs, _) {
466 				static assert(is(typeof(lhs) : DefaultValue!T, T));
467 				return true;
468 			},
469 			(index lhs, index rhs) => true,
470 			(ForeignKeyImpl lhs, AnnotationFlag rhs) => !rhs.among!(
471 				AnnotationFlag.autoCreateTime,
472 				AnnotationFlag.autoUpdateTime,
473 				AnnotationFlag.autoIncrement,
474 				AnnotationFlag.primaryKey,
475 				AnnotationFlag.unique,
476 			),
477 			(ForeignKeyImpl lhs, ForeignKeyImpl rhs) => false,
478 			(ForeignKeyImpl lhs, Choices rhs) => false,
479 			(ForeignKeyImpl lhs, _) => true,
480 			(a, b) => firstTry ? other.isCompatibleWith(this, false) : false
481 		)(value, other.value);
482 	}
483 
484 	bool isForeignKeyInheritable() const
485 	{
486 		return value.match!(
487 			(const AnnotationFlag _) => false,
488 			(const maxLength _) => true,
489 			(someDefaultValue) {
490 				static assert(is(typeof(someDefaultValue) : DefaultValue!T, T));
491 				return false;
492 			},
493 			(const Choices _) => false,
494 			(const index _) => false,
495 			(const ForeignKeyImpl _) => false
496 		);
497 	}
498 }
499 
500 alias InternalAnnotation = SumType!(
501 	ConstructValueRef,
502 	ValidatorRef,
503 );
504 
505 private struct IonDBAnnotation
506 {
507 	JsonAlgebraic data;
508 
509 	this(DBAnnotation a) @safe
510 	{
511 		a.match!(
512 			(AnnotationFlag f) {
513 				string typeStr;
514 				final switch (f)
515 				{
516 					case AnnotationFlag.autoCreateTime:
517 						typeStr = "auto_create_time";
518 						break;
519 					case AnnotationFlag.autoUpdateTime:
520 						typeStr = "auto_update_time";
521 						break;
522 					case AnnotationFlag.notNull:
523 						typeStr = "not_null";
524 						break;
525 					case AnnotationFlag.autoIncrement:
526 						typeStr = "auto_increment";
527 						break;
528 					case AnnotationFlag.primaryKey:
529 						typeStr = "primary_key";
530 						break;
531 					case AnnotationFlag.unique:
532 						typeStr = "unique";
533 						break;
534 				}
535 				data = JsonAlgebraic([
536 					"Type": JsonAlgebraic(typeStr)
537 				]);
538 			},
539 			(maxLength l) {
540 				data = JsonAlgebraic([
541 					"Type": JsonAlgebraic("max_length"),
542 					"Value": JsonAlgebraic(l.maxLength)
543 				]);
544 			},
545 			(Choices c) {
546 				data = JsonAlgebraic([
547 					"Type": JsonAlgebraic("choices"),
548 					"Value": JsonAlgebraic(c.choices.map!(v => JsonAlgebraic(v)).array)
549 				]);
550 			},
551 			(index i) {
552 				JsonAlgebraic[string] args;
553 				if (i._composite !is i.composite.init)
554 					args["Name"] = i._composite.name;
555 				if (i._priority !is i.priority.init)
556 					args["Priority"] = i._priority.priority;
557 
558 				if (args.empty)
559 					data = JsonAlgebraic(["Type": JsonAlgebraic("index")]);
560 				else
561 					data = JsonAlgebraic([
562 						"Type": JsonAlgebraic("index"),
563 						"Value": JsonAlgebraic(args)
564 					]);
565 			},
566 			(DefaultValue!(ubyte[]) binary) {
567 				import std.digest : toHexString;
568 
569 				data = JsonAlgebraic([
570 					"Type": JsonAlgebraic("default_value"),
571 					"Value": JsonAlgebraic(binary.value.toHexString)
572 				]);
573 			},
574 			(ForeignKeyImpl foreignKey) {
575 				import std.digest : toHexString;
576 
577 				data = JsonAlgebraic([
578 					"Type": JsonAlgebraic("foreign_key"),
579 					"Value": JsonAlgebraic([
580 						"TableName": JsonAlgebraic(foreignKey.table),
581 						"ColumnName": JsonAlgebraic(foreignKey.column),
582 						"OnUpdate": JsonAlgebraic(foreignKey.onUpdate.toPascalCase),
583 						"OnDelete": JsonAlgebraic(foreignKey.onDelete.toPascalCase)
584 					])
585 				]);
586 			},
587 			(rest) {
588 				static assert(is(typeof(rest) == DefaultValue!U, U));
589 				static if (__traits(hasMember, rest.value, "toISOExtString"))
590 				{
591 					data = JsonAlgebraic([
592 						"Type": JsonAlgebraic("default_value"),
593 						"Value": JsonAlgebraic(rest.value.toISOExtString)
594 					]);
595 				}
596 				else
597 				{
598 					data = JsonAlgebraic([
599 						"Type": JsonAlgebraic("default_value"),
600 						"Value": JsonAlgebraic(rest.value)
601 					]);
602 				}
603 			}
604 		);
605 	}
606 
607 	void serialize(S)(scope ref S serializer) const
608 	{
609 		import mir.ser : serializeValue;
610 
611 		serializeValue(serializer, data);
612 	}
613 }
614 
615 private string toPascalCase(ReferentialAction type) @safe nothrow @nogc pure
616 {
617 	final switch (type)
618 	{
619 		case restrict: return "Restrict";
620 		case cascade: return "Cascade";
621 		case setNull: return "SetNull";
622 		case setDefault: return "SetDefault";
623 	}
624 }
625 
626 /**
627  * Corresponds to the $(REF constructValue, dorm,annotations) and $(REF
628  * constructValue, dorm,annotations) UDAs.
629  *
630  * A global function that is compiled into the executable through the call of
631  * $(REF processModelsToDeclarations, dorm,declarative) generating the
632  * `InternalAnnotation` values. Manually constructing this function is not
633  * required, use the $(REF RegisterModels, dorm,declarative,entrypoint) mixin
634  * instead.
635  *
636  * The functions take in a Model (class) instance and assert it is the correct
637  * model class type that it was registered with.
638  */
639 struct ConstructValueRef
640 {
641 	/**
642 	 * This function calls the UDA specified lambda without argument and
643 	 * sets the annotated field value inside the containing Model instance to
644 	 * its return value, with the code assuming it can simply assign it.
645 	 * (a compiler error will occur if it cannot implicitly convert to the
646 	 * annotated property type)
647 	 *
648 	 * The value itself is meaningless, it's only for later executing the actual
649 	 * callback through runValueConstructor.
650 	 */
651 	string rid;
652 }
653 
654 /// ditto
655 struct ValidatorRef
656 {
657 	/**
658 	 * This function calls the UDA specified lambda with the field as argument
659 	 * and returns its return value, with the code assuming it is a boolean.
660 	 * (a compiler error will occur if it cannot implicitly convert to `bool`)
661 	 *
662 	 * The value itself is meaningless, it's only for later executing the actual
663 	 * callback through runValueConstructor.
664 	 */
665 	string rid;
666 }
667 
668 unittest
669 {
670 	import mir.ser.json;
671 
672 	SerializedModels models;
673 	ModelFormat m;
674 	m.tableName = "foo";
675 	m.definedAt = SourceLocation("file.d", 140, 10);
676 	ModelFormat.Field f;
677 	f.columnName = "foo";
678 	f.type = ModelFormat.Field.DBType.varchar;
679 	f.definedAt = SourceLocation("file.d", 142, 12);
680 	f.annotations = [
681 		DBAnnotation(AnnotationFlag.notNull),
682 		DBAnnotation(AnnotationFlag.primaryKey),
683 		DBAnnotation(index()),
684 		DBAnnotation(maxLength(255))
685 	];
686 	f.internalAnnotations = [
687 		InternalAnnotation(ValidatorRef("NONE"))
688 	];
689 	m.fields = [f];
690 
691 	models.models = [m];
692 	string json = serializeJsonPretty(models);
693 	assert(json == `{
694 	"Models": [
695 		{
696 			"Name": "foo",
697 			"SourceDefinedAt": {
698 				"File": "file.d",
699 				"Line": 140,
700 				"Column": 10
701 			},
702 			"Fields": [
703 				{
704 					"Name": "foo",
705 					"Type": "varchar",
706 					"Annotations": [
707 						{
708 							"Type": "not_null"
709 						},
710 						{
711 							"Type": "primary_key"
712 						},
713 						{
714 							"Type": "index"
715 						},
716 						{
717 							"Type": "max_length",
718 							"Value": 255
719 						}
720 					],
721 					"SourceDefinedAt": {
722 						"File": "file.d",
723 						"Line": 142,
724 						"Column": 12
725 					}
726 				}
727 			]
728 		}
729 	]
730 }`, json);
731 }