ReQL: pluses and minuses of pipeline-style queries

Rikki Cattermole via Digitalmars-d digitalmars-d at puremagic.com
Sun Apr 26 05:27:19 PDT 2015


On 27/04/2015 12:10 a.m., Laeeth Isharc wrote:
> On Sunday, 26 April 2015 at 01:03:12 UTC, Rikki Cattermole wrote:
>>
>> I'm personally moving towards a DSL.
>>
>> unittest {
>>     auto myQuery = """
>> using webdev.base.orm.query.parser.defs # allow for D class name
>> instead of table name
>> ; # end of \"sentence\"
>>
>> from MyModel
>> where key == $0
>> as simple
>> # as index # but only one can be defined, two in total internal
>> ; # end of \"sentence\"
>>
>> from MyModel
>> where value contains $0
>> as complex
>> # as index # but only one can be defined, two in total internal
>> ; # end of \"sentence\"
>>
>> """.query;
>>
>>     MyModel[] gotValues = myQuery.simple("test");
>>     gotValues = myQuery.perform("complex", "another");
>> }
>>
>> Pros:
>> - Runtime reloadability
>> - Runtime composability
>> - More flexible
>> Cons:
>> - It's a string
>
> Can Pegged be usefully applied to implement this?
>
> Incidentally, it seems that although Hibernated is very nice, there is
> still work to be done.  Eg I would like to insert 10 million rows, and
> it seems like a transaction would be the best way of doing so, but it's
> not yet supported.  No big deal since the schema is very simple and I
> can do it by hand, but it would be nice to have at some point.  (I
> looked at your own ORM, but keeping it in memory won't work for me).

Dvorm like Cmsed is going bye byes.
My next web service framework is going to have a very different approach 
to ORM's and routing!

The given code example, is an actual unittest for the parser. It'll be 
using a reflection API to wrap ORM models up. So the ORM will no longer 
do serialization. Instead the backend will to the appropriate types.

Just so you get a small taste of the reflection capabilities. I know it 
may not seem like much, but imagine e.g. lua to have wrapper code around 
this and to act as if the data models were written natively in it or to 
pass a template (lets say lua based) a bunch of data models and have it 
call D methods on it!
The only problem is that the web server that it is meant to compliment 
isn't ready yet.
https://github.com/DNetDev/webserver

Until I or somebody else gets round to implementing this 
https://github.com/rejectedsoftware/vibe.d/issues/1074 mindset wise, I 
can't continue on.

Also you are welcome to join https://gitter.im/DNetDev/Public if you 
want to talk more.

module webdev.base.reflection.model;
import webdev.base.traits.are : isADataModel, isADataModelProperty, 
isDataModelMemberId, isADataModelQueryMethod, isADataModelQueryStaticMethod;
import webdev.base.traits.have : getDataModelName, 
getDataModelDescription, getDataModelPropertyDescription, 
getDataModelPropertyHints, getDataModelPropertyName;
import webdev.base.udas : OrmPropertyTypes;

private __gshared {
	import std.variant : Algebraic;
	import std.traits : fullyQualifiedName, isArray, ReturnType, 
ParameterTypeTuple;

	AReflectedModel*[string] models;
	AReflectedModel*[string] modelsByTableName;
}

/*
  * Basic interactions of the different kinds of models
  */

/**
  * Gets all names of data models registered
  * Uses the fully qualified name (package + module + class/struct name)
  *
  * Returns:
  * 		The names to all data models
  */
string[] reflectedModelNames() {
	return models.keys;
}

/**
  * Lazily registers and gets a reflected model given the data model type
  *
  * Returns:
  * 		The reflected model
  */
AReflectedModel* getReflectModel(T)() if(isADataModel!T) {
	return getReflectedModel(fullyQualifiedName!T);
}


/**
  * Gets a reflected model based upon its fully qualified name
  *
  * Params:
  * 		name	=	Name of the model
  *
  * Returns:
  * 		The reflected model
  */
AReflectedModel* getReflectedModel(string name) {
	if (name !in models) return null;
	return models[name].dup();
}

/**
  * Gets a reflected model based upon its table name
  *
  * Params:
  * 		name	=	Name of the model
  *
  * Returns:
  * 		The reflected model
  */
AReflectedModel* getReflectedModelByTableName(string name) {
	if (name !in modelsByTableName) return null;
	return modelsByTableName[name].dup();
}

/*
  * General reflection based types
  */

///
enum OrmActualPropertyTypes {
	Unknown,

	UByte,
	Byte,
	UShort,
	Short,
	UInt,
	Int,
	ULong,
	Long,
	Float,
	Double,
	String,
	WString,
	DString,
	
	Array,
	DataModel
}

///
alias ModelValidTypes = Algebraic!(AReflectedModelInstance*, ubyte, 
byte, ushort, short, uint, int, ulong, long, float, double, string, 
wstring, dstring);

///
struct PropertyHint {
	///
	string name;

	///
	OrmActualPropertyTypes actualType;

	///
	OrmActualPropertyTypes arrayActualType;

	///
	AReflectedModel* objectActualType;

	///
	OrmPropertyTypes hintType;

	///
	size_t size;

	///
	string description;

	///
	bool isId;
}

///
struct QueryDescriptor {
	///
	QueryTypeDescriptor[] arguments;

	///
	QueryTypeDescriptor returnType;

	struct QueryTypeDescriptor {
		///
		OrmActualPropertyTypes actualType;
		
		///
		OrmActualPropertyTypes arrayActualType;
		
		///
		AReflectedModel* objectActualType;
	}
}

/*
  * The actual reflection mechanism
  */

struct AReflectedModel {
	static AReflectedModel* reflect(T)() if(isADataModel!T) {
		AReflectedModel* ret = new AReflectedModel;

		ret.dup = () { return AReflectedModel.reflect!T(); };

		/// constructs function delegates for a model instance aware of the type
		void funcCalls(AReflectedModelInstance* retm) {
			static if (__traits(hasMember, T, "isValid")) {
				retm.isValid = () { return (cast(T*)retm.instance_).isValid(); };
			} else {
				retm.isValid = () { return true; };
			}

			retm.get = (string name) {
				foreach(member; __traits(allMembers, T)) {
					static if (isADataModelProperty!(T, member)) {
						if (member == name) {
							return new ModelValidTypes(mixin("(cast(T*)retm.instance_)." ~ 
member));
						}
					}
				}

				return null;
			};

			retm.set = (string name, ModelValidTypes* value) {
				foreach(member; __traits(allMembers, T)) {
					mixin("alias MTYPE = typeof(T." ~ member ~ ");");

					static if (isADataModelProperty!(T, member)) {
						if (member == name) {
							static if (__traits(compiles, {MTYPE t = null;})) {
								if (value is null) {
									mixin("(cast(T*)retm.instance_)." ~ member ~ " = null;");
								} else if (value.convertsTo!MTYPE) {
									mixin("(cast(T*)retm.instance_)." ~ member ~ " = 
value.get!MTYPE;");
								} else {
									assert(0, value.type.toString ~ " is not convertable to " ~ 
MTYPE.stringof);
								}
							} else {
								if (value !is null && value.convertsTo!MTYPE) {
									mixin("(cast(T*)retm.instance_)." ~ member ~ " = 
value.get!MTYPE;");
								} else {
									assert(0, value.type.toString ~ " is not convertable to " ~ 
MTYPE.stringof);
								}
							}
						}
					}
				}
			};

			retm.query = (string name, ModelValidTypes[] values...) {
				foreach(member; __traits(allMembers, T)) {
					mixin("alias MTYPE = typeof(T." ~ member ~ ");");

					static if (isADataModelQueryMethod!(T, member)) {
						alias MRET = ReturnType!MTYPE;

						if (member == name) {
							alias ARGU = ParameterTypeTuple!MTYPE;

							if (values.length != ARGU.length)
								assert(0, "Not enough arguments");
								
							foreach(i, ARG; ARGU) {
								if (!values[i].convertsTo!ARG)
									assert(0, "Wrong types for arguments");
							}

							static if (is(MRET == void)) {
								// return call
								mixin("(cast(T*)retm.instance)." ~ member ~ "(" ~ 
getCallToMethodSyntaxVarient!("values", "ARGU", ARGU) ~ ");");
								return cast(ModelValidTypes[])null;
							} else {
								// call
								mixin("auto ret = (cast(T*)retm.instance_)." ~ member ~ "(" ~ 
getCallToMethodSyntaxVarient!("values", "ARGU", ARGU) ~ ");");

								static if (isArray!MRET) {
									ModelValidTypes[] ret2;

									foreach(v; ret) {
										ret2 ~= ModelValidTypes(v);
									}

									return ret2;
								} else {
									return cast(ModelValidTypes[])[ModelValidTypes(v)];
								}
							}
						}
					}
				}

				assert(0);
			};
		}

		ret.create = () {
			AReflectedModelInstance* retm = new AReflectedModelInstance;
			retm.model_ = ret;

			static if (is(T == class))
				retm.instance_ = &(new T);
			else static if (is(T == struct))
				retm.instance_ = new T;
			else static assert(0);
			
			funcCalls(retm);
			return retm;
		};

		ret.fromInstance = (void* value) {
			/// in
	
			assert(value !is null);

			if (T* ttv = cast(T*)value){}
			else assert(0, "Argument is not of type " ~ T.stringof);

			/// body

			AReflectedModelInstance* retm = new AReflectedModelInstance;
			retm.model_ = ret;

			retm.instance_ = value;

			funcCalls(retm);
			return retm;
		};

		ret.tableName = () { return getDataModelName!T; };
		ret.fullName = () { return fullyQualifiedName!T; };
		ret.description = () { return getDataModelDescription!T; };

		ret.propertyNames = () {
			string[] retm;

			foreach(member; __traits(allMembers, T)) {
				static if (isADataModelProperty!(T, member)) {
					retm ~= member;
				}
			}

			return retm;
		};

		ret.propertyHints = (string name) {
			PropertyHint retm;

			foreach(member; __traits(allMembers, T)) {
				static if (isADataModelProperty!(T, member)) {
					if (member == name) {
						mixin("alias MTYPE = typeof(T." ~ member ~ ");");

						retm.name = getDataModelPropertyName!(T, member);

						// actualType
						enum MTYPEA = actualTypeFromType!MTYPE;
						retm.actualType = MTYPEA;

						// arrayActualType
						static if (MTYPEA == OrmActualPropertyTypes.Array)
							retm.arrayActualType = actualTypeFromType!(typeof(MTYPE.init)[0]);

						// objectActualType
						static if (MTYPEA == OrmActualPropertyTypes.DataModel)
							retm.objectActualType = getReflectModel!MTYPE;

						auto hint = getDataModelPropertyHints!(T, member);

						// hintType
						retm.hintType = hint.type;

						// size
						if (hint.size == 0) {
							static if (isArray!MTYPE) {
							} else
								hint.size = MTYPE.sizeof;
						} else
							retm.size = hint.size;

						// description
						retm.description = getDataModelPropertyDescription!(T, member);

						// isId
						retm.isId = isDataModelMemberId!(T, member);
					}
				}
			}

			return retm;
		};

		ret.query = (string name, ModelValidTypes[] values...) {
			foreach(member; __traits(allMembers, T)) {
				mixin("alias MTYPE = typeof(T." ~ member ~ ");");
				
				static if (isADataModelQueryStaticMethod!(T, member)) {
					alias MRET = ReturnType!MTYPE;
					
					if (member == name) {
						alias ARGU = ParameterTypeTuple!MTYPE;
						
						if (values.length != ARGU.length)
							assert(0, "Not enough arguments");
						
						foreach(i, ARG; ARGU) {
							if (!values[i].convertsTo!ARG)
								assert(0, "Wrong types for arguments");
						}
						
						static if (is(MRET == void)) {
							// return call
							mixin("T." ~ member ~ "(" ~ 
getCallToMethodSyntaxVarient!("values", "ARGU", ARGU) ~ ");");
							return cast(ModelValidTypes[])null;
						} else {
							// call
							mixin("auto ret = T." ~ member ~ "(" ~ 
getCallToMethodSyntaxVarient!("values", "ARGU", ARGU) ~ ");");
							
							ModelValidTypes[] ret2;

							static if (isArray!MRET) {
								foreach(v; ret) {
									static if (is(typeof(v) == class) || is(typeof(v) == struct)) {
										ret2 ~= 
ModelValidTypes(getReflectModel!(typeof(v))().fromInstance(&v));
									} else {
										ret2 ~= ModelValidTypes(v);
									}
								}
							} else {
								static if (is(MRET == class) || is(MRET == struct)) {
									ret2 ~= 
ModelValidTypes(getReflectModel!(MRET)().fromInstance(&ret));
								} else {
									ret2 ~= ModelValidTypes(ret);
								}
							}

							return ret2;
						}
					}
				}
			}
			
			assert(0);
		};

		// queryNames
		ret.queryNames = () {
			string[] ret;

			foreach(member; __traits(allMembers, T)) {
				mixin("alias MTYPE = typeof(T." ~ member ~ ");");
				
				static if (isADataModelQueryStaticMethod!(T, member)) {
					ret ~= member;
				}
			}

			return ret;
		};

		// queryParameters
		ret.queryParameters = (string name) {
			QueryDescriptor ret;
			
			foreach(member; __traits(allMembers, T)) {
				static if (isADataModelQueryStaticMethod!(T, member)) {
					if (member == name) {

						// arguments
						foreach(ARG; ParameterTypeTuple!(mixin("T." ~ name))) {
							QueryDescriptor.QueryTypeDescriptor rett;

							// actualType
							enum MTYPEA = actualTypeFromType!ARG;
							rett.actualType = MTYPEA;

							// arrayActualType
							static if (MTYPEA == OrmActualPropertyTypes.Array)
								rett.arrayActualType = actualTypeFromType!(typeof(ARG.init)[0]);

							// objectActualType
							static if (MTYPEA == OrmActualPropertyTypes.DataModel)
								rett.objectActualType = getReflectModel!ARG;

							ret.arguments ~= rett;
						}

						// return type

						alias RETM = ReturnType!(mixin("T." ~ m));

						// actualType
						enum MTYPEA = actualTypeFromType!RETM;
						ret.returnType.actualType = MTYPEA;
						
						// arrayActualType
						static if (MTYPEA == OrmActualPropertyTypes.Array)
							ret.returnType.arrayActualType = 
actualTypeFromType!(typeof(ARG.init)[0]);
						
						// objectActualType
						static if (MTYPEA == OrmActualPropertyTypes.DataModel)
							ret.returnType.objectActualType = getReflectModel!ARG;
					}
				}
			}
			
			return ret;
		};

		// queryInstanceNames
		ret.queryInstanceNames = () {
			string[] ret;
			
			foreach(member; __traits(allMembers, T)) {
				mixin("alias MTYPE = typeof(T." ~ member ~ ");");
				
				static if (isADataModelQueryMethod!(T, member)) {
					ret ~= member;
				}
			}
			
			return ret;
		};

		// queryInstanceParameters
		ret.queryInstanceParameters = (string name) {
			QueryDescriptor ret;
			
			foreach(member; __traits(allMembers, T)) {
				static if (isADataModelQueryMethod!(T, member)) {
					if (member == name) {
						
						// arguments
						foreach(ARG; ParameterTypeTuple!(mixin("T." ~ name))) {
							QueryDescriptor.QueryTypeDescriptor rett;
							
							// actualType
							enum MTYPEA = actualTypeFromType!ARG;
							rett.actualType = MTYPEA;
							
							// arrayActualType
							static if (MTYPEA == OrmActualPropertyTypes.Array)
								rett.arrayActualType = actualTypeFromType!(typeof(ARG.init)[0]);
							
							// objectActualType
							static if (MTYPEA == OrmActualPropertyTypes.DataModel)
								rett.objectActualType = getReflectModel!ARG;
							
							ret.arguments ~= rett;
						}
						
						// return type
						
						alias RETM = ReturnType!(mixin("T." ~ m));
						
						// actualType
						enum MTYPEA = actualTypeFromType!RETM;
						ret.returnType.actualType = MTYPEA;
						
						// arrayActualType
						static if (MTYPEA == OrmActualPropertyTypes.Array)
							ret.returnType.arrayActualType = 
actualTypeFromType!(typeof(ARG.init)[0]);
						
						// objectActualType
						static if (MTYPEA == OrmActualPropertyTypes.DataModel)
							ret.returnType.objectActualType = getReflectModel!ARG;
					}
				}
			}
			
			return ret;
		};

		models[fullyQualifiedName!T] = ret;
		models[ret.tableName()] = ret;
		return ret;
	}

	AReflectedModel* delegate() dup;
	AReflectedModelInstance* delegate() create;
	AReflectedModelInstance* delegate(void*) fromInstance;

	string delegate() tableName;
	string delegate() fullName;
	string delegate() description;

	const(string[]) delegate() propertyNames;
	PropertyHint delegate(string name) propertyHints;

	ModelValidTypes[] delegate(string func, ModelValidTypes[] values...) query;
	const(string[]) delegate() queryNames;
	QueryDescriptor delegate(string name) queryParameters; //FIXME: should 
not be void*

	const(string[]) delegate() queryInstanceNames;
	QueryDescriptor delegate(string name) queryInstanceParameters; //FIXME: 
should not be void*
}

struct AReflectedModelInstance {
	private {
		AReflectedModel* model_;

		void* instance_;
	}

	AReflectedModel* model() { return model; }
	void* instance() { return instance; }

	/*
	 *
	 * Shouldn't everything below this, stored in the model instead of the 
state?
	 *
	 */

	bool delegate() isValid;

	/*
	 * Get/Set for all approved properties given a name and the value for a 
model instance
	 */
	ModelValidTypes* delegate(string name) get;
	void delegate(string name, ModelValidTypes* value) set;

	ModelValidTypes[] delegate(string func, ModelValidTypes[] values...) query;
}

private {
	OrmActualPropertyTypes actualTypeFromType(T)() {
		static if (is(T == ubyte))
			return OrmActualPropertyTypes.UByte;
		else static if (is(T == byte))
			return OrmActualPropertyTypes.Byte;
		else static if (is(T == ushort))
			return OrmActualPropertyTypes.UShort;
		else static if (is(T == short))
			return OrmActualPropertyTypes.Short;
		else static if (is(T == uint))
			return OrmActualPropertyTypes.UInt;
		else static if (is(T == int))
			return OrmActualPropertyTypes.Int;
		else static if (is(T == ulong))
			return OrmActualPropertyTypes.ULong;
		else static if (is(T == long))
			return OrmActualPropertyTypes.Long;
		else static if (is(T == float))
			return OrmActualPropertyTypes.Float;
		else static if (is(T == double))
			return OrmActualPropertyTypes.Double;
		else static if (is(T == string))
			return OrmActualPropertyTypes.String;
		else static if (is(T == wstring))
			return OrmActualPropertyTypes.WString;
		else static if (is(T == dstring))
			return OrmActualPropertyTypes.DString;
		else static if (is(T == class) || is(T == struct))
			return OrmActualPropertyTypes.DataModel;
		else static if (isArray!T)
			return OrmActualPropertyTypes.Array;
		else
			return OrmActualPropertyTypes.Unknown;
	}

	string getCallToMethodSyntaxVarient(string valuesName, string argName, 
ARGS...)() pure {
		import std.conv : text;
		string ret;

		foreach(i, ARG; ARGS) {
			string TI = text(i);
			ret ~= valuesName ~ "[" ~ TI ~ "].get!(" ~ argName ~ "[" ~ TI ~ "]), ";
		}

		if (ARGS.length > 0)
			ret.length -= 2;

		return ret;
	}
}


More information about the Digitalmars-d mailing list