Skip to content

Commit

Permalink
Merge pull request sequelize#5283 from onzag/geography
Browse files Browse the repository at this point in the history
geography and tests
  • Loading branch information
janmeier committed Jan 30, 2016
2 parents 046620a + 778ec12 commit 7b44a87
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 30 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
- [FIXED] Prevent race condition after transaction finished [#5222](https://github.com/sequelize/sequelize/issues/5222)
- [FIXED] Fixed Instance.reload issues ([#4844](https://github.com/sequelize/sequelize/issues/4844) and [#4452](https://github.com/sequelize/sequelize/issues/4452))
- [FIXED] Fix upsert when primary key contains `.field` (internal API change for `queryInterface.upsert`) [#4755](https://github.com/sequelize/sequelize/issues/4755)
- [ADDED] Geography support for postgres

# 3.18.0
- [ADDED] Support silent: true in bulk update [#5200](https://github.com/sequelize/sequelize/issues/5200)
Expand Down
28 changes: 27 additions & 1 deletion lib/data-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,31 @@ GEOMETRY.prototype.$stringify = function (value) {
return 'GeomFromText(\'' + Wkt.stringify(value) + '\')';
};

/**
* A geography datatype represents two dimensional spacial objects in an elliptic coord system.
* @property GEOGRAPHY
*/

var GEOGRAPHY = ABSTRACT.inherits(function(type, srid) {
var options = _.isPlainObject(type) ? type : {
type: type,
srid: srid
};

if (!(this instanceof GEOGRAPHY)) return new GEOGRAPHY(options);

this.options = options;
this.type = options.type;
this.srid = options.srid;
});

GEOGRAPHY.prototype.key = GEOGRAPHY.key = 'GEOGRAPHY';

GEOGRAPHY.prototype.escape = false;
GEOGRAPHY.prototype.$stringify = function (value) {
return 'GeomFromText(\'' + Wkt.stringify(value) + '\')';
};

Object.keys(helpers).forEach(function (helper) {
helpers[helper].forEach(function (DataType) {
if (!DataType[helper]) {
Expand Down Expand Up @@ -887,7 +912,8 @@ var dataTypes = {
REAL: REAL,
DOUBLE: DOUBLE,
'DOUBLE PRECISION': DOUBLE,
GEOMETRY: GEOMETRY
GEOMETRY: GEOMETRY,
GEOGRAPHY: GEOGRAPHY
};

_.each(dataTypes, function (dataType) {
Expand Down
4 changes: 3 additions & 1 deletion lib/dialects/postgres/connection-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ ConnectionManager.prototype.connect = function(config) {

// oids for hstore and geometry are dynamic - so select them at connection time
if (dataTypes.HSTORE.types.postgres.oids.length === 0) {
query += 'SELECT typname, oid, typarray FROM pg_type WHERE typtype = \'b\' AND typname IN (\'hstore\', \'geometry\')';
query += 'SELECT typname, oid, typarray FROM pg_type WHERE typtype = \'b\' AND typname IN (\'hstore\', \'geometry\', \'geography\')';
}

return new Promise(function (resolve, reject) {
Expand All @@ -152,6 +152,8 @@ ConnectionManager.prototype.connect = function(config) {
type = dataTypes.postgres.GEOMETRY;
} else if (row.typname === 'hstore') {
type = dataTypes.postgres.HSTORE;
} else if (row.typname === 'geography'){
type = dataTypes.postgres.GEOGRAPHY;
}

type.types.postgres.oids.push(row.oid);
Expand Down
33 changes: 33 additions & 0 deletions lib/dialects/postgres/data-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,38 @@ module.exports = function (BaseTypes) {
return 'ST_GeomFromGeoJSON(\'' + JSON.stringify(value) + '\')';
};

var GEOGRAPHY = BaseTypes.GEOGRAPHY.inherits();

GEOGRAPHY.prototype.toSql = function() {
var result = 'GEOGRAPHY';

if (this.type){
result += '(' + this.type;

if (this.srid){
result += ',' + this.srid;
}

result += ')';
}

return result;
};

BaseTypes.GEOGRAPHY.types.postgres = {
oids: [],
array_oids: []
};

GEOGRAPHY.parse = GEOGRAPHY.prototype.parse = function(value) {
var b = new Buffer(value, 'hex');
return wkx.Geometry.parse(b).toGeoJSON();
};

GEOGRAPHY.prototype.$stringify = function (value) {
return 'ST_GeomFromGeoJSON(\'' + JSON.stringify(value) + '\')';
};

var hstore;
var HSTORE = BaseTypes.HSTORE.inherits(function () {
if (!(this instanceof HSTORE)) return new HSTORE();
Expand Down Expand Up @@ -373,6 +405,7 @@ module.exports = function (BaseTypes) {
'DOUBLE PRECISION': DOUBLE,
FLOAT: FLOAT,
GEOMETRY: GEOMETRY,
GEOGRAPHY: GEOGRAPHY,
HSTORE: HSTORE,
RANGE: RANGE
};
Expand Down
1 change: 1 addition & 0 deletions lib/dialects/postgres/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ PostgresDialect.prototype.supports = _.merge(_.cloneDeep(Abstract.prototype.supp
NUMERIC: true,
ARRAY: true,
GEOMETRY: true,
GEOGRAPHY: true,
JSON: true,
JSONB: true,
deferrableConstraints: true,
Expand Down
181 changes: 181 additions & 0 deletions test/integration/model/geography.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
'use strict';

/* jshint -W030 */
/* jshint -W110 */
var chai = require('chai')
, expect = chai.expect
, Support = require(__dirname + '/../support')
, DataTypes = require(__dirname + '/../../../lib/data-types');

var current = Support.sequelize;

describe(Support.getTestDialectTeaser('Model'), function() {
if (current.dialect.supports.GEOGRAPHY) {
describe('GEOGRAPHY', function() {
beforeEach(function() {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
geography: DataTypes.GEOGRAPHY
});

return this.User.sync({ force: true });
});

it('works with aliases fields', function () {
var Pub = this.sequelize.define('Pub', {
location: {field: 'coordinates', type: DataTypes.GEOGRAPHY}
})
, point = {type: 'Point', coordinates: [39.807222, -76.984722]};

return Pub.sync({ force: true }).then(function () {
return Pub.create({location: point});
}).then(function (pub) {
expect(pub).not.to.be.null;
expect(pub.location).to.be.deep.eql(point);
});
});

it('should create a geography object', function() {
var User = this.User;
var point = { type: 'Point', coordinates: [39.807222,-76.984722]};

return User.create({username: 'username', geography: point }).then(function(newUser) {
expect(newUser).not.to.be.null;
expect(newUser.geography).to.be.deep.eql(point);
});
});

it('should update a geography object', function() {
var User = this.User;
var point1 = { type: 'Point', coordinates: [39.807222,-76.984722]}
, point2 = { type: 'Point', coordinates: [49.807222,-86.984722]};
var props = {username: 'username', geography: point1};

return User.create(props).then(function(user) {
return User.update({geography: point2}, {where: {username: props.username}});
}).then(function(count) {
return User.findOne({where: {username: props.username}});
}).then(function(user) {
expect(user.geography).to.be.deep.eql(point2);
});
});
});

describe('GEOGRAPHY(POINT)', function() {
beforeEach(function() {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
geography: DataTypes.GEOGRAPHY('POINT')
});

return this.User.sync({ force: true });
});

it('should create a geography object', function() {
var User = this.User;
var point = { type: 'Point', coordinates: [39.807222,-76.984722]};

return User.create({username: 'username', geography: point }).then(function(newUser) {
expect(newUser).not.to.be.null;
expect(newUser.geography).to.be.deep.eql(point);
});
});

it('should update a geography object', function() {
var User = this.User;
var point1 = { type: 'Point', coordinates: [39.807222,-76.984722]}
, point2 = { type: 'Point', coordinates: [49.807222,-86.984722]};
var props = {username: 'username', geography: point1};

return User.create(props).then(function(user) {
return User.update({geography: point2}, {where: {username: props.username}});
}).then(function(count) {
return User.findOne({where: {username: props.username}});
}).then(function(user) {
expect(user.geography).to.be.deep.eql(point2);
});
});
});

describe('GEOGRAPHY(LINESTRING)', function() {
beforeEach(function() {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
geography: DataTypes.GEOGRAPHY('LINESTRING')
});

return this.User.sync({ force: true });
});

it('should create a geography object', function() {
var User = this.User;
var point = { type: 'LineString', 'coordinates': [ [100.0, 0.0], [101.0, 1.0] ] };

return User.create({username: 'username', geography: point }).then(function(newUser) {
expect(newUser).not.to.be.null;
expect(newUser.geography).to.be.deep.eql(point);
});
});

it('should update a geography object', function() {
var User = this.User;
var point1 = { type: 'LineString', coordinates: [ [100.0, 0.0], [101.0, 1.0] ] }
, point2 = { type: 'LineString', coordinates: [ [101.0, 0.0], [102.0, 1.0] ] };
var props = {username: 'username', geography: point1};

return User.create(props).then(function(user) {
return User.update({geography: point2}, {where: {username: props.username}});
}).then(function(count) {
return User.findOne({where: {username: props.username}});
}).then(function(user) {
expect(user.geography).to.be.deep.eql(point2);
});
});
});

describe('GEOGRAPHY(POLYGON)', function() {
beforeEach(function() {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
geography: DataTypes.GEOGRAPHY('POLYGON')
});

return this.User.sync({ force: true });
});

it('should create a geography object', function() {
var User = this.User;
var point = { type: 'Polygon', coordinates: [
[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],
[100.0, 1.0], [100.0, 0.0] ]
]};

return User.create({username: 'username', geography: point }).then(function(newUser) {
expect(newUser).not.to.be.null;
expect(newUser.geography).to.be.deep.eql(point);
});
});

it('should update a geography object', function() {
var User = this.User;
var polygon1 = { type: 'Polygon', coordinates: [
[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],
[100.0, 1.0], [100.0, 0.0] ]
]}
, polygon2 = { type: 'Polygon', coordinates: [
[ [100.0, 0.0], [102.0, 0.0], [102.0, 1.0],
[100.0, 1.0], [100.0, 0.0] ]
]};
var props = {username: 'username', geography: polygon1};

return User.create(props).then(function(user) {
return User.update({geography: polygon2}, {where: {username: props.username}});
}).then(function(count) {
return User.findOne({where: {username: props.username}});
}).then(function(user) {
expect(user.geography).to.be.deep.eql(polygon2);
});
});
});
}
});
48 changes: 20 additions & 28 deletions test/integration/model/geometry.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@ describe(Support.getTestDialectTeaser('Model'), function() {
if (current.dialect.supports.GEOMETRY) {
describe('GEOMETRY', function() {
beforeEach(function() {
return Support.prepareTransactionTest(this.sequelize).bind(this).then(function(sequelize) {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
geometry: DataTypes.GEOMETRY
});

return this.User.sync({ force: true });
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
geometry: DataTypes.GEOMETRY
});

return this.User.sync({ force: true });
});

it('works with aliases fields', function () {
Expand Down Expand Up @@ -65,14 +63,12 @@ describe(Support.getTestDialectTeaser('Model'), function() {

describe('GEOMETRY(POINT)', function() {
beforeEach(function() {
return Support.prepareTransactionTest(this.sequelize).bind(this).then(function(sequelize) {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
geometry: DataTypes.GEOMETRY('POINT')
});

return this.User.sync({ force: true });
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
geometry: DataTypes.GEOMETRY('POINT')
});

return this.User.sync({ force: true });
});

it('should create a geometry object', function() {
Expand Down Expand Up @@ -103,14 +99,12 @@ describe(Support.getTestDialectTeaser('Model'), function() {

describe('GEOMETRY(LINESTRING)', function() {
beforeEach(function() {
return Support.prepareTransactionTest(this.sequelize).bind(this).then(function(sequelize) {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
geometry: DataTypes.GEOMETRY('LINESTRING')
});

return this.User.sync({ force: true });
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
geometry: DataTypes.GEOMETRY('LINESTRING')
});

return this.User.sync({ force: true });
});

it('should create a geometry object', function() {
Expand Down Expand Up @@ -141,14 +135,12 @@ describe(Support.getTestDialectTeaser('Model'), function() {

describe('GEOMETRY(POLYGON)', function() {
beforeEach(function() {
return Support.prepareTransactionTest(this.sequelize).bind(this).then(function(sequelize) {
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
geometry: DataTypes.GEOMETRY('POLYGON')
});

return this.User.sync({ force: true });
this.User = this.sequelize.define('User', {
username: DataTypes.STRING,
geometry: DataTypes.GEOMETRY('POLYGON')
});

return this.User.sync({ force: true });
});

it('should create a geometry object', function() {
Expand Down

0 comments on commit 7b44a87

Please sign in to comment.