<commit_before>'use strict';

var Table = require('./table');
var inflection = require('inflection');
var utils = require('./../../helpers/utils');
var Property = require('./property');
var Q = require('q');
var crypto = require('crypto');

function toModelName(modelNameOrModel) {
	var modelName = '';
	if(typeof modelNameOrModel == 'string') {
		modelName = modelNameOrModel;
	}
	else {
		modelName = modelNameOrModel.getName();
	}
	return modelName;
}

/**
 * @constructor PropertyTypes
**/
var propertyTypes = {
	/**
	 * Sets the property's data type to `TEXT`. See {@link PropertyTypes#String} which is preferred as it's a less database-specific term.
	 *
	 * @name PropertyTypes#Text
	 */
	Text: function() {
		return Table.keywords.Text;
	},

	/**
	 * Sets the property's data type to `TEXT`.
	 *
	 * @name PropertyTypes#Text
	 */
	String: function() {
		return Table.keywords.Text;
	},

	/**
	 * Sets the property's data type to `INTEGER`.
	 *
	 * @name PropertyTypes#Number
	 */
	Number: function() {
		return Table.keywords.Integer;
	},

	/**
	 * Sets the property's data type to `INTEGER`. This is a synonym of {@link PropertyTypes#Number}.
	 *
	 * @name PropertyTypes#Integer
	 */
	Integer: function() {
		return Table.keywords.Integer;
	},

	/**
	 * Sets the property's data type to `BOOLEAN`.
	 *
	 * @name PropertyTypes#Boolean
	 */
	Boolean: function() {
		return Table.keywords.Boolean;
	},

	/**
	 * Sets the property's data type to `DATE`.
	 *
	 * This only stores the date. Use {@link PropertyTypes#DateTime} if you want to include time as well.
	 *
	 * @name PropertyTypes#Date.
	 */
	Date: function() {
		return Table.keywords.Date;
	},

	/**
	 * Sets the property's data type to `TIMESTAMP WITHOUT TIME ZONE`.
	 *
	 * @name PropertyTypes#DateTime
	 */
	DateTime: function DateTime() {
		return Table.keywords.Timestamp;
	},

	/**
	* Sets the property's data type to `TIMESTAMP WITHOUT TIME ZONE`.
	*
	* @name PropertyTypes#Timestamp
	*/
	Timestamp: function() {
		return Table.keywords.Timestamp;
	},

	/**
	 * Sets the property's data type to `TIME WITH TIME ZONE`.
	 *
	 * @name PropertyTypes#Time
	 */
	Time: function() {
		return Table.keywords.Time;
	},

	/**
	 * Sets the property's data type to `INTERVAL`.
	 *
	 * See {@link http://www.postgresql.org/docs/9.1/static/datatype-datetime.html}.
	 *
	 * @name PropertyTypes#Interval
	 */
	Interval: function() {
		return Table.keywords.Interval;
	},

	/**
	 * Sets the property to `UNSIGNED`. This can be used in combination with e.g. {@link PropertyTypes#Number}.
	 *
	 *
	 */
	Unsigned: function() {
		return {
			clause: Table.keywords.Unsigned
		};
	},

	/**
	 * Sets the property's data type to `SERIAL`, see {@link http://www.postgresql.org/docs/9.3/static/datatype-numeric.html#DATATYPE-SERIAL}.
	 *
	 *
	 */
	Serial: function() {
		return Table.keywords.Serial;
	},

	/**
	 * Defines the property as primary key on the model.
	 *
	 *
	 */
	PrimaryKey: function() {
		return {
			clause: Table.keywords.PrimaryKey
		};
	},

	/**
	 * Defines the property as `UNIQUE`.
	 *
	 *
	 */
	Unique: function() {
		return {
			clause: Table.keywords.Unique
		};
	},

	/**
	 * Sets the property as `NOT NULL`.
	 */
	Required: function() {
		return function(property) {
			property.options.required = true;

			// TODO: We should overhaul this system: we shouldn't be adding clauses, they should be configured per property
			// This allows us to easily set different properties on relating properties
			// TODO: Set required on any relationships

			return {
				clause: Table.keywords.NotNull
			};
		};
	},

	/**
	 * Creates a virtual read-only property.
	 *
	 * This allows you to create a static value, or calculated value based on other properties, without actually storing it.
	 *
	 * `readOnly` should be an SQLish string, see {@link Table#parseSQLishStatement} for more information on SQLish.
	 *
	 * For example:
	 * ```
	 * function Book() {
	 * 	this.title = [this.String];
	 * 	this.titleLength = [this.ReadOnly('LENGTH($title) * 2')];
	 * }
	 * ```
	 * This creates a book model with a `title` property and a `titleLength` property which returns the length of the title times two.
	 *
	 * A more advanced example:
	 * ```
	 * function Article() {
	 * 	this.title 			= [this.String, this.Required];
	 *  this.url 			= [this.String, this.Required, this.CanUpdate(false), this.Unique];
	 *  this.createdAt 		= [this.DateTime, this.Default('CURRENT_TIMESTAMP')];
	 *  this.author 		= [this.BelongsTo(this.models.User, 'articles'), this.Automatic, this.Required, this.AutoFetch];
	 *  this.voters 		= [this.HasMany(this.models.User, 'votes'), this.AutoFetch];
	 *  this.position 		= [this.ReadOnly('($count("voters") - 1) / ((EXTRACT(EPOCH FROM current_timestamp - $createdAt) / 3600) + 2)^1.8')];
	 * }
	 * app.model(Article);
	 * ```
	 *
	 * In the future this method will be extended so you can also provide a function to return a read-only value.
	 *
	 * @param {String|Function } readOnly SQLish statement.
	 */
	ReadOnly: function(readOnly) {
		return function(property) {
			property.options.isVirtual = true;
			property.options.readOnly = readOnly;
		};
	},

	/**
	 * Defines the property as `SERIAL PRIMARY KEY`.
	 *
	 * Please note that by default a `id` property is created on every model. The default data type is {@link PropertyTypes#UUID}.
	 *
	 *
	 */
	Id: function() {
		return Table.keywords.Id;
	},

	/**
	 * Defines the property as `UUID PRIMARY KEY DEFAULT uuid_generate_v4()`.
	 *
	 * UUID is the default data type for the automatic `id` property. UUID requires the `uuid-ossp` extension, see {@link Datastore#setup}.
	 */
	UUID: function() {
		return Table.keywords.UUID;
	},

	/**
	 * Sets the default value of the property.
	 *
	 * You can either provide a string which will be evaluated in the SQL statement, or provide a function, which will be invoked when a new model instance is created.
	 *
	 * The below example sets the `createdAt`'s default value to the current time stamp (via SQL statement).
	 * ```
	 * function Article() {
	 * 	this.createdAt = [this.DateTime, this.Default('CURRENT_TIMESTAMP')];
	 * }
	 * ```
	 *
	 * The below example also sets the `createdAt`'s default value to the current time stamp (via a function).
	 * ```
	 * function Article() {
	 * 	this.createdAt = [this.DateTime, this.Default(function() {
	 * 		return new Date();
	 * 	})];
	 * }
	 * ```
	 *
	 * Please note that you can even return a promise in the case of a function. The default value will become the value the promise resolves to.
	 *
	 * @param {String|Function} defaultValue The default value, either an SQL string or a function returning a value.
	 *
	 * @name PropertyTypes#Default
	 * @function
	 */
	Default: function(defaultValue) {
		if(typeof defaultValue == 'function') {
			return function(property) {
				property.options.defaultValue = defaultValue;
			};
		}
		else {
			return {
				clause: Table.keywords.Default(defaultValue)
			};
		}
	},

	/**
	 * Hashes a property's value before it's inserted in the database or it's searched for in a where map, see {@link Model#find}.
	 *
	 * This is especially useful if you, for example, want to hash a user's password before it's inserted into the database. For example:
	 * ```
	 * var crypto = require('crypto');
	 *
	 * function User() {
	 * 	this.email = [this.String];
	 * 	this.password = [this.String, this.Required, this.Hash(function(password) {
	 * 		var hash = crypto.createHash('sha512');
	 * 		hash.update(password);
	 * 		return hash.digest('hex');
	 * 	})];
	 * }
	 * app.model(User);
	 * ```
	 *
	 * This also hashes any values in the where map, for example, if you would query for the user:
	 * ```
	 * this.models.User.findOne({
	 * 	email: '<EMAIL>',
	 * 	password: '<PASSWORD>'
	 * });
	 * ```
	 * This automatically hashes `"test"` as well.
	 *
	 * Please note, if you want to implement authentication, you are advised to use {@link PropertyTypes#Authenticate} instead.
	 *
	 * @param {Function} method A function taking 1 argument returning it's hash.
	 * @name PropertyTypes#Hash
	 * @function
	 */
	Hash: function(method) {
		return function(property) {
			property.options.hashMethod = method;
		};
	},

	/**
	 * Set this on an association to automatically fetch all instances when a model instance is retrieved.
	 *
	 * If an association is auto-fetched, it's directly available as property on the model instance.
	 *
	 * @name PropertyTypes#AutoFetch
	 * @function
	 */
	AutoFetch: function() {
		return function(property) {
			property.options.autoFetch = true;
		};
	},

	/**
	 * Sets a property as virtual. Virtual properties are not stored in the database.
	 *
	 * @param {Boolean} value True if virtual, false if not. Defaults to true.
	 */
	Virtual: function(value) {
		return function(property) {
			property.options.isVirtual = value || true;
		};
	},

	/**
	 * Creates one or more virtual properties and transforms all values to one value.
	 *
	 * ```
	 * function Test() {
	 * 	this.three = [this.Transform(one, two) {
	 * 		return (one * two);
	 * 	}];
	 * }
	 * app.model(Test);
	 *
	 * //
	 *
	 * models.Test.create({
	 * 		one: 3,
	 * 		two: 6
	 *  })
	 * 	.then(function(test) {
	 * 		// test.three is 18
	 *  });
	 * ```
	 *
	 * The transform method is not invoked if any of the transform parameters is undefined. For example, the below example doesn't set `three`.
	 * ```
	 * models.Test.create({
	 * 	one: 3
	 * })
	 * .then(function(test) {
	 * 	// test.three is not set via the transform method (thus it's the default value)
	 * })
	 * ```
	 *
	 * @param {Function} method The transform method. The property's created are based on this method's arguments.
	 *
	 * @name PropertyTypes#Transform
	 * @function
	 */
	Transform: function(method) {
		return function(property) {
			property.options.transformMethod = method;
			property.options.transformKeyNames = utils.getMethodArgumentNames(method);

			var model = property.model;
			for(var i = 0, il = property.options.transformKeyNames.length; i < il; i++) {
				var key = property.options.transformKeyNames[i];

				model.addProperty(new Property(key, [model.Virtual], model, model.models));
			}
		};
	},

	/**
	 * Creates a virtual property which allows you to transform the where clause.
	 *
	 * For example, you can create a `popular` select property and when querying popular articles, only return the articles with more than ten comments.
	 *
	 * ```
	 * function Article() {
	 * 	this.title = [this.String];
	 * 	this.numberOfComments = [this.Integer];
	 * 	this.popular = [this.Select(function(popular) {
	 * 		if(popular) {
	 * 			return {
	 * 				numberOfComments: {
	 * 					$gt: 10
	 * 				};
	 * 			};
	 * 		}
	 * 	});
	 * }
	 * app.model(Article);
	 *
	 * //
	 *
	 * models.Article.find({popular: true})
	 * 	.then(function(popularArticles) {
	 * 		// Do something with the popular articles.
	 * 	});
	 * ```
	 *
	 * @param {Function} method The select method.
	 *
	 * @name PropertyTypes#Select
	 * @function
	 */
	Select: function(method) {
		return function(property) {
			property.options.selectMethod = method;
			property.options.isVirtual = true;
		};
	},

	/**
	 * This property type is part of the association methods. Use this model in a one-to-one or one-to-many association.
	 *
	 * The below example shows how to configure a one-to-one association:
	 * ```
	 * function User() {
	 * 	this.name = [this.String];
	 * 	this.address = [this.HasOne(this.models.UserAddress)]
	 * }
	 * app.model(User);
	 *
	 * function UserAddress() {
	 * 	this.user = [this.BelongsTo(this.models.User)];
	 * 	this.line1 = [this.String];
	 * 	this.line2 = [this.String];
	 * }
	 * app.model(UserAddress);
	 * ```
	 *
	 *
	 * @param {Model|String} modelNameOrModel   The associated model.
	 * @param {String} linkedPropertyName If multiple associations on the target model to this model exists,
	 */
	BelongsTo: function(modelNameOrModel, linkedPropertyName) {
		if(modelNameOrModel) {
			return function(property) {
				property.columnName 			= property.columnName + '_id';
				property.options.referenceName 	= toModelName(modelNameOrModel);
				property.options.belongsTo 		= true;

				if(linkedPropertyName) {
					property.options.linkedPropertyName = linkedPropertyName;
				}

				var associatedModel = property.getAssociatedModel();
				if(associatedModel) {
					// Now, find any associations which reference this model
					var associations = associatedModel.findAssociationsTo(property.model, linkedPropertyName);

					if(associations.length > 1) {
						throw new Error('Multiple associations to `' + property.model.getName() + '` exists on `' + associatedModel.getName() + '`.');
					}
					else if(associations.length == 1) {
						var associatedProperty = associations[0];

						property.options.belongsTo = associatedProperty.name;

						if(associatedProperty.options.hasMany) {
							associatedProperty.options.hasMany = property.name;
						}
						else {
							// TODO: Set hasOne?
						}

						associatedProperty.options.relationshipVia = property;
						property.options.relationshipVia = associatedProperty;
					}
				}

				if(associatedModel) {
					return {
						index: 999,
						clause: Table.keywords.References(associatedModel.getTable().name),
						dataType: 'UUID'
					};
				}
				else {
					return {
						index: 999,
						clause: Table.keywords.References(inflection.tableize(property.options.referenceName)),
						dataType: 'UUID'
					};
				}
			};
		}

		return null;
	},

	Has: function(hasModel, hasMethod) {
		return function(property) {
			property.options.isVirtual = true;
			property.options.hasMethod = hasMethod;
			property.options.hasModel = hasModel;
		};
	},

	HasMany: function(modelNameOrModel, linkedPropertyName) {
		// `modelNameOrModel` may be falsy. Likely during a soft migration. Do not throw an error here.
		if(modelNameOrModel) {
			return function(property) {
				// Set reference to the current model
				property.options.referenceName 	= toModelName(modelNameOrModel);

				if(linkedPropertyName) {
					property.options.linkedPropertyName = linkedPropertyName;
				}

				// Now let's check if this is a many-to-many reference
				var associatedModel = property.getAssociatedModel();
				var associatedProperty = null;

				if(associatedModel) {
					// Now, find any associations which reference this model
					var associations = associatedModel.findAssociationsTo(property.model, linkedPropertyName);

					if(associations.length > 1) {
						throw new Error('Multiple associations to `' + property.model.getName() + '` exists on `' + associatedModel.getName() + '`.');
					}
					else if(associations.length == 1) {
						associatedProperty = associations[0];

						// TODO: should we set name, or something else?
						property.options.hasMany = associatedProperty.name;
					}
					else {
						// Could not find an association
						// ... this could be part of a one-to-many association
						// so let's just set:
						property.options.hasMany = inflection.camelize(property.model.getName(), true);
					}

					if(associatedProperty) {
						if(associatedProperty.options.hasMany) {
							// Now we link many-to-many associatedModel and property.model

							// We sort the names, to make sure we always generate the same table.

							var propertyNames = [
								inflection.camelize(property.model.getName()) + utils.ucfirst(inflection.singularize(property.name)),
								inflection.camelize(associatedModel.getName()) + utils.ucfirst(inflection.singularize(associatedProperty.name))
							];


							property.options.throughPropertyName 			= utils.lcfirst(propertyNames[0]);
							associatedProperty.options.throughPropertyName 	= utils.lcfirst(propertyNames[1]);

							var names = [propertyNames[0], propertyNames[1]];
							names.sort();
							var name = names[0] + names[1];

							var properties = {};
							properties.id = [];
							properties[property.options.throughPropertyName] = [property.model.BelongsTo(property.model)];
							properties[associatedProperty.options.throughPropertyName] = [associatedModel.BelongsTo(associatedModel)];

							var model = property.model.models.findModel(name);

							// findModel may return a string for the forward reference methods.
							if(!model || typeof model != 'object') {
								model = property.model.models.createModel(name, properties);
							}

							// .. and let's also set the options.hasMany thingy correctly on both properties
							associatedProperty.options.hasMany = property.name;

							// Through is the model which connects the two relationships
							associatedProperty.options.through = model;
							property.options.through = model;
						}
						else if(associatedProperty.options.belongsTo) {
							associatedProperty.options.belongsTo = property.name;

							// TODO: Set the columnName?
						}
						else {
							// ... any other cases we need to cover?
						}

						// RelationshipVia is the property of the other model
						associatedProperty.options.relationshipVia 	= property;
						property.options.relationshipVia 			= associatedProperty;
					}
				}
				else {
					// Couldn't find the associated model yet, so we can't know the right property
					property.options.hasMany = true;
				}
			};
		}

		return null;
	},

	HasOne: function(modelNameOrModel, linkedPropertyName) {
		if(modelNameOrModel) {
			return function(property) {
				property.options.referenceName 	= toModelName(modelNameOrModel);
				property.options.hasOne 		= inflection.camelize(property.model.getName(), true);

				if(linkedPropertyName) {
					property.options.linkedPropertyName = linkedPropertyName;
				}

				var associatedModel = property.getAssociatedModel();
				if(associatedModel) {
					var associations = associatedModel.findAssociationsTo(property.model, linkedPropertyName);

					if(associations.length > 1) {
						throw new Error('Multiple associations to `' + property.model.getName() + '` exists on `' + associatedModel.getName() + '`.');
					}
					else if(associations.length == 1) {
						var associatedProperty = associations[0];

						// If this is a hasMany, let's set the correct name
						if(associatedProperty.options.hasMany) {
							associatedProperty.options.hasMany = property.name;
						}
						else if(associatedProperty.options.belongsTo) {
							associatedProperty.options.belongsTo = property.name;
							associatedProperty.columnName = associatedProperty.name + '_id';
						}
						else {
							// TODO: Anything we want to do here?
						}

						associatedProperty.options.relationshipVia = property;
						property.options.relationshipVia = associatedProperty;
					}
				}
			};
		}

		return null;
	},

	Count: function(propertyName) {
		return function(property) {
			property.options.isVirtual = true;

			var targetProperty = property.model.getProperty(propertyName);
			if(!targetProperty) {
				throw new Error('Cannot find property with name `' + propertyName + '`.');
			}

			// The many-to-many relation likely does not exist yet.
			property.options.counting = propertyName;
		};
	},

	/**
	 * Wut.
	 *
	 * @name PropertyTypes#Authenticate
	 */
	Authenticate: function() {
		return function(property) {
			var createRandomTokenFunction = function() {
				var defer = Q.defer();
				crypto.randomBytes(128, function(error, buffer) {
					if(error) {
						defer.reject(error);
					}
					else {
						defer.resolve(buffer.toString('hex'));
					}
				});
				return defer.promise;
			};

			var model = property.model;

			model.options.authenticatingProperty = property;
			property.options.authenticate = true;

			var resetPasswordModel = model.models.createModel(model.getName() + 'ResetPassword', {
				authenticator: [model.BelongsTo(model), model.Required],
				token: [model.String, model.Default(createRandomTokenFunction), model.Required]
			});
			resetPasswordModel.disableAutomaticModelController = true;

			model.addProperty(new Property('password', [model.String, model.Required, model.Private, model.Hash(function(value) {
				if(value) {
					var hash = crypto.createHash('sha512');
					hash.update(value);
					return hash.digest('hex');
				}
				else {
					throw new Error('No password provided');
				}
			})], model, model.models), true);

			model.addProperty(new Property('accessToken', [model.String, model.Default(createRandomTokenFunction)], model, model.models), true);
			model.addProperty(new Property('resetPassword', [model.HasOne(resetPasswordModel)], model, model.models), true);
		};
	},

	Private: function() {
		return function(property) {<commit_msg>disables updating of accessToken through api<commit_after>'use strict';

var Table = require('./table');
var inflection = require('inflection');
var utils = require('./../../helpers/utils');
var Property = require('./property');
var Q = require('q');
var crypto = require('crypto');

function toModelName(modelNameOrModel) {
	var modelName = '';
	if(typeof modelNameOrModel == 'string') {
		modelName = modelNameOrModel;
	}
	else {
		modelName = modelNameOrModel.getName();
	}
	return modelName;
}

/**
 * @constructor PropertyTypes
**/
var propertyTypes = {
	/**
	 * Sets the property's data type to `TEXT`. See {@link PropertyTypes#String} which is preferred as it's a less database-specific term.
	 *
	 * @name PropertyTypes#Text
	 */
	Text: function() {
		return Table.keywords.Text;
	},

	/**
	 * Sets the property's data type to `TEXT`.
	 *
	 * @name PropertyTypes#Text
	 */
	String: function() {
		return Table.keywords.Text;
	},

	/**
	 * Sets the property's data type to `INTEGER`.
	 *
	 * @name PropertyTypes#Number
	 */
	Number: function() {
		return Table.keywords.Integer;
	},

	/**
	 * Sets the property's data type to `INTEGER`. This is a synonym of {@link PropertyTypes#Number}.
	 *
	 * @name PropertyTypes#Integer
	 */
	Integer: function() {
		return Table.keywords.Integer;
	},

	/**
	 * Sets the property's data type to `BOOLEAN`.
	 *
	 * @name PropertyTypes#Boolean
	 */
	Boolean: function() {
		return Table.keywords.Boolean;
	},

	/**
	 * Sets the property's data type to `DATE`.
	 *
	 * This only stores the date. Use {@link PropertyTypes#DateTime} if you want to include time as well.
	 *
	 * @name PropertyTypes#Date.
	 */
	Date: function() {
		return Table.keywords.Date;
	},

	/**
	 * Sets the property's data type to `TIMESTAMP WITHOUT TIME ZONE`.
	 *
	 * @name PropertyTypes#DateTime
	 */
	DateTime: function DateTime() {
		return Table.keywords.Timestamp;
	},

	/**
	* Sets the property's data type to `TIMESTAMP WITHOUT TIME ZONE`.
	*
	* @name PropertyTypes#Timestamp
	*/
	Timestamp: function() {
		return Table.keywords.Timestamp;
	},

	/**
	 * Sets the property's data type to `TIME WITH TIME ZONE`.
	 *
	 * @name PropertyTypes#Time
	 */
	Time: function() {
		return Table.keywords.Time;
	},

	/**
	 * Sets the property's data type to `INTERVAL`.
	 *
	 * See {@link http://www.postgresql.org/docs/9.1/static/datatype-datetime.html}.
	 *
	 * @name PropertyTypes#Interval
	 */
	Interval: function() {
		return Table.keywords.Interval;
	},

	/**
	 * Sets the property to `UNSIGNED`. This can be used in combination with e.g. {@link PropertyTypes#Number}.
	 *
	 *
	 */
	Unsigned: function() {
		return {
			clause: Table.keywords.Unsigned
		};
	},

	/**
	 * Sets the property's data type to `SERIAL`, see {@link http://www.postgresql.org/docs/9.3/static/datatype-numeric.html#DATATYPE-SERIAL}.
	 *
	 *
	 */
	Serial: function() {
		return Table.keywords.Serial;
	},

	/**
	 * Defines the property as primary key on the model.
	 *
	 *
	 */
	PrimaryKey: function() {
		return {
			clause: Table.keywords.PrimaryKey
		};
	},

	/**
	 * Defines the property as `UNIQUE`.
	 *
	 *
	 */
	Unique: function() {
		return {
			clause: Table.keywords.Unique
		};
	},

	/**
	 * Sets the property as `NOT NULL`.
	 */
	Required: function() {
		return function(property) {
			property.options.required = true;

			// TODO: We should overhaul this system: we shouldn't be adding clauses, they should be configured per property
			// This allows us to easily set different properties on relating properties
			// TODO: Set required on any relationships

			return {
				clause: Table.keywords.NotNull
			};
		};
	},

	/**
	 * Creates a virtual read-only property.
	 *
	 * This allows you to create a static value, or calculated value based on other properties, without actually storing it.
	 *
	 * `readOnly` should be an SQLish string, see {@link Table#parseSQLishStatement} for more information on SQLish.
	 *
	 * For example:
	 * ```
	 * function Book() {
	 * 	this.title = [this.String];
	 * 	this.titleLength = [this.ReadOnly('LENGTH($title) * 2')];
	 * }
	 * ```
	 * This creates a book model with a `title` property and a `titleLength` property which returns the length of the title times two.
	 *
	 * A more advanced example:
	 * ```
	 * function Article() {
	 * 	this.title 			= [this.String, this.Required];
	 *  this.url 			= [this.String, this.Required, this.CanUpdate(false), this.Unique];
	 *  this.createdAt 		= [this.DateTime, this.Default('CURRENT_TIMESTAMP')];
	 *  this.author 		= [this.BelongsTo(this.models.User, 'articles'), this.Automatic, this.Required, this.AutoFetch];
	 *  this.voters 		= [this.HasMany(this.models.User, 'votes'), this.AutoFetch];
	 *  this.position 		= [this.ReadOnly('($count("voters") - 1) / ((EXTRACT(EPOCH FROM current_timestamp - $createdAt) / 3600) + 2)^1.8')];
	 * }
	 * app.model(Article);
	 * ```
	 *
	 * In the future this method will be extended so you can also provide a function to return a read-only value.
	 *
	 * @param {String|Function } readOnly SQLish statement.
	 */
	ReadOnly: function(readOnly) {
		return function(property) {
			property.options.isVirtual = true;
			property.options.readOnly = readOnly;
		};
	},

	/**
	 * Defines the property as `SERIAL PRIMARY KEY`.
	 *
	 * Please note that by default a `id` property is created on every model. The default data type is {@link PropertyTypes#UUID}.
	 *
	 *
	 */
	Id: function() {
		return Table.keywords.Id;
	},

	/**
	 * Defines the property as `UUID PRIMARY KEY DEFAULT uuid_generate_v4()`.
	 *
	 * UUID is the default data type for the automatic `id` property. UUID requires the `uuid-ossp` extension, see {@link Datastore#setup}.
	 */
	UUID: function() {
		return Table.keywords.UUID;
	},

	/**
	 * Sets the default value of the property.
	 *
	 * You can either provide a string which will be evaluated in the SQL statement, or provide a function, which will be invoked when a new model instance is created.
	 *
	 * The below example sets the `createdAt`'s default value to the current time stamp (via SQL statement).
	 * ```
	 * function Article() {
	 * 	this.createdAt = [this.DateTime, this.Default('CURRENT_TIMESTAMP')];
	 * }
	 * ```
	 *
	 * The below example also sets the `createdAt`'s default value to the current time stamp (via a function).
	 * ```
	 * function Article() {
	 * 	this.createdAt = [this.DateTime, this.Default(function() {
	 * 		return new Date();
	 * 	})];
	 * }
	 * ```
	 *
	 * Please note that you can even return a promise in the case of a function. The default value will become the value the promise resolves to.
	 *
	 * @param {String|Function} defaultValue The default value, either an SQL string or a function returning a value.
	 *
	 * @name PropertyTypes#Default
	 * @function
	 */
	Default: function(defaultValue) {
		if(typeof defaultValue == 'function') {
			return function(property) {
				property.options.defaultValue = defaultValue;
			};
		}
		else {
			return {
				clause: Table.keywords.Default(defaultValue)
			};
		}
	},

	/**
	 * Hashes a property's value before it's inserted in the database or it's searched for in a where map, see {@link Model#find}.
	 *
	 * This is especially useful if you, for example, want to hash a user's password before it's inserted into the database. For example:
	 * ```
	 * var crypto = require('crypto');
	 *
	 * function User() {
	 * 	this.email = [this.String];
	 * 	this.password = [this.String, this.Required, this.Hash(function(password) {
	 * 		var hash = crypto.createHash('sha512');
	 * 		hash.update(password);
	 * 		return hash.digest('hex');
	 * 	})];
	 * }
	 * app.model(User);
	 * ```
	 *
	 * This also hashes any values in the where map, for example, if you would query for the user:
	 * ```
	 * this.models.User.findOne({
	 * 	email: '<EMAIL>',
	 * 	password: '<PASSWORD>'
	 * });
	 * ```
	 * This automatically hashes `"test"` as well.
	 *
	 * Please note, if you want to implement authentication, you are advised to use {@link PropertyTypes#Authenticate} instead.
	 *
	 * @param {Function} method A function taking 1 argument returning it's hash.
	 * @name PropertyTypes#Hash
	 * @function
	 */
	Hash: function(method) {
		return function(property) {
			property.options.hashMethod = method;
		};
	},

	/**
	 * Set this on an association to automatically fetch all instances when a model instance is retrieved.
	 *
	 * If an association is auto-fetched, it's directly available as property on the model instance.
	 *
	 * @name PropertyTypes#AutoFetch
	 * @function
	 */
	AutoFetch: function() {
		return function(property) {
			property.options.autoFetch = true;
		};
	},

	/**
	 * Sets a property as virtual. Virtual properties are not stored in the database.
	 *
	 * @param {Boolean} value True if virtual, false if not. Defaults to true.
	 */
	Virtual: function(value) {
		return function(property) {
			property.options.isVirtual = value || true;
		};
	},

	/**
	 * Creates one or more virtual properties and transforms all values to one value.
	 *
	 * ```
	 * function Test() {
	 * 	this.three = [this.Transform(one, two) {
	 * 		return (one * two);
	 * 	}];
	 * }
	 * app.model(Test);
	 *
	 * //
	 *
	 * models.Test.create({
	 * 		one: 3,
	 * 		two: 6
	 *  })
	 * 	.then(function(test) {
	 * 		// test.three is 18
	 *  });
	 * ```
	 *
	 * The transform method is not invoked if any of the transform parameters is undefined. For example, the below example doesn't set `three`.
	 * ```
	 * models.Test.create({
	 * 	one: 3
	 * })
	 * .then(function(test) {
	 * 	// test.three is not set via the transform method (thus it's the default value)
	 * })
	 * ```
	 *
	 * @param {Function} method The transform method. The property's created are based on this method's arguments.
	 *
	 * @name PropertyTypes#Transform
	 * @function
	 */
	Transform: function(method) {
		return function(property) {
			property.options.transformMethod = method;
			property.options.transformKeyNames = utils.getMethodArgumentNames(method);

			var model = property.model;
			for(var i = 0, il = property.options.transformKeyNames.length; i < il; i++) {
				var key = property.options.transformKeyNames[i];

				model.addProperty(new Property(key, [model.Virtual], model, model.models));
			}
		};
	},

	/**
	 * Creates a virtual property which allows you to transform the where clause.
	 *
	 * For example, you can create a `popular` select property and when querying popular articles, only return the articles with more than ten comments.
	 *
	 * ```
	 * function Article() {
	 * 	this.title = [this.String];
	 * 	this.numberOfComments = [this.Integer];
	 * 	this.popular = [this.Select(function(popular) {
	 * 		if(popular) {
	 * 			return {
	 * 				numberOfComments: {
	 * 					$gt: 10
	 * 				};
	 * 			};
	 * 		}
	 * 	});
	 * }
	 * app.model(Article);
	 *
	 * //
	 *
	 * models.Article.find({popular: true})
	 * 	.then(function(popularArticles) {
	 * 		// Do something with the popular articles.
	 * 	});
	 * ```
	 *
	 * @param {Function} method The select method.
	 *
	 * @name PropertyTypes#Select
	 * @function
	 */
	Select: function(method) {
		return function(property) {
			property.options.selectMethod = method;
			property.options.isVirtual = true;
		};
	},

	/**
	 * This property type is part of the association methods. Use this model in a one-to-one or one-to-many association.
	 *
	 * The below example shows how to configure a one-to-one association:
	 * ```
	 * function User() {
	 * 	this.name = [this.String];
	 * 	this.address = [this.HasOne(this.models.UserAddress)]
	 * }
	 * app.model(User);
	 *
	 * function UserAddress() {
	 * 	this.user = [this.BelongsTo(this.models.User)];
	 * 	this.line1 = [this.String];
	 * 	this.line2 = [this.String];
	 * }
	 * app.model(UserAddress);
	 * ```
	 *
	 *
	 * @param {Model|String} modelNameOrModel   The associated model.
	 * @param {String} linkedPropertyName If multiple associations on the target model to this model exists,
	 */
	BelongsTo: function(modelNameOrModel, linkedPropertyName) {
		if(modelNameOrModel) {
			return function(property) {
				property.columnName 			= property.columnName + '_id';
				property.options.referenceName 	= toModelName(modelNameOrModel);
				property.options.belongsTo 		= true;

				if(linkedPropertyName) {
					property.options.linkedPropertyName = linkedPropertyName;
				}

				var associatedModel = property.getAssociatedModel();
				if(associatedModel) {
					// Now, find any associations which reference this model
					var associations = associatedModel.findAssociationsTo(property.model, linkedPropertyName);

					if(associations.length > 1) {
						throw new Error('Multiple associations to `' + property.model.getName() + '` exists on `' + associatedModel.getName() + '`.');
					}
					else if(associations.length == 1) {
						var associatedProperty = associations[0];

						property.options.belongsTo = associatedProperty.name;

						if(associatedProperty.options.hasMany) {
							associatedProperty.options.hasMany = property.name;
						}
						else {
							// TODO: Set hasOne?
						}

						associatedProperty.options.relationshipVia = property;
						property.options.relationshipVia = associatedProperty;
					}
				}

				if(associatedModel) {
					return {
						index: 999,
						clause: Table.keywords.References(associatedModel.getTable().name),
						dataType: 'UUID'
					};
				}
				else {
					return {
						index: 999,
						clause: Table.keywords.References(inflection.tableize(property.options.referenceName)),
						dataType: 'UUID'
					};
				}
			};
		}

		return null;
	},

	Has: function(hasModel, hasMethod) {
		return function(property) {
			property.options.isVirtual = true;
			property.options.hasMethod = hasMethod;
			property.options.hasModel = hasModel;
		};
	},

	HasMany: function(modelNameOrModel, linkedPropertyName) {
		// `modelNameOrModel` may be falsy. Likely during a soft migration. Do not throw an error here.
		if(modelNameOrModel) {
			return function(property) {
				// Set reference to the current model
				property.options.referenceName 	= toModelName(modelNameOrModel);

				if(linkedPropertyName) {
					property.options.linkedPropertyName = linkedPropertyName;
				}

				// Now let's check if this is a many-to-many reference
				var associatedModel = property.getAssociatedModel();
				var associatedProperty = null;

				if(associatedModel) {
					// Now, find any associations which reference this model
					var associations = associatedModel.findAssociationsTo(property.model, linkedPropertyName);

					if(associations.length > 1) {
						throw new Error('Multiple associations to `' + property.model.getName() + '` exists on `' + associatedModel.getName() + '`.');
					}
					else if(associations.length == 1) {
						associatedProperty = associations[0];

						// TODO: should we set name, or something else?
						property.options.hasMany = associatedProperty.name;
					}
					else {
						// Could not find an association
						// ... this could be part of a one-to-many association
						// so let's just set:
						property.options.hasMany = inflection.camelize(property.model.getName(), true);
					}

					if(associatedProperty) {
						if(associatedProperty.options.hasMany) {
							// Now we link many-to-many associatedModel and property.model

							// We sort the names, to make sure we always generate the same table.

							var propertyNames = [
								inflection.camelize(property.model.getName()) + utils.ucfirst(inflection.singularize(property.name)),
								inflection.camelize(associatedModel.getName()) + utils.ucfirst(inflection.singularize(associatedProperty.name))
							];


							property.options.throughPropertyName 			= utils.lcfirst(propertyNames[0]);
							associatedProperty.options.throughPropertyName 	= utils.lcfirst(propertyNames[1]);

							var names = [propertyNames[0], propertyNames[1]];
							names.sort();
							var name = names[0] + names[1];

							var properties = {};
							properties.id = [];
							properties[property.options.throughPropertyName] = [property.model.BelongsTo(property.model)];
							properties[associatedProperty.options.throughPropertyName] = [associatedModel.BelongsTo(associatedModel)];

							var model = property.model.models.findModel(name);

							// findModel may return a string for the forward reference methods.
							if(!model || typeof model != 'object') {
								model = property.model.models.createModel(name, properties);
							}

							// .. and let's also set the options.hasMany thingy correctly on both properties
							associatedProperty.options.hasMany = property.name;

							// Through is the model which connects the two relationships
							associatedProperty.options.through = model;
							property.options.through = model;
						}
						else if(associatedProperty.options.belongsTo) {
							associatedProperty.options.belongsTo = property.name;

							// TODO: Set the columnName?
						}
						else {
							// ... any other cases we need to cover?
						}

						// RelationshipVia is the property of the other model
						associatedProperty.options.relationshipVia 	= property;
						property.options.relationshipVia 			= associatedProperty;
					}
				}
				else {
					// Couldn't find the associated model yet, so we can't know the right property
					property.options.hasMany = true;
				}
			};
		}

		return null;
	},

	HasOne: function(modelNameOrModel, linkedPropertyName) {
		if(modelNameOrModel) {
			return function(property) {
				property.options.referenceName 	= toModelName(modelNameOrModel);
				property.options.hasOne 		= inflection.camelize(property.model.getName(), true);

				if(linkedPropertyName) {
					property.options.linkedPropertyName = linkedPropertyName;
				}

				var associatedModel = property.getAssociatedModel();
				if(associatedModel) {
					var associations = associatedModel.findAssociationsTo(property.model, linkedPropertyName);

					if(associations.length > 1) {
						throw new Error('Multiple associations to `' + property.model.getName() + '` exists on `' + associatedModel.getName() + '`.');
					}
					else if(associations.length == 1) {
						var associatedProperty = associations[0];

						// If this is a hasMany, let's set the correct name
						if(associatedProperty.options.hasMany) {
							associatedProperty.options.hasMany = property.name;
						}
						else if(associatedProperty.options.belongsTo) {
							associatedProperty.options.belongsTo = property.name;
							associatedProperty.columnName = associatedProperty.name + '_id';
						}
						else {
							// TODO: Anything we want to do here?
						}

						associatedProperty.options.relationshipVia = property;
						property.options.relationshipVia = associatedProperty;
					}
				}
			};
		}

		return null;
	},

	Count: function(propertyName) {
		return function(property) {
			property.options.isVirtual = true;

			var targetProperty = property.model.getProperty(propertyName);
			if(!targetProperty) {
				throw new Error('Cannot find property with name `' + propertyName + '`.');
			}

			// The many-to-many relation likely does not exist yet.
			property.options.counting = propertyName;
		};
	},

	/**
	 * Wut.
	 *
	 * @name PropertyTypes#Authenticate
	 */
	Authenticate: function() {
		return function(property) {
			var createRandomTokenFunction = function() {
				var defer = Q.defer();
				crypto.randomBytes(128, function(error, buffer) {
					if(error) {
						defer.reject(error);
					}
					else {
						defer.resolve(buffer.toString('hex'));
					}
				});
				return defer.promise;
			};

			var model = property.model;

			model.options.authenticatingProperty = property;
			property.options.authenticate = true;

			var resetPasswordModel = model.models.createModel(model.getName() + 'ResetPassword', {
				authenticator: [model.BelongsTo(model), model.Required],
				token: [model.String, model.Default(createRandomTokenFunction), model.Required]
			});
			resetPasswordModel.disableAutomaticModelController = true;

			model.addProperty(new Property('password', [model.String, model.Required, model.Private, model.Hash(function(value) {
				if(value) {
					var hash = crypto.createHash('sha512');
					hash.update(value);
					return hash.digest('hex');
				}
				else {
					throw new Error('No password provided');
				}
			})], model, model.models), true);

			model.addProperty(new Property('accessToken', [model.String, model.Default(createRandomTokenFunction), model.CanUpdate(false)], model, model.models), true);
			model.addProperty(new Property('resetPassword', [model.HasOne(resetPasswordModel)], model, model.models), true);
		};
	},

	Private: function() {
		return function(property) {
{'fraction_non_alphanumeric': 0.12550558260069267, 'fraction_numerical': 0.0026641478359853716, 'mean_word_length': 3.156432454197705, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3501811', 'n_tokens_mistral': 14673, 'n_tokens_neox': 11974, 'n_words': 3278}