Skip to content

Commit 3b9e178

Browse files
authored
Merge branch 'master' into patch-1
2 parents 08db917 + f28ff42 commit 3b9e178

File tree

8 files changed

+355
-1143
lines changed

8 files changed

+355
-1143
lines changed

.travis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ node_js:
88
services:
99
- mysql
1010
- postgresql
11+
addons:
12+
postgresql: "9.4"
1113
script: npm run ci
1214
notifications:
1315
email: false

CHANGELOG.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
- 2017-09-25 - v2.0.0-alpha
2-
- 2017-09-25 - :warning: GraphQL support is incomplete in this alpha release
3-
- 2017-09-25 - Bump all dependencies
4-
- 2017-09-25 - Support Node.js 8
5-
- 2017-09-25 - Only Node.js >= 4.5 is now supported
6-
- 2017-09-25 - Support latest `jsonapi-server`
1+
- 2017-09-27 - v2.1.0
2+
- 2017-09-27 - Migrate to Sequelize v4
3+
- 2017-09-27 - Use native Array support for PostgreSQL
4+
- 2017-09-26 - v2.0.0
5+
- 2017-09-26 - Support Node.js 8
6+
- 2017-09-26 - Only Node.js >= 4.5 is now supported
7+
- 2017-09-26 - Support latest `jsonapi-server`
8+
- 2017-09-26 - Bug fix: correctly support arrays
9+
- 2017-09-26 - Bug fix: properly fix table names
710
- 2016-08-09 - v1.2.8
811
- 2016-08-09 - Update `sequelize` dependency to latest version
912
- 2016-08-09 - Optimised searches filtered by related resource

README.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
### :warning: !!! GraphQL support is incomplete for the current 2.0.0-alpha release !!! :warning:
2-
31
[![Coverage Status](https://coveralls.io/repos/holidayextras/jsonapi-store-relationaldb/badge.svg?branch=master&service=github)](https://coveralls.io/github/holidayextras/jsonapi-store-relationaldb?branch=master)
42
[![Build Status](https://travis-ci.org/holidayextras/jsonapi-store-relationaldb.svg?branch=master)](https://travis-ci.org/holidayextras/jsonapi-store-relationaldb)
53
[![npm version](https://badge.fury.io/js/jsonapi-store-relationaldb.svg)](http://badge.fury.io/js/jsonapi-store-relationaldb)
@@ -18,8 +16,6 @@ This project conforms to the specification laid out in the [jsonapi-server handl
1816
* Postgres
1917
* MySQL
2018
* MariaDB
21-
* SQLite
22-
* Microsoft SQL Server
2319

2420
### Usage
2521

@@ -30,6 +26,9 @@ jsonApi.define({
3026
resource: "comments",
3127
handlers: new RelationalDbStore({
3228
dialect: "mysql",
29+
dialectOptions: {
30+
supportBigNumbers: true
31+
},
3332
host: "localhost",
3433
port: 3306,
3534
database: "jsonapi", // If not provided, defaults to the name of the resource
@@ -64,6 +63,14 @@ When deploying schema changes, you'll need to correct your database schema - dat
6463

6564
When changing columns in a production database, a typical approach might be to create a new table that is a clone of the table in production, copy all data from the production table into the new table, run an ALTER-TABLE command on the new table to adjust the columns (this may take a while and will lock the table), then run a RENAME-TABLES to swap the production table out for the new one.
6665

66+
**Note:** When populating database tables, you can use the `force` config option to DROP and CREATE tables. This is helpful in development stage, when your data doesn't matter and you want your Tables schemas to change according to the DAOs without having to manually write migrations.
67+
68+
```js
69+
(new RelationalDbStore()).populate({force: true}, () => {
70+
//tables dropped and created
71+
})
72+
```
73+
6774
### Gotchas
6875

6976
Relational databases don't differentiate between `undefined` and `null` values. `Joi` does differentiate between `undefined` and `null` values. Some `undefined` properties will pass validation, whilst `null` properties may not. For example, the default articles resource contains a `created` attribute of type `"date"` - this won't pass validation with a `null` value, so the Joi schema will need tweaking.

lib/modelGenerators/default.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
var DataTypes = require('sequelize').DataTypes
2+
3+
exports.joiSchemaToSequelizeModel = function(resourceName, joiSchema) {
4+
var model = {
5+
id: { type: new DataTypes.STRING(38), primaryKey: true },
6+
type: {
7+
type: DataTypes.VIRTUAL, //We do not actually save this to DB, but API needs this
8+
set: function (val) {
9+
this.setDataValue('type', val)
10+
},
11+
get: function () {
12+
return resourceName
13+
}
14+
},
15+
meta: {
16+
type: DataTypes.STRING,
17+
get: function() {
18+
var data = this.getDataValue("meta");
19+
if (!data) return undefined;
20+
return JSON.parse(data);
21+
},
22+
set: function(val) {
23+
return this.setDataValue("meta", JSON.stringify(val));
24+
}
25+
}
26+
};
27+
28+
Object.keys(joiSchema).forEach(function(attributeName) {
29+
var attribute = joiSchema[attributeName];
30+
if (attribute._type === "string") model[attributeName] = { type: DataTypes.TEXT, allowNull: true };
31+
if (attribute._type === "date") model[attributeName] = { type: DataTypes.DATE, allowNull: true };
32+
if (attribute._type === "number") model[attributeName] = { type: DataTypes.NUMERIC, allowNull: true };
33+
if (attribute._type === "boolean") model[attributeName] = { type: DataTypes.BOOLEAN, allowNull: true };
34+
if (attribute._type === "array") {
35+
//Serialize array to ';'-separated string for most SQL dbs.
36+
model[attributeName] = {
37+
type: DataTypes.STRING,
38+
allowNull: true,
39+
get: function () {
40+
var data = this.getDataValue(attributeName);
41+
return data ? data.split(';') : []
42+
},
43+
set: function (val) {
44+
this.setDataValue(attributeName, val.join(';'));
45+
}
46+
}
47+
48+
}
49+
});
50+
51+
return model;
52+
};

lib/modelGenerators/postgres.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
var DataTypes = require('sequelize').DataTypes
2+
3+
exports.joiSchemaToSequelizeModel = function(resourceName, joiSchema) {
4+
var model = {
5+
id: { type: new DataTypes.STRING(38), primaryKey: true },
6+
type: {
7+
type: DataTypes.VIRTUAL, //We do not actually save this to DB, but API needs this
8+
set: function (val) {
9+
this.setDataValue('type', val)
10+
},
11+
get: function () {
12+
return resourceName
13+
}
14+
},
15+
meta: {
16+
type: DataTypes.JSONB,
17+
get: function() {
18+
var data = this.getDataValue("meta");
19+
if (!data) return undefined;
20+
return data;
21+
},
22+
set: function(val) {
23+
return this.setDataValue("meta", val);
24+
}
25+
}
26+
};
27+
28+
Object.keys(joiSchema).forEach(function(attributeName) {
29+
var attribute = joiSchema[attributeName];
30+
if (attribute._type === "string") model[attributeName] = { type: DataTypes.TEXT, allowNull: true };
31+
if (attribute._type === "date") model[attributeName] = { type: DataTypes.DATE, allowNull: true };
32+
if (attribute._type === "number") {
33+
if (typeof attribute._flags.precision !== "undefined") {
34+
model[attributeName] = { type: DataTypes.NUMERIC(32, attribute._flags.precision), allowNull: true };
35+
} else {
36+
model[attributeName] = { type: DataTypes.NUMERIC, allowNull: true };
37+
}
38+
}
39+
if (attribute._type === "boolean") model[attributeName] = { type: DataTypes.BOOLEAN, allowNull: true };
40+
if (attribute._type === "array") {
41+
//PostgreSQL has proper array support, so lets use that
42+
switch (attribute._inner.items[0]._type) {
43+
case "string": model[attributeName] = {type: DataTypes.ARRAY(DataTypes.STRING), allowNull: true}; break;
44+
case "number": model[attributeName] = {type: DataTypes.ARRAY(DataTypes.NUMERIC), allowNull: true}; break;
45+
case "boolean": model[attributeName] = {type: DataTypes.ARRAY(DataTypes.BOOLEAN), allowNull: true}; break;
46+
}
47+
model[attributeName].get = function () {
48+
return this.getDataValue(attributeName) || []
49+
}
50+
model[attributeName].set = function (val) {
51+
this.setDataValue(attributeName, val ? val : [])
52+
}
53+
}
54+
});
55+
56+
return model;
57+
};

lib/sqlHandler.js

Lines changed: 40 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
"use strict";
22
// http://docs.sequelizejs.com/en/latest/
33
var Sequelize = require("sequelize");
4+
var DataTypes = Sequelize.DataTypes;
45
var async = require("async");
56
var crypto = require("crypto");
67
var debug = require("debug")("jsonApi:store:relationaldb");
78
var Joi = require("joi");
89
var semver = require("semver");
9-
var _ = {
10-
pick: require("lodash.pick"),
11-
assign: require("lodash.assign"),
12-
omit: require("lodash.omit")
13-
};
14-
var util = require('util');
10+
var _ = require("lodash");
11+
var util = require('util');
12+
var modelGenerators = {
13+
postgres: require('./modelGenerators/postgres'),
14+
default: require('./modelGenerators/default')
15+
}
1516

1617
var MIN_SERVER_VERSION = "1.10.0";
1718

@@ -46,10 +47,16 @@ SqlStore.prototype.initialise = function(resourceConfig) {
4647
var database = self.config.database || resourceConfig.resource;
4748
var sequelizeArgs = [database, self.config.username, self.config.password, {
4849
dialect: self.config.dialect,
50+
dialectOptions: self.config.dialectOptions,
4951
host: self.config.host,
5052
port: self.config.port,
53+
storage: self.config.storage || ":memory:",
5154
logging: self.config.logging || require("debug")("jsonApi:store:relationaldb:sequelize"),
52-
freezeTableName: true
55+
define: {
56+
// Set freezeTableName on all table definitions to prevent sequelize from pluralizing
57+
// all table names by itself.
58+
freezeTableName: true
59+
}
5360
}];
5461

5562
// To prevent too many open connections, we will store all Sequelize instances in a hash map.
@@ -61,8 +68,7 @@ SqlStore.prototype.initialise = function(resourceConfig) {
6168
var instances = SqlStore._sequelizeInstances;
6269

6370
if (!instances[instanceId]) {
64-
var sequelize = Object.create(Sequelize.prototype);
65-
Sequelize.apply(sequelize, sequelizeArgs);
71+
var sequelize = new (Function.prototype.bind.apply(Sequelize, [null].concat(sequelizeArgs)))();
6672
instances[instanceId] = sequelize;
6773
}
6874

@@ -73,16 +79,21 @@ SqlStore.prototype.initialise = function(resourceConfig) {
7379
self.ready = true;
7480
};
7581

76-
SqlStore.prototype.populate = function(callback) {
82+
SqlStore.prototype.populate = function(options, callback) {
83+
if (typeof options === 'function') {
84+
callback = options;
85+
options = {};
86+
}
87+
7788
var self = this;
7889

7990
var tasks = [
8091
function(cb) {
81-
self.baseModel.sync().asCallback(cb);
92+
self.baseModel.sync(options).asCallback(cb);
8293
},
8394
function(cb) {
8495
async.eachSeries(self.relationArray, function(model, ecb) {
85-
model.sync().asCallback(ecb);
96+
model.sync(options).asCallback(ecb);
8697
}, cb);
8798
},
8899
function(cb) {
@@ -113,7 +124,19 @@ SqlStore.prototype._buildModels = function() {
113124
});
114125
relations = _.pick(self.resourceConfig.attributes, relations);
115126

116-
var modelAttributes = self._joiSchemaToSequelizeModel(localAttributes);
127+
var modelAttributes = { };
128+
switch (self.config.dialect) {
129+
case 'postgres':
130+
modelAttributes = modelGenerators.postgres.joiSchemaToSequelizeModel(
131+
self.resourceConfig.resource,
132+
localAttributes)
133+
break;
134+
default:
135+
modelAttributes = modelGenerators.default.joiSchemaToSequelizeModel(
136+
self.resourceConfig.resource,
137+
localAttributes)
138+
}
139+
117140
self.baseModel = self.sequelize.define(self.resourceConfig.resource, modelAttributes, { timestamps: false });
118141

119142
self.relations = { };
@@ -126,54 +149,26 @@ SqlStore.prototype._buildModels = function() {
126149
});
127150
};
128151

129-
SqlStore.prototype._joiSchemaToSequelizeModel = function(joiSchema) {
130-
var model = {
131-
id: { type: new Sequelize.STRING(38), primaryKey: true },
132-
type: Sequelize.STRING,
133-
meta: {
134-
type: Sequelize.STRING,
135-
get: function() {
136-
var data = this.getDataValue("meta");
137-
if (!data) return undefined;
138-
return JSON.parse(data);
139-
},
140-
set: function(val) {
141-
return this.setDataValue("meta", JSON.stringify(val));
142-
}
143-
}
144-
};
145-
146-
Object.keys(joiSchema).forEach(function(attributeName) {
147-
var attribute = joiSchema[attributeName];
148-
if (attribute._type === "string") model[attributeName] = { type: Sequelize.STRING, allowNull: true };
149-
if (attribute._type === "date") model[attributeName] = { type: Sequelize.STRING, allowNull: true };
150-
if (attribute._type === "number") model[attributeName] = { type: Sequelize.INTEGER, allowNull: true };
151-
if (attribute._type === "boolean") model[attributeName] = { type: Sequelize.BOOLEAN, allowNull: true };
152-
});
153-
154-
return model;
155-
};
156-
157152
SqlStore.prototype._defineRelationModel = function(relationName, many) {
158153
var self = this;
159154

160155
var modelName = self.resourceConfig.resource + "-" + relationName;
161156
var modelProperties = {
162157
uid: {
163-
type: Sequelize.INTEGER,
158+
type: DataTypes.INTEGER,
164159
primaryKey: true,
165160
autoIncrement: true
166161
},
167162
id: {
168-
type: new Sequelize.STRING(38),
163+
type: new DataTypes.STRING(38),
169164
allowNull: false
170165
},
171166
type: {
172-
type: new Sequelize.STRING(38),
167+
type: new DataTypes.STRING(38),
173168
allowNull: false
174169
},
175170
meta: {
176-
type: Sequelize.STRING,
171+
type: DataTypes.STRING,
177172
get: function() {
178173
var data = this.getDataValue("meta");
179174
if (!data) return undefined;

0 commit comments

Comments
 (0)