2014-04-14 21:01:12 +08:00
var _ = require ( 'underscore' ) ,
PSQLWrapper = require ( '../sql/psql_wrapper' ) ,
Step = require ( 'step' ) ,
pg = require ( 'pg' ) ; //.native; // disabled for now due to: https://github.com/brianc/node-postgres/issues/48
2011-11-22 08:06:14 +08:00
_ . mixin ( require ( 'underscore.string' ) ) ;
2011-06-13 11:23:02 +08:00
2013-05-25 00:16:12 +08:00
// Max database connections in the pool
// Subsequent connections will block waiting for a free slot
pg . defaults . poolSize = global . settings . db _pool _size || 16 ;
// Milliseconds of idle time before removing connection from pool
pg . defaults . poolIdleTimeout = global . settings . db _pool _idleTimeout || 30000 ;
// Frequency to check for idle clients within the pool, ms
pg . defaults . reapIntervalMillis = global . settings . db _pool _reapInterval || 1000 ;
pg . on ( 'error' , function ( err , client ) {
console . log ( "PostgreSQL connection error: " + err ) ;
} ) ;
2013-07-10 03:50:28 +08:00
// Workaround for https://github.com/Vizzuality/CartoDB-SQL-API/issues/100
var types = require ( _ _dirname + '/../../node_modules/pg/lib/types' ) ;
var arrayParser = require ( _ _dirname + '/../../node_modules/pg/lib/types/arrayParser' ) ;
var floatParser = function ( val ) {
return parseFloat ( val ) ;
} ;
var floatArrayParser = function ( val ) {
if ( ! val ) { return null ; }
var p = arrayParser . create ( val , function ( entry ) {
return floatParser ( entry ) ;
} ) ;
return p . parse ( ) ;
} ;
2013-07-23 00:22:30 +08:00
types . setTypeParser ( 20 , floatParser ) ; // int8
types . setTypeParser ( 700 , floatParser ) ; // float4
types . setTypeParser ( 701 , floatParser ) ; // float8
types . setTypeParser ( 1700 , floatParser ) ; // numeric
types . setTypeParser ( 1021 , floatArrayParser ) ; // _float4
types . setTypeParser ( 1022 , floatArrayParser ) ; // _float8
types . setTypeParser ( 1231 , floatArrayParser ) ; // _numeric
types . setTypeParser ( 1016 , floatArrayParser ) ; // _int8
2013-07-10 03:50:28 +08:00
2013-10-02 16:22:13 +08:00
// Standard type->name mappnig (up to oid=2000)
var stdTypeName = {
16 : 'bool' ,
17 : 'bytea' ,
20 : 'int8' ,
21 : 'int2' ,
23 : 'int4' ,
25 : 'text' ,
26 : 'oid' ,
114 : 'JSON' ,
700 : 'float4' ,
701 : 'float8' ,
1000 : '_bool' ,
1015 : '_varchar' ,
1042 : 'bpchar' ,
1043 : 'varchar' ,
1005 : '_int2' ,
1007 : '_int4' ,
1014 : '_bpchar' ,
1016 : '_int8' ,
1021 : '_float4' ,
1022 : '_float8' ,
1008 : '_regproc' ,
1009 : '_text' ,
2013-11-06 18:43:56 +08:00
1082 : 'date' ,
2013-10-02 16:22:13 +08:00
1114 : 'timestamp' ,
2013-11-06 18:43:56 +08:00
1182 : '_date' ,
2013-10-02 16:22:13 +08:00
1184 : 'timestampz' ,
1186 : 'interval' ,
1231 : '_numeric' ,
1700 : 'numeric'
} ;
// Holds a typeId->typeName mapping for each
// database ever connected to
var extTypeName = { } ;
2013-05-25 00:16:12 +08:00
2011-06-13 11:23:02 +08:00
// PSQL
//
// A simple postgres wrapper with logic about username and database to connect
//
// * intended for use with pg_bouncer
// * defaults to connecting with a "READ ONLY" user to given DB if not passed a specific user_id
2013-11-18 18:42:43 +08:00
//
// @param opts connection options:
// user: database username
// pass: database user password
// host: database host
// port: database port
// dbname: database name
//
var PSQL = function ( dbopts ) {
2011-08-22 20:33:12 +08:00
2011-11-22 08:06:14 +08:00
var error _text = "Incorrect access parameters. If you are accessing via OAuth, please check your tokens are correct. For public users, please ensure your table is published."
2013-11-18 18:42:43 +08:00
if ( ! dbopts || ( ! _ . isString ( dbopts . user ) && ! _ . isString ( dbopts . dbname ) ) )
{
// console.log("DBOPTS: "); console.dir(dbopts);
throw new Error ( error _text ) ;
}
2011-11-22 08:06:14 +08:00
var me = {
2013-11-18 18:42:43 +08:00
dbopts : dbopts
2011-11-22 08:06:14 +08:00
} ;
me . username = function ( ) {
2013-11-18 18:42:43 +08:00
return this . dbopts . user ;
2011-11-22 08:06:14 +08:00
} ;
2013-11-18 20:31:11 +08:00
me . password = function ( ) {
return this . dbopts . pass ;
} ;
2011-11-22 08:06:14 +08:00
me . database = function ( ) {
2013-11-18 18:42:43 +08:00
return this . dbopts . dbname ;
} ;
me . dbhost = function ( ) {
return this . dbopts . host ;
} ;
2011-11-22 08:06:14 +08:00
2013-11-18 18:42:43 +08:00
me . dbport = function ( ) {
return this . dbopts . port ;
2011-11-22 08:06:14 +08:00
} ;
2013-11-18 20:31:11 +08:00
me . conString = "tcp://" + me . username ( ) +
":" + me . password ( ) + // this line only if not-null ?
"@" +
2013-11-18 18:42:43 +08:00
me . dbhost ( ) + ":" +
me . dbport ( ) + "/" +
2013-05-24 18:06:14 +08:00
me . database ( ) ;
2013-12-02 18:47:52 +08:00
me . dbkey = function ( ) {
return this . database ( ) ; // + ":" + this.dbhost() + ":" + me.dbport();
} ;
2013-10-02 16:22:13 +08:00
me . ensureTypeCache = function ( cb ) {
2013-12-02 18:47:52 +08:00
var db = this . dbkey ( ) ;
2013-10-02 16:22:13 +08:00
if ( extTypeName [ db ] ) { cb ( ) ; return ; }
pg . connect ( this . conString , function ( err , client , done ) {
if ( err ) { cb ( err ) ; return ; }
var types = [ "'geometry'" , "'raster'" ] ; // types of interest
client . query ( "SELECT oid, typname FROM pg_type where typname in (" + types . join ( ',' ) + ")" , function ( err , res ) {
done ( ) ;
if ( err ) { cb ( err ) ; return ; }
var cache = { } ;
res . rows . map ( function ( r ) {
cache [ r . oid ] = r . typname ;
} ) ;
extTypeName [ db ] = cache ;
cb ( ) ;
} ) ;
} ) ;
}
// Return type name for a type identifier
//
// Possibly returns undefined, for unkonwn (uncached)
//
me . typeName = function ( typeId ) {
2013-12-02 18:47:52 +08:00
return stdTypeName [ typeId ] ? stdTypeName [ typeId ] : extTypeName [ this . dbkey ( ) ] [ typeId ] ;
2013-10-02 16:22:13 +08:00
}
me . connect = function ( cb ) {
var that = this ;
this . ensureTypeCache ( function ( err ) {
if ( err ) cb ( err ) ;
else pg . connect ( that . conString , cb ) ;
} ) ;
} ;
2013-05-29 20:36:31 +08:00
me . eventedQuery = function ( sql , callback ) {
var that = this ;
Step (
function ( ) {
that . sanitize ( sql , this ) ;
} ,
function ( err , clean ) {
if ( err ) throw err ;
2013-10-02 16:22:13 +08:00
that . connect ( this ) ;
2013-05-29 20:36:31 +08:00
} ,
function ( err , client , done ) {
2014-06-02 20:48:38 +08:00
var next = this ;
2013-05-29 20:36:31 +08:00
if ( err ) throw err ;
var query = client . query ( sql ) ;
2013-11-19 00:01:06 +08:00
// forward notices to query
var noticeListener = function ( ) {
query . emit ( 'notice' , arguments ) ;
} ;
client . on ( 'notice' , noticeListener ) ;
2013-05-29 20:36:31 +08:00
// NOTE: for some obscure reason passing "done" directly
// as the listener works but can be slower
// (by x2 factor!)
2013-11-19 00:01:06 +08:00
query . on ( 'end' , function ( ) {
client . removeListener ( 'notice' , noticeListener ) ;
done ( ) ;
} ) ;
2014-06-02 20:48:38 +08:00
next ( null , query , client ) ;
2013-05-29 20:36:31 +08:00
} ,
2014-06-02 20:48:38 +08:00
function ( err , query , client ) {
var queryCanceller = function ( ) {
pg . cancel ( undefined , client , query ) ;
} ;
callback ( err , query , queryCanceller ) ;
2013-05-29 20:36:31 +08:00
}
) ;
2013-10-01 23:19:44 +08:00
} ;
me . quoteIdentifier = function ( str ) {
2013-10-02 18:32:07 +08:00
return pg . Client . prototype . escapeIdentifier ( str ) ;
2013-10-01 23:19:44 +08:00
} ;
me . escapeLiteral = function ( s ) {
2013-10-02 18:32:07 +08:00
return pg . Client . prototype . escapeLiteral ( str ) ;
2013-10-01 23:19:44 +08:00
} ;
2013-05-29 20:36:31 +08:00
2013-05-24 16:22:17 +08:00
me . query = function ( sql , callback ) {
2011-11-22 08:06:14 +08:00
var that = this ;
2013-05-24 18:06:14 +08:00
var finish ;
2011-11-22 08:06:14 +08:00
Step (
function ( ) {
that . sanitize ( sql , this ) ;
} ,
function ( err , clean ) {
if ( err ) throw err ;
2013-10-02 16:22:13 +08:00
that . connect ( this ) ;
2011-11-22 08:06:14 +08:00
} ,
2013-05-24 18:06:14 +08:00
function ( err , client , done ) {
2013-05-24 16:38:27 +08:00
if ( err ) throw err ;
2013-05-24 18:06:14 +08:00
finish = done ;
2013-05-24 16:22:17 +08:00
client . query ( sql , this ) ;
2011-11-22 08:06:14 +08:00
} ,
function ( err , res ) {
2013-05-24 18:06:14 +08:00
// Release client to the pool
// should this be postponed to after the callback ?
// NOTE: if we pass a true value to finish() the client
// will be removed from the pool.
// We don't want this. Not now.
if ( finish ) finish ( ) ;
2011-11-22 08:06:14 +08:00
callback ( err , res )
}
) ;
} ;
2013-04-09 17:49:05 +08:00
// throw exception if illegal operations are detected
// NOTE: this check is weak hack, better database
// permissions should be used instead.
2011-11-22 08:06:14 +08:00
me . sanitize = function ( sql , callback ) {
2013-04-09 18:36:37 +08:00
// NOTE: illegal table access is checked in main app
2013-04-09 17:49:05 +08:00
if ( sql . match ( /^\s+set\s+/i ) ) {
var error = new SyntaxError ( "SET command is forbidden" ) ;
error . http _status = 403 ;
callback ( error ) ;
return ;
2011-11-22 08:06:14 +08:00
}
2013-04-09 17:49:05 +08:00
callback ( null , true ) ;
2011-11-22 08:06:14 +08:00
} ;
return me ;
2011-06-13 11:23:02 +08:00
} ;
2014-04-14 21:01:12 +08:00
/ * *
* Little hack for UI
* TODO : drop , fix in the UI ( it ' s not documented in doc / API )
*
* @ param { string } sql
* @ param { number } limit
* @ param { number } offset
* @ returns { string } The wrapped SQL query with the limit window
* /
2013-06-14 17:18:16 +08:00
PSQL . window _sql = function ( sql , limit , offset ) {
2014-04-14 21:01:12 +08:00
// keeping it here for backwards compatibility
return new PSQLWrapper ( sql ) . window ( limit , offset ) . query ( ) ;
} ;
2013-06-14 17:18:16 +08:00
2011-06-13 18:31:50 +08:00
module . exports = PSQL ;