From c1616fd9983e43b85546adc361fa9b4b4211bc15 Mon Sep 17 00:00:00 2001 From: dgsan Date: Mon, 17 Jun 2013 14:25:57 -0700 Subject: [PATCH 1/2] Support for Enum type in MySQL adapter. Requires patch to mainline JDB. (See pull request: https://github.com/1602/jugglingdb/pull/296.) After this there are several ways to use Enum with the adapter as shown in datatypes.test.js. --- lib/enumFactory.js | 52 +++++++++++++++++ lib/mysql.js | 12 ++++ test/datatypes.test.js | 125 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 189 insertions(+) create mode 100644 lib/enumFactory.js create mode 100644 test/datatypes.test.js diff --git a/lib/enumFactory.js b/lib/enumFactory.js new file mode 100644 index 0000000..6a984d7 --- /dev/null +++ b/lib/enumFactory.js @@ -0,0 +1,52 @@ +function Enum() { + if(arguments.length > 0){ + var dxList = []; + dxList.push(''); // Want empty value to be at index 0 to match MySQL Enum values and MySQL non-strict behavior. + for(var arg in arguments){ + arg = String(arguments[arg]); + Object.defineProperty(this, arg.toUpperCase(), {configurable: false, enumerable: true, value: arg, writable: false}); + dxList.push(arg); + } + Object.defineProperty(this, '_values', {configurable: false, enumerable: false, value: dxList, writable: false}); + Object.defineProperty(this, '_string', {configurable: false, enumerable: false, value: stringified(this), writable: false}); + Object.freeze(this); + return this; + } else { + throw "No arguments - can't create Enum."; + } +}; +Object.defineProperty(Enum.prototype, 'name', {configurable: false, enumerable: false, value: 'Enum', writable: false}); + + +var EnumFactory = (function() { + function FakeEnumConstructor(args) { + return Enum.apply(this, args); + } + + FakeEnumConstructor.prototype = Enum.prototype; + + return function() { + var returnObject = new FakeEnumConstructor(arguments); + returnObject.constructor = Enum.constructor; + return returnObject; + } +})(); + +function stringified(anEnum) { + var s = []; + for(var i in anEnum._values){ + if(anEnum._values[i] != ''){ + s.push("'" + anEnum._values[i] + "'"); + } + } + return s.join(','); +} + +exports.EnumFactory = EnumFactory; +exports.Enum = Enum; + + + + + + diff --git a/lib/mysql.js b/lib/mysql.js index 9d300dc..d8dbf3a 100644 --- a/lib/mysql.js +++ b/lib/mysql.js @@ -3,6 +3,8 @@ */ var mysql = require('mysql'); var jdb = require('jugglingdb'); +var Enums = require('./enumFactory'); + exports.initialize = function initializeSchema(schema, callback) { if (!mysql) return; @@ -58,6 +60,12 @@ exports.initialize = function initializeSchema(schema, callback) { // MySQL specific column types schema.constructor.registerType(function Point() {}); + + schema.EnumFactory = Enums.EnumFactory; // factory for Enums + schema.Enum = Enums.Enum; // constructor for Enums + + schema.constructor.registerType(Enums.Enum); + }; /** @@ -644,6 +652,10 @@ function datatype(p) { case 'Point': dt = 'POINT'; break; + case 'Enum': + dt = 'ENUM(' + p.type._string + ')'; + dt = stringOptions(p, dt); // Enum columns can have charset/collation. + break; } return dt; } diff --git a/test/datatypes.test.js b/test/datatypes.test.js new file mode 100644 index 0000000..7cd2be3 --- /dev/null +++ b/test/datatypes.test.js @@ -0,0 +1,125 @@ +var should = require('./init.js'); +var assert = require('assert'); +var Schema = require('jugglingdb').Schema; + +var db, settings, adapter, EnumModel, ANIMAL_ENUM; + +describe('MySQL specific datatypes', function() { + + before(setup); + + it('should run migration', function(done) { + db.automigrate(function(){ + done(); + }); + }); + + it('should create a model instance with Enums', function(done) { + var em = EnumModel.create({animal: ANIMAL_ENUM.CAT, condition: 'sleepy', mood: 'happy'}, function(err, obj) { + assert.ok(!err); + assert.equal(obj.condition, 'sleepy'); + EnumModel.findOne({where: {animal: ANIMAL_ENUM.CAT}}, function(err, found){ + assert.ok(!err); + assert.equal(found.mood, 'happy'); + assert.equal(found.animal, ANIMAL_ENUM.CAT); + done(); + }); + }); + }); + + it('should fail spectacularly with invalid enum values', function(done) { + var em = EnumModel.create({animal: 'horse', condition: 'sleepy', mood: 'happy'}, function(err, obj) { + assert.ok(!err); + EnumModel.find(obj.id, function(err, found){ + assert.ok(!err); + assert.equal(found.animal, ''); // MySQL fun. + assert.equal(found.animal, 0); + done(); + }); + }); + }); + + it('should disconnect when done', function(done) { + db.disconnect(); + done() + }); + +}); + +function setup(done) { + + require('./init.js'); + + db = getSchema(); + + ANIMAL_ENUM = db.EnumFactory('dog', 'cat', 'mouse'); + + EnumModel = db.define('EnumModel', { + animal: { type: ANIMAL_ENUM, null: false }, + condition: { type: db.EnumFactory('hungry', 'sleepy', 'thirsty') }, + mood: { type: new db.Enum('angry', 'happy', 'sad') } + }); + + blankDatabase(db, done); + +} + +var query = function (sql, cb) { + db.adapter.query(sql, cb); +}; + +var blankDatabase = function (db, cb) { + var dbn = db.settings.database; + var cs = db.settings.charset; + var co = db.settings.collation; + query('DROP DATABASE IF EXISTS ' + dbn, function(err) { + var q = 'CREATE DATABASE ' + dbn; + if(cs){ + q += ' CHARACTER SET ' + cs; + } + if(co){ + q += ' COLLATE ' + co; + } + query(q, function(err) { + query('USE '+ dbn, cb); + }); + }); +}; + +getFields = function (model, cb) { + query('SHOW FIELDS FROM ' + model, function(err, res) { + if (err) { + cb(err); + } else { + var fields = {}; + res.forEach(function(field){ + fields[field.Field] = field; + }); + cb(err, fields); + } + }); +} + +getIndexes = function (model, cb) { + query('SHOW INDEXES FROM ' + model, function(err, res) { + if (err) { + console.log(err); + cb(err); + } else { + var indexes = {}; + // Note: this will only show the first key of compound keys + res.forEach(function(index) { + if (parseInt(index.Seq_in_index, 10) == 1) { + indexes[index.Key_name] = index + } + }); + cb(err, indexes); + } + }); +}; + + + + + + From 4bf7bcd2b38504d9e4a9ac2adde3adc7d2347151 Mon Sep 17 00:00:00 2001 From: dgsan Date: Tue, 18 Jun 2013 10:36:05 -0700 Subject: [PATCH 2/2] After feedback changed Enum implementation to be a function. The main side effect is that the `Enum` type can't be registered. Use `EnumFactory()` to build an `Enum`. --- lib/enumFactory.js | 45 ++++++++++++++++++++---------------------- lib/mysql.js | 7 +++---- test/datatypes.test.js | 13 +++++++++++- 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/lib/enumFactory.js b/lib/enumFactory.js index 6a984d7..baf5371 100644 --- a/lib/enumFactory.js +++ b/lib/enumFactory.js @@ -1,36 +1,33 @@ -function Enum() { +var EnumFactory = function() { if(arguments.length > 0){ + var Enum = function Enum(arg){ + if(typeof arg === 'number' && arg % 1 == 0) { + return Enum._values[arg]; + } else if(Enum[arg]){ + return Enum[arg] + } else if (Enum._values.indexOf(arg) !== -1 ) { + return arg; + } else if (arg === null) { + return null; + } else { + return ''; + } + }; var dxList = []; dxList.push(''); // Want empty value to be at index 0 to match MySQL Enum values and MySQL non-strict behavior. for(var arg in arguments){ arg = String(arguments[arg]); - Object.defineProperty(this, arg.toUpperCase(), {configurable: false, enumerable: true, value: arg, writable: false}); + Object.defineProperty(Enum, arg.toUpperCase(), {configurable: false, enumerable: true, value: arg, writable: false}); dxList.push(arg); } - Object.defineProperty(this, '_values', {configurable: false, enumerable: false, value: dxList, writable: false}); - Object.defineProperty(this, '_string', {configurable: false, enumerable: false, value: stringified(this), writable: false}); - Object.freeze(this); - return this; + Object.defineProperty(Enum, '_values', {configurable: false, enumerable: false, value: dxList, writable: false}); + Object.defineProperty(Enum, '_string', {configurable: false, enumerable: false, value: stringified(Enum), writable: false}); + Object.freeze(Enum); + return Enum; } else { - throw "No arguments - can't create Enum."; + throw "No arguments - could not create Enum."; } }; -Object.defineProperty(Enum.prototype, 'name', {configurable: false, enumerable: false, value: 'Enum', writable: false}); - - -var EnumFactory = (function() { - function FakeEnumConstructor(args) { - return Enum.apply(this, args); - } - - FakeEnumConstructor.prototype = Enum.prototype; - - return function() { - var returnObject = new FakeEnumConstructor(arguments); - returnObject.constructor = Enum.constructor; - return returnObject; - } -})(); function stringified(anEnum) { var s = []; @@ -43,7 +40,7 @@ function stringified(anEnum) { } exports.EnumFactory = EnumFactory; -exports.Enum = Enum; + diff --git a/lib/mysql.js b/lib/mysql.js index d8dbf3a..c0272a5 100644 --- a/lib/mysql.js +++ b/lib/mysql.js @@ -3,7 +3,7 @@ */ var mysql = require('mysql'); var jdb = require('jugglingdb'); -var Enums = require('./enumFactory'); +var EnumFactory = require('./enumFactory').EnumFactory; exports.initialize = function initializeSchema(schema, callback) { @@ -61,10 +61,8 @@ exports.initialize = function initializeSchema(schema, callback) { // MySQL specific column types schema.constructor.registerType(function Point() {}); - schema.EnumFactory = Enums.EnumFactory; // factory for Enums - schema.Enum = Enums.Enum; // constructor for Enums + schema.EnumFactory = EnumFactory; // factory for Enums. Note that currently Enums can not be registered. - schema.constructor.registerType(Enums.Enum); }; @@ -213,6 +211,7 @@ MySQL.prototype.toDatabase = function (prop, val) { return '"' + dateToMysql(val) + '"'; } if (prop.type.name == "Boolean") return val ? 1 : 0; + if (typeof prop.type === 'function') return this.client.escape(prop.type(val)); return this.client.escape(val.toString()); }; diff --git a/test/datatypes.test.js b/test/datatypes.test.js index 7cd2be3..882b39a 100644 --- a/test/datatypes.test.js +++ b/test/datatypes.test.js @@ -14,6 +14,17 @@ describe('MySQL specific datatypes', function() { }); }); + it('An enum should parse itself', function(done) { + assert.equal(ANIMAL_ENUM.CAT, ANIMAL_ENUM('cat')); + assert.equal(ANIMAL_ENUM.CAT, ANIMAL_ENUM('CAT')); + assert.equal(ANIMAL_ENUM.CAT, ANIMAL_ENUM(2)); + assert.equal(ANIMAL_ENUM.CAT, 'cat'); + assert.equal(ANIMAL_ENUM(null), null); + assert.equal(ANIMAL_ENUM(''), ''); + assert.equal(ANIMAL_ENUM(0), ''); + done(); + }); + it('should create a model instance with Enums', function(done) { var em = EnumModel.create({animal: ANIMAL_ENUM.CAT, condition: 'sleepy', mood: 'happy'}, function(err, obj) { assert.ok(!err); @@ -57,7 +68,7 @@ function setup(done) { EnumModel = db.define('EnumModel', { animal: { type: ANIMAL_ENUM, null: false }, condition: { type: db.EnumFactory('hungry', 'sleepy', 'thirsty') }, - mood: { type: new db.Enum('angry', 'happy', 'sad') } + mood: { type: db.EnumFactory('angry', 'happy', 'sad') } }); blankDatabase(db, done);