A no-frills active record implementation for node-mysql.
npm install quell View on GitHub Report an Issue
var mysql = require('mysql');
var pool = mysql.createPool({ /* MySQL Connection Settings */});
var quell = require('quell');
var Book = quell('books', { connection: pool });
var Author = quell('authors', { connection: pool });
var tperry = new Author({
firstname: 'Terry',
lastname: 'Pratchett'
});
var nightWatch = new Book({
title: 'Night Watch'
});
tperry.save(function () {
nightWatch.set('authorid', tperry.get('id'));
nightWatch.save();
});
Creates a Model constructor, using provided the tablename and/or prototype, for
creating records representing rows in the table.
All properties of the options object will be mixed into to the model's prototype.
// Create a plain model for the users table that loads its schema
// from the database on first use.
var User = quell('users');
// Alternative syntax for defining the table name, with a prototype method.
var User = quell({
tablename: 'users',
checkPassword: function (password) {
// ...
}
});
// Create a model for the user table with a predefined expected schema and
// couple member functions. Quell will not attempt to fetch the table schema
// if a valid definition exists on the prototype.
var User = quell('users', {
schema: {
columns: {
id: quell.INT({unsigned: true}),
email: quell.VARCHAR(255)
fullname: quell.TINYTEXT(),
enabled: quell.BOOLEAN()
},
autoincrement: 'id',
primaries: ['id']
},
enable: function () {
this.set('enabled', true);
return this;
},
disable: function () {
this.set('enabled', false);
return this;
}
});
var quell = function (tablename, options) {
if (typeof tablename === 'object') {
options = tablename;
} else {
options = options || {};
options.tablename = tablename;
}
if (!options.tablename || typeof options.tablename !== 'string') { throw new TypeError('Tablename must be a string.'); }
var model = function () {
if (!this.connection) {
this.connection = model.prototype.connection || options.connection || quell.connection || false;
}
modelBase.apply(this, arguments);
};
// Copy over the Model members
Object.assign(model, modelBase);
// Create the new Model prototype
model.prototype = Object.create(modelBase.prototype);
// Apply any overrides
Object.assign(model.prototype, options);
model.prototype.tablename = model.tablename = options.tablename;
model.connection = model.prototype.connection = options.connection || quell.connection || false;
return model;
};
Object.assign(quell, types);
module.exports = exports = quell;
Model constructor used to create a new record.
Takes the default data contents of the model.
var User = quell('users')
var userRecord = new User();
function modelBase (data, options) {
data = data || {};
options = options || {};
if (options.connection) {
this.connection = options.connection;
} else if (quell.connection) {
this.connection = quell.connection;
}
this.data = {};
this.set(data, options);
this.changed = {};
EventEmitter.call(this);
this.initialize.apply(this, arguments);
};
quell._model = modelBase;
util.inherits(modelBase, EventEmitter);
The raw model data.
data: null,
Indicates if the record already exists in the database. Will be null if existence is unknown.
exists: null,
Function called at model initialization. Abstract method intended to be overridden during
model creation, not intended to be called directly.
Receives all arguments passed to new Model()
.
var User = quell({
initialize: function (data, options) {
console.log('User record instance created', data);
}
});
var user = new User({id: 200});
// Console:
// "User record instance created", {"id": 200}
Returns a copy of the model's data
toJSON () {
return clone(this.data);
},
Gets the current value of a column from the Record.
get (field, formatted) {
// default to formatted unless the user passed false
if ((formatted || formatted === undefined) && this.schema && this.schema.columns && this.schema.columns[field]) {
return this.schema.columns[field].format(this.data[field]);
}
return this.data[field];
},
Set a hash of attributes (one or many) on the model.
If any of the attributes change the model's state, a "change" event will be triggered
on the model. Change events for specific attributes are also triggered, and you can bind
to those as well, for example: change:title, and change:content. You may also pass
individual keys and values.
set (field, value, options) {
if (!field) {
return this;
}
// Handle both `"field", value` and `{field: value}` -style arguments.
var attrs;
if (typeof field === 'object') {
attrs = field;
options = value;
} else {
(attrs = {})[field] = value;
}
options = options || {};
// Extract data and options.
var unset = options.unset;
var silent = options.silent;
var changes = [];
var changing = this._changing;
this._changing = true;
if (!changing) {
this._previousData = clone(this.data);
this.changed = {};
}
var current = this.data;
var prev = this._previousData;
// For each `set` data, update or delete the current value.
Object.keys(attrs).forEach((attr) => {
var attrValue = attrs[attr];
if (!isEqual(current[attr], attrValue, this.schema && this.schema[attr])) {
changes.push(attr);
}
if (!isEqual(prev[attr], attrValue, this.schema && this.schema[attr])) {
this.changed[attr] = attrValue;
} else {
delete this.changed[attr];
}
if (unset) {
delete current[attr];
} else {
current[attr] = attrValue;
}
});
// Trigger all relevant data changes.
if (!silent) {
if (changes.length) { this._pending = true; }
for (var i = 0, l = changes.length; i < l; i++) {
this.emit('change:' + changes[i], this, current[changes[i]], options);
}
}
// You might be wondering why there's a `while` loop here. Changes can
// be recursively nested within `"change"` events.
if (changing) { return this; }
if (!silent) {
while (this._pending) {
this._pending = false;
this.emit('change', this, options);
}
}
this._pending = false;
this._changing = false;
return this;
},
Remove an attribute by deleting it from the internal attributes hash.
Fires a "change" event unless silent is passed as an option.
unset (field, options) {
return this.set(field, undefined, Object.assign({}, options, { unset: true }));
},
Returns true
if the attribute is set to a non-null or non-undefined value.
has (field) {
return Boolean(typeof this.data[field] !== 'undefined');
},
Fetches a record from the database.
Load may be called in a variety of ways depending on the object state. The following are all
methods that may be used to load a record from a table primary keyed on an 'id' column.
Returns an ES6 Promise, but a traditional callback may be supplied as the last argument instead.
If the response is false, a record could not be found matching the keys requested.
If no schema is defined on the model, Quell will load the schema from the database before
performing the select.
// Load using existing data already in the record object.
var record = new User({id: 16});
record.load().then(function (exists) {
// loaded.
});
// Load using primary key (note, does not work for tables with multiple primaries)
var record = new User();
record.load(16).then(function (exists) {
// loaded
});
// Load using a specific column value (column does not need to be a primary key)
var record = new User();
record.load(16, 'id', function (exists) {
// loaded
});
// Load using multiple column values, or a column hash.
var record = new User();
record.load({id: 16}, function (exists) {
// loaded
});
load (value, field, options, callback) {
switch (arguments.length) {
case 3:
if (typeof options === 'function') {
callback = options;
options = { callback };
} else {
options = options || {};
options.callback = options.callback || callback;
}
break;
case 2:
if (typeof field === 'function') {
callback = field;
field = undefined;
}
if (typeof field === 'object') {
options = field || {};
field = undefined;
}
break;
case 1:
if (typeof value === 'function') {
callback = value;
value = undefined;
}
break;
default:
break;
}
var defer;
if (value === undefined) {
defer = this._loadWithExisting(options);
} else if (typeof value === 'object') {
defer = this._loadWithMultiColumn(value, options);
} else if (isScalar(value)) {
if (field === undefined) {
defer = this._loadWithPrimaryKey(value, options);
} else {
defer = this._loadWithSingleColumn(value, field, options);
}
}
if (typeof callback === 'function') {
return defer.then(
(exists) => { callback(null, exists); },
(err) => { callback(err || true); }
);
}
return defer;
},
Intelligently saves the record contents to the database.
Record.save()
attempts to ascertain if the record already exists in the database
by performing a query for the primary keys. This query is skipped if it is already known
if the record exists due to a fetch or Record.exists
being set to true
or false
.
If the record exists, an update is performed, otherwise a fresh insert is done.
If the options object contains a truthy replace
option, the save will always be a REPLACE.
See Record.update
and Record.insert
for details of those behaviors.
Returns an ES6 Promise, but a traditional callback may be supplied as the last argument instead.
save (options, callback) {
var self = this;
if (typeof options === 'function') {
callback = options;
options = { callback };
} else {
options = options || {};
options.callback = options.callback || callback;
}
if (options.replace) {
var ai = this.schema && this.schema.autoincrement;
if (ai) {
this.unset(ai);
}
return this.insert(options);
}
return Promise.resolve(self.exists === null ? self._promiseIfExists(options) : self.exists)
.then((exists) => {
if (exists) {
return self.update(options, callback);
}
return self.insert(options, callback);
});
},
Inserts the record into the database as a new row.
If the table has an auto-incrementing id, that field on the record will be updated to the new id,
overwriting any existing value.
If the options object contains a truthy replace
option, the save will always be a REPLACE using
the existing primary keys (including an auto-incrementing key).
Returns an ES6 Promise, but a traditional callback may be supplied as the last argument instead.
If no schema is defined on the model, Quell will load the schema from the database before
performing the insert.
insert (options, callback) {
if (typeof options === 'function') {
callback = options;
options = { callback };
} else {
options = options || {};
options.callback = options.callback || callback;
}
var self = this;
return this._promiseValidateSchema()
.then(() => {
var write = {};
var fields = Object.keys(self.data);
for (let i = 0; i < fields.length; i++) {
const field = fields[i];
const type = self.schema.columns[field];
if (type && typeof self.data[field] !== 'undefined' && (options.replace || self.schema.autoincrement !== field)) {
write[field] = type.prepare(self.data[field]);
}
}
return quell._buildInsertQuery(self.tablename, write, options.replace);
})
.then((query) => quell._promiseQueryRun(
query.query,
query.data,
(options && options.connection) || self.connection || quell.connection
))
.then((result) => {
if (self.schema.autoincrement && result && result.insertId !== undefined) {
self.data[self.schema.autoincrement] = result.insertId;
}
if (options.callback) {
options.callback(null, self);
}
return self;
}).catch((err) => {
if (options.callback) {
callback(err);
}
return Promise.reject(err);
});
},
Updates the database with the current contents of the record.
By default the update operation uses the primary keys of the record as the WHERE
clause of the
UPDATE
query, and will throw an error if all of the primary keys do not contain values. This
behavior can be overridden by providing a using
hash object in the update options which defines
what column values to use for the update. This is the only way to perform an update if the table
schema does not define any primary keys.
Returns an ES6 Promise, but a traditional callback may be supplied as the last argument instead.
If no schema is defined on the model, Quell will load the schema from the database before
performing the update.
update (options, callback) {
if (typeof options === 'function') {
callback = options;
options = { callback };
} else {
options = options || {};
options.callback = options.callback || callback;
}
var self = this;
return this._promiseValidateSchema()
.then(() => {
var lookup = {};
var lookupCount = 0;
var write = {};
var fields = Object.keys(self.data);
if (typeof options.using === 'object') {
lookup = options.using;
lookupCount = Object.keys(lookup).length;
} else {
for (let i = 0; i < self.schema.primaries.length; i++) {
const field = self.schema.primaries[i];
const type = self.schema.columns[field];
if (!self.has(field)) {
throw new Error('Could not update quell record, required primary key value was absent: ' + field);
} else {
lookup[field] = type.prepare(self.data[field]);
lookupCount++;
}
}
}
if (!lookupCount) {
throw new Error('Could not update quell record, no primary keys was available to update against.');
}
for (let i = 0; i < fields.length; i++) {
const field = fields[i];
const type = self.schema.columns[field];
if (type && typeof self.data[field] !== 'undefined' && (options.replace || self.schema.autoincrement !== field)) {
write[field] = type.prepare(self.data[field]);
}
}
return quell._buildUpdateQuery(self.tablename, write, lookup);
})
.then((query) => quell._promiseQueryRun(
query.query,
query.data,
(options && options.connection) || self.connection || quell.connection
))
.then(() => {
if (options.callback) {
options.callback(null, self);
}
return self;
}).catch((err) => {
if (options.callback) {
callback(err);
}
return Promise.reject(err);
});
},
Deletes the record from the database.
By default the delete operation uses the primary keys of the record as the WHERE
clause of the
DELETE
query, and will throw an error if all of the primary keys do not contain values. If the
table schema does not define any primary keys, Quell will use all data on the record to conduct the
query. This behavior can be overridden by providing a using
hash object in the delete options
which defines what column values to use for the delete. An error will be thrown if no values exist
to perform the delete with, so as to avoid deleting everything.
Returns an ES6 Promise, but a traditional callback may be supplied as the last argument instead.
If no schema is defined on the model, Quell will load the schema from the database before
performing the delete.
delete (options, callback) {
if (typeof options === 'function') {
callback = options;
options = { callback };
} else {
options = options || {};
options.callback = options.callback || callback;
}
var self = this;
return this._promiseValidateSchema()
.then(() => {
var lookup = {};
var lookupCount = 0;
var fields = self.schema.primaries;
if (typeof options.using === 'object') {
lookup = options.using;
lookupCount = Object.keys(lookup).length;
// If the schema has no primary keys, use any column data we have.
} else if (fields.length) {
for (let i = 0; i < fields.length; i++) {
const field = fields[i];
const type = self.schema.columns[field];
if (!self.has(field)) {
throw new Error('Could not delete quell record, required primary key value was absent: ' + field);
} else {
lookup[field] = type.prepare(self.data[field]);
lookupCount++;
}
}
} else {
fields = Object.keys(self.schema.columns);
for (let i = 0; i < fields.length; i++) {
const field = fields[i];
const type = self.schema.columns[field];
if (self.has(field)) {
lookup[field] = type.prepare(self.data[field]);
lookupCount++;
}
}
}
if (!lookupCount) {
throw new Error('Could not delete quell record, no data was available to delete against.');
}
return quell._buildDeleteQuery(self.tablename, lookup);
})
.then((query) => quell._promiseQueryRun(
query.query,
query.data,
(options && options.connection) || self.connection || quell.connection
))
.then(() => {
self.exists = false;
if (options.callback) {
options.callback(null, self);
}
return self;
}).catch((err) => {
if (options.callback) {
callback(err);
}
return Promise.reject(err);
});
},
Loads a record from the database using the existing primary key data.
_loadWithExisting (options) {
var self = this;
return this._promiseValidateSchema().then(() => {
if (!self.schema.primaries || !self.schema.primaries.length) {
throw new Error('Could not load quell model using existing data; table has no primary keys.');
}
var lookup = {};
for (let i = 0; i < self.schema.primaries.length; i++) {
const field = self.schema.primaries[i];
const type = self.schema.columns[field];
if (!self.has(field)) {
throw new Error('Could not load quell record, required primary key value was absent: ' + field);
} else {
lookup[field] = type.prepare(self.data[field]);
}
}
return self._loadUsing(lookup, options);
});
},
Loads a record from the database using a single primary key.
_loadWithPrimaryKey (value, options) {
var self = this;
return this._promiseValidateSchema().then(() => {
if (!self.schema.primaries.length) {
throw new Error('Could not load quell model using existing data; schema has no primary keys.');
}
if (self.schema.primaries.length > 1) {
throw new Error('Could not load quell model using single primary key, schema has more than one primary key.');
}
var key = self.schema.primaries[0];
var type = self.schema.columns[key];
var lookup = {};
lookup[key] = type.prepare(value);
return self._loadUsing(lookup, options);
});
},
Loads a record from the database using a single column value.
_loadWithSingleColumn (value, field, options) {
var self = this;
return this._promiseValidateSchema().then(() => {
var type = self.schema.columns[field];
var lookup = {};
if (!type) {
throw new Error('Could not load quell model, ' + field + ' does not exist in the table schema.');
}
lookup[field] = type.prepare(value);
return self._loadUsing(lookup, options);
});
},
Loads a record from the database using one or more column values from an object hash.
_loadWithMultiColumn (search, options) {
return this._promiseValidateSchema().then(() => {
if (typeof search !== 'object' || !Object.keys(search).length) {
throw new Error('Could not load quell model; provided data was empty or not an object.');
}
var lookup = {};
var fields = Object.keys(search);
for (let i = 0; i < fields.length; i++) {
const field = fields[i];
const type = this.schema.columns[field];
if (!type) {
throw new Error('Could not load quell model, ' + field + ' does not exist in the table schema.');
} else {
lookup[field] = type.prepare(search[field]);
}
}
return this._loadUsing(lookup, options);
});
},
Loads a record from the database using pre-validated data.
_loadUsing (lookup, options) {
var self = this;
var query = quell._buildSelectQuery(self.tablename, lookup);
return quell._promiseQueryRun(
query.query,
query.data,
(options && options.connection) || self.connection || quell.connection
).then((results) => {
// If results are returned, then we found the row and can map the data onto the model
// If no results were returned, then the row wasn't found and we resolve with false.
if (results.length) {
self.exists = true;
self.set(results[0]);
self.changed = {};
return self;
}
self.exists = false;
return false;
});
},
Checks to see if the record already exists in the database using the primary keys.
_promiseIfExists (options) {
var self = this;
return this._promiseValidateSchema().then(() => {
var lookup = {};
// if there are no primary keys, then it is impossible to determine if this row existed
// in a previous state. Therefore, we return true if we already knew it existed before
// or false if the previous state is unknown. This means that all new models without
// primary keys will default to inserts.
if (!self.schema.primaries.length) {
return (!!self.exists);
}
// Iterate over all primary keys. If we do not have a value for a key, assume the
// record does not exist. If all keys have values, perform a lookup of those fields
// and values to verify that the row exists.
for (let i = 0; i < self.schema.primaries.length; i++) {
const key = self.schema.primaries[i];
if (!self.has(key)) {
return (self.exists = false);
} else if (self.schema.columns[key]) {
lookup[key] = self.schema.columns[key].prepare(self.data[key]);
}
}
var query = quell._buildSelectQuery(self.tablename, lookup, self.schema.primaries);
return quell._promiseQueryRun(
query.query,
query.data,
(options && options.connection) || self.connection || quell.connection
).then((results) => {
self.exists = !!results.length;
return self.exists;
});
});
},
Validates the schema data for the model, loading the schema from the database if needed.
_promiseValidateSchema () {
var self = this;
if (!this.connection) {
throw new Error('quell model does not have a MySQL connection or pool defined.');
}
// if we have a schema already marked as good, just continue the callback chain
if (this.schema && this.schema.loaded) {
return Promise.resolve();
}
var valid =
this.schema &&
this.schema.primaries &&
Array.isArray(this.schema.primaries) &&
this.schema.columns &&
Object.keys(this.schema.columns).length
;
if (!valid) {
return quell._promiseTableSchema(this.tablename, this.connection).then((schema) => {
self.schema = schema;
});
}
return Promise.resolve();
},
};
Object.assign(modelBase.prototype, Record);
Constructs the query for a SELECT request.
quell._buildSelectQuery = function (tablename, lookup, select) {
var q = queryize()
.select(select || '*')
.from(tablename)
.where(lookup);
return q.compile();
};
Constructs the query for an INSERT request.
quell._buildInsertQuery = function (tablename, write, replace) {
var q = queryize()[replace ? 'replace' : 'insert'](write)
.into(tablename);
return q.compile();
};
Constructs the query for an UPDATE request.
quell._buildUpdateQuery = function (tablename, write, lookup) {
var q = queryize()
.update(tablename)
.set(write)
.where(lookup);
return q.compile();
};
Constructs the query for a DELETE request.
quell._buildDeleteQuery = function (tablename, lookup) {
var q = queryize()
.deleteFrom(tablename)
.where(lookup);
return q.compile();
};
Runs an arbitrary query. Attempts to use prepared statements when available.
quell._promiseQueryRun = function (query, data, mysql) {
var callback = proxmis();
mysql.query(query, data, callback);
return callback;
};
Loads the schema for a table from the database and parses for use.
quell._promiseTableSchema = function (tablename, mysql) {
return this._promiseQueryRun('DESCRIBE ' + tablename, null, mysql).then((results) => {
var schema = {
columns: {},
primaries: [],
autoincrement: false,
loaded: true,
};
for (let i = 0; i < results.length; i++) {
parseRow(results[i]);
}
function parseRow (row) {
var matches;
var column = {
NULL: row.Null === 'YES',
};
if (row.Type === 'date' || row.Type === 'datetime' || row.Type === 'timestamp' || row.Type === 'time' || row.Type === 'year') {
column = types[row.Type.toUpperCase()](column);
} else if ((matches = row.Type.match(/^(decimal|float|double)\((\d+),(\d+)\)/))) {
column.size = parseInt(matches[2], 10);
column.precision = parseInt(matches[3], 10);
column.unsigned = row.Type.indexOf('unsigned') >= 0;
column = types[matches[1].toUpperCase()](column);
} else if ((matches = row.Type.match(/^((?:big|medium|small|tiny)?int(?:eger)?)\((\d+)\)/))) {
column.size = parseInt(matches[2], 10);
column.unsigned = row.Type.indexOf('unsigned') >= 0;
column = types[matches[1].toUpperCase()](column);
} else if ((matches = row.Type.match(/^enum\((.*)\)/))) {
column.options = matches[1].split(',').map((opt) => opt.slice(1, -1));
column = types.ENUM(column);
} else if ((matches = row.Type.match(/^((?:var)?char)\((\d+)\)/))) {
column.size = parseInt(matches[2], 10);
column = types[matches[1].toUpperCase()](column);
} else {
// didn't find a known type. Split the type field by opening
// parens to get the type name without other info.
column.type = row.Type.split('(')[0].toUpperCase();
if (types[column.type]) {
column = types[column.type](column);
} else {
column = types.UNKNOWN(column);
}
}
schema.columns[row.Field] = column;
if (row.Key === 'PRI') {
schema.primaries.push(row.Field);
}
if (row.Extra === 'auto_increment') {
schema.autoincrement = row.Field;
}
}
return schema;
});
};
Creates a Queryize chain for loading multiple records.
Overrides the exec
function to pre-wrap all results with Quell models.
See QueryizeJS documentation for more details.
modelBase.find = function (where) {
var self = this;
var q = queryize().select().from(this.tablename);
if (where) {
q.where(where);
}
var exec = q.exec;
q.exec = function (conn, callback) {
switch (arguments.length) {
case 0:
conn = self.connection || quell.connection;
break;
case 1:
if (typeof conn === 'function') {
callback = conn;
conn = self.connection || quell.connection || undefined;
}
break;
case 2:
default:
}
if (!conn) {
const err = new Error('You must provide a node-mysql connection or pool for this query to use.');
if (callback) {
callback(err);
}
return Promise.reject(err);
}
var defer = proxmis(callback);
exec.call(q, conn, (err, results) => {
if (err) return defer(err);
results = results.map((row) => new self(row)); // eslint-disable-line new-cap
defer(null, results);
});
return defer.promise;
};
return q;
};
Pre-loads the schema details for the model.
modelBase.loadSchema = function (options, callback) {
if (typeof options === 'function') {
callback = options;
options = { callback };
} else {
options = options || {};
options.callback = options.callback || callback;
}
var self = this;
return quell._promiseTableSchema(
this.tablename,
(options && options.connection) || self.connection || quell.connection
).then((schema) => {
self.schema = schema;
if (options.callback) {
callback(null, self);
}
return self;
}, (err) => {
if (callback) {
callback(err);
}
return Promise.reject(err);
});
};
Checks if the value passed in is a primitive value.
function isScalar (value) {
switch (typeof value) {
case 'string':
case 'number':
case 'boolean':
return true;
default:
return false;
}
}
Checks if two values are equal using the column type
function isEqual (a, b, ctype) {
if (ctype) {
return ctype.compare(a, b);
}
if (a === b) { return true; }
return false;
}
By default, quell will automatically load the table schema for any models that are created
the first time a read/write operation is performed, but this operation may be skipped by
predefining the table schema as part of the model. Note: quell does not alter or create
table structure, defining the schema in code is not a substitute for creating a table
structure in the database.
To make it easier to define table structure, all of the column data types are accessible
directly from the quell object and can be used exactly as you would in a CREATE TABLE
command (eg, VARCHAR(10)
. If used as a value instead of a function, quell will use the
default values for that type. To define extra column attributes such as integer sign and
null acceptance, you must pass a column settings object as the main argument on the data
type object. See the main quell definition for an example.
Custom Types:
Every quell data type must define three functions: format
, prepare
and compare
function. If any of these options are excluded from the settings object, the defaults
for the base type are used.
prepare
is called right before the data is used in a query and sanitizes the input for
the expected data type. In the case of integers this means stripping any non-numeric
characters, for dates the value is parsed with Moment and then formatted as a MySQL date.
String and Enum values are force cast to string. All values are tested for null
.
The format
function is used when fetching data from the model via the .get()
method.
It attempts the structure whatever data is on the model in the way that MySQL would format
it in the database (eg, trimming values for their column lengths). This is useful for
validating model data before saving.
compare
is called when quell is determining what columns have changed from their
original values. It takes two arguments and returns a boolean.
var TYPE = function (options) {
var o = Object.create(this);
if (typeof options === 'object' && !Array.isArray(options)) {
Object.assign(o, this, options || {});
} else if (typeof o.initialize === 'function') {
o.initialize.apply(o, arguments);
}
return o;
};
var compare = function (a, b) {
return this.format(a) === this.format(b);
};
var TEXT = {
type: 'TEXT',
NULL: true,
size: 0,
format (o) {
if (o === null) {
if (this.NULL) {
return null;
}
return '';
}
if (this.size && this.size < o.length) {
return ('' + o).slice(0, this.size);
}
return ('' + o);
},
prepare (o) {
if (o === null) {
if (this.NULL) {
return null;
}
return '';
}
return '' + o;
},
compare,
initialize () {
if (arguments.length > 0) {
this.size = arguments[0];
}
},
};
var ENUM = {
type: 'ENUM',
NULL: true,
options: [],
format (o) {
var NULL = this.NULL ? null : '';
if (o === null) {
return NULL;
}
o = '' + o;
var options = this.options.map((s) => s.toUpperCase());
var i = options.indexOf(o.toUpperCase());
if (i > -1) {
return this.options[i];
}
return NULL;
},
prepare (o) {
if (o === null) {
return this.NULL ? null : '';
}
return '' + o;
},
compare,
initialize () {
this.options = Array.prototype.slice.call(arguments);
},
};
var INT = {
type: 'INT',
NULL: true,
size: 11,
precision: 0,
unsigned: false,
format (o) {
var result;
var NULL = this.NULL ? null : '0';
if (o === null) {
return NULL;
}
o = parseFloat(o);
if (isNaN(o)) {
return NULL;
}
if (this.precision) {
// round at the needed precision and then split on the decimal.
var k = Math.pow(10, this.precision);
result = String(Math.round(o * k) / k).split('.');
// if no decimal existed, make sure we create a place for it.
if (result.length === 1) { result.push(''); }
} else {
// parse as float and round off, then store in an array to simplify below.
result = [ Math.round(parseFloat(o)) ];
}
// if the whole number is longer than available space, slice off the extra characters
if (this.size && this.size - this.precision < result[0].length) {
if (this.size - this.precision === 0) {
result[0] = '';
} else {
result[0] = result[0].slice(0 - (this.size - this.precision));
}
}
// pad out the decimal places with 0, if needed
if (this.precision && result[1].length < this.precision) {
result[1] += new Array(this.precision - result[1].length + 1).join('0');
}
return this.precision ? result.join('.') : '' + result[0];
},
prepare (o) {
if (o === null) {
return this.NULL ? null : 0;
}
o = Number(o);
if (isNaN(o)) {
return this.NULL ? null : 0;
}
return o;
},
compare,
initialize () {
if (arguments.length > 0) {
this.size = arguments[0];
}
if (arguments.length > 1) {
this.precision = arguments[1];
}
},
};
var DATETIME = {
type: 'DATETIME',
NULL: true,
mask: 'YYYY-MM-DD HH:mm:ss',
format (o) {
var NULL = this.NULL ? null : '0000-00-00 00:00:00';
if (o === undefined) {
return undefined;
}
if (o === null) {
return NULL;
}
if (o === 'now') {
return moment().format(this.mask);
}
if (o instanceof Date) {
o = moment(o);
} else if (typeof o === 'string') {
o = moment(o, momentParse(o));
} else if (typeof o === 'number') {
o = moment(new Date(o));
}
o = moment(o).format(this.mask);
if (o === 'Invalid date') {
return NULL;
}
return o;
},
compare,
};
DATETIME.prepare = DATETIME.format;
function makeType (base, extras) {
var properties = Object.assign({}, base, extras || {});
var type = TYPE.bind(properties);
return Object.assign(type, properties);
}
TEXT Column Type
exports.TEXT = makeType(TEXT, { type: 'TEXT', size: 65535 });
BLOB Column Type
exports.BLOB = makeType(TEXT, { type: 'BLOB', size: 65535 });
MEDIUMBLOB Column Type
exports.MEDIUMBLOB = makeType(TEXT, { type: 'MEDIUMBLOB', size: 16777215 });
MEDIUMTEXT Column Type
exports.MEDIUMTEXT = makeType(TEXT, { type: 'MEDIUMTEXT', size: 16777215 });
LONGBLOB Column Type
exports.LONGBLOB = makeType(TEXT, { type: 'LONGBLOB', size: 4294967295 });
LONGTEXT Column Type
exports.LONGTEXT = makeType(TEXT, { type: 'LONGTEXT', size: 4294967295 });
TINYBLOB Column Type
exports.TINYBLOB = makeType(TEXT, { type: 'TINYBLOB', size: 255 });
TINYTEXT Column Type
exports.TINYTEXT = makeType(TEXT, { type: 'TINYTEXT', size: 255 });
VARCHAR Column Type
quell.VARCHAR({
size: 10,
NULL: false
})
exports.VARCHAR = makeType(TEXT, { type: 'VARCHAR', size: 255 });
CHAR Column Type
quell.CHAR({
size: 10,
NULL: false
})
exports.CHAR = makeType(TEXT, { type: 'CHAR', size: 255 });
INT Column Type
quell.INT({
length: 10,
unsigned: true,
NULL: false
})
exports.INT = makeType(INT);
exports.INTEGER = makeType(INT);
TINYINT Column Type
quell.TINYINT({
length: 2,
unsigned: true,
NULL: false
})
exports.TINYINT = makeType(INT, { type: 'TINYINT', size: 1 });
SMALLINT Column Type
quell.SMALLINT({
length: 3,
unsigned: true,
NULL: false
})
exports.SMALLINT = makeType(INT, { type: 'SMALLINT', size: 4 });
MEDIUMINT Column Type
quell.MEDIUMINT({
length: 10,
unsigned: true,
NULL: false
})
exports.MEDIUMINT = makeType(INT, { type: 'MEDIUMINT', size: 8 });
BIGINT Column Type
quell.BIGINT({
length: 10,
unsigned: true,
NULL: false
})
exports.BIGINT = makeType(INT, { type: 'BIGINT', size: 20 });
FLOAT Column Type
quell.FLOAT({
length: 6,
precision: 4
NULL: false
})
exports.FLOAT = makeType(INT, { type: 'FLOAT', size: 10, precision: 2 });
DOUBLE Column Type
quell.DOUBLE({
length: 6,
precision: 4
NULL: false
})
exports.DOUBLE = makeType(INT, { type: 'DOUBLE', size: 16, precision: 4 });
DECIMAL Column Type
quell.DECIMAL({
length: 6,
precision: 4
NULL: false
})
exports.DECIMAL = makeType(INT, { type: 'DECIMAL', size: 0, precision: 20 });
// DECIMALS must have a size and precision defined in the schema, so we're being ambiguous with the values
DATETIME Column Type
exports.DATETIME = makeType(DATETIME);
TIMESTAMP Column Type
exports.TIMESTAMP = makeType(DATETIME, { type: 'TIMESTAMP', NULL: false });
DATE Column Type
exports.DATE = makeType(DATETIME, { type: 'DATE', mask: 'YYYY-MM-DD' });
TIME Column Type
exports.TIME = makeType(DATETIME, { type: 'DATE', mask: 'HH:mm:ss' });
YEAR Column Type
exports.YEAR = makeType(DATETIME, { type: 'YEAR', mask: 'YYYY' });
ENUM Column Type
quell.ENUM({
options: ['Normal', 'Moderator', 'Admin'],
NULL: false
})
exports.ENUM = makeType(ENUM);
Used to define any column type not natively supported by Quell.
See the Schema entry for details of the column format.
exports.UNKNOWN = makeType(TEXT);