patio
Version:
Patio query engine and ORM
1,070 lines (947 loc) • 39.5 kB
JavaScript
var pg = require("pg"),
PgTypes = require("pg-types"),
QueryStream = require('pg-query-stream'),
comb = require("comb"),
asyncArray = comb.async.array,
string = comb.string,
isHash = comb.isHash,
argsToArray = comb.argsToArray,
pad = string.pad,
format = string.format,
when = comb.when,
array = comb.array,
toArray = array.toArray,
zip = array.zip,
flatten = array.flatten,
Promise = comb.Promise,
isUndefinedOrNull = comb.isUndefinedOrNull,
isUndefined = comb.isUndefined,
isString = comb.isString,
isArray = comb.isArray,
isEmpty = comb.isEmpty,
isBoolean = comb.isBoolean,
isObject = comb.isObject,
isFunction = comb.isFunction,
define = comb.define,
merge = comb.merge,
isDefined = comb.isDefined,
isInstanceOf = comb.isInstanceOf,
QueryError = require("../errors").QueryError,
Dataset = require("../dataset"),
Database = require("../database"),
sql = require("../sql").sql,
stringToIdentifier = sql.stringToIdentifier,
DateTime = sql.DateTime,
Time = sql.Time,
Year = sql.Year,
literal = sql.literal,
StringExpression = sql.StringExpression,
identifier = sql.identifier,
BooleanExpression = sql.BooleanExpression,
LiteralString = sql.LiteralString,
Subscript = sql.Subscript,
patio, DS,
stream = require("stream"),
PassThroughStream = stream.PassThrough,
pipeAll = require("../utils").pipeAll,
hashPick = comb.hash.pick,
isSafeInteger = Number.isSafeInteger || require('is-safe-integer');
var getPatio = function () {
return patio || (patio = require("../index.js"));
};
var isBlank = function (obj) {
var ret = false;
if (isUndefinedOrNull(obj)) {
ret = true;
} else if (isString(obj) || isArray(obj)) {
ret = obj.length === 0;
} else if (isBoolean(obj) && !obj) {
ret = true;
} else if (isObject(obj) && isEmpty(obj)) {
ret = true;
}
return ret;
};
var byteaParser = function (val) {
if (val.toString().indexOf("\\x") === 0) {
val = val.toString().replace(/^\\x/, "");
return new Buffer(val, "hex");
} else {
val = val.toString().replace(/\\([0-7]{3})/g, function (fullMatch, code) {
return String.fromCharCode(parseInt(code, 8));
}).replace(/\\\\/g, "\\");
return new Buffer(val, "binary");
}
};
PgTypes.setTypeParser(17, "text", byteaParser);
PgTypes.setTypeParser(20, 'text', function (val) {
if (!getPatio().parseInt8) {
return val;
}
var i = parseInt(val, 10);
if (!isSafeInteger(i)) {
throw new Error(format("The value '%s' cannot be represented by a javascript number.", val));
}
return i;
});
var timestampOrig = PgTypes.getTypeParser(1114, "text");
PgTypes.setTypeParser(1184, "text", function (val) {
return getPatio().stringToDate(val.toString());
});
PgTypes.setTypeParser(1082, "text", function (val) {
return getPatio().stringToDate(val.toString());
});
PgTypes.setTypeParser(1083, "text", function (val) {
val = String(val);
if (!val.match(/\.(\d{0,3})/)) {
val += ".000";
} else {
val = val.replace(/\.(\d{0,3})$/, function (m, m1) {
return "." + pad(m1, 3, "0", true);
});
}
return getPatio().stringToTime(val.toString(), DS.TIME_FORMAT);
});
PgTypes.setTypeParser(1700, "text", parseFloat);
PgTypes.setTypeParser(114, "text", function (data) {
return getPatio().sql.json(JSON.parse(data));
});
var Connection = define(null, {
instance: {
connection: null,
errored: false,
closed: false,
constructor: function (conn) {
this.connection = conn;
},
closeConnection: function () {
this.closed = true;
this.connection.end();
return new Promise().callback().promise();
},
stream: function (query, opts) {
var ret;
if (!this.closed) {
try {
opts = hashPick(opts || {}, ["batchSize", "highWaterMark"]);
this.connection.setMaxListeners(0);
var fields = [];
query = new QueryStream(query, null, opts);
ret = this.connection.query(query);
var orig = ret.handleRowDescription;
ret.handleRowDescription = function (msg) {
ret.emit("fields", msg.fields);
ret.handleRowDescription = orig;
return orig.apply(ret, arguments);
};
} catch (e) {
ret = new PassThroughStream();
setImmediate(function () {
ret.emit("error", e);
});
}
} else {
ret = new PassThroughStream();
setImmediate(function () {
ret.emit("error", new Error("Connection already closed"));
});
}
return ret;
},
query: function (query) {
var ret = new Promise();
if (!this.closed) {
try {
this.connection.setMaxListeners(0);
var fields = [], rows = [];
var q = this.connection.query(new pg.Query(query))
.on("error", ret.errback)
.on("row", function(row){
rows.push(row);
})
.on("end", function(){
ret.callback(rows, fields);
});
var orig = q.handleRowDescription;
q.handleRowDescription = function (msg) {
fields = msg.fields;
q.handleRowDescription = orig;
return orig.apply(q, arguments);
};
} catch (e) {
ret.errback(e);
}
} else {
ret.errback(new Error("Connection already closed"));
}
return ret.promise();
}
}
});
function colCallback(o) {
return o;
}
DS = define(Dataset, {
instance: {
complexExpressionSql: function (op, args) {
var ret = "";
if (op === "^") {
var j = this._static.XOR_OP, c = false;
args.forEach(function (a) {
if (c) {
ret += j;
}
ret += this.literal(a);
c = true;
}, true);
} else {
return this._super(arguments);
}
return ret;
},
forShare: function () {
return this.lockStyle("share");
},
fullTextSearch: function (cols, terms, opts) {
opts = opts || {};
var lang = opts.language || 'simple';
if (Array.isArray(terms)) {
terms = terms.join(' | ');
}
return this.filter("to_tsvector(?, ?) @@ to_tsquery(?, ?)", lang, this.__fullTextStringJoin(toArray(cols).map(function (c) {
return stringToIdentifier(c);
})), lang, terms);
},
/**
* Lock all tables in the datasets from clause (but not in JOINs), in the specified mode. If
* a function is passed in as the last argument
* @para {String} mode the lock mode (e.g. 'EXCLUSIVE').
* @param {Object} [opts] see {@link patio.Database#transaction} for options.
* @param {Function} [cb] of provided then a new {@link patio.Database} transaction is started.
*
*/
lock: function (mode, opts, cb) {
if (isFunction(opts)) {
cb = opts;
opts = null;
} else {
opts = opts || {};
}
if (isFunction(cb)) {
var self = this;
return this.db.transaction(opts, function () {
return self.lock(mode, opts)
.chain(function () {
return cb.call(self);
});
});
} else {
return this.db.execute(format(this._static.LOCK, [this._sourceList(this.__opts.from), mode]), opts);
}
},
multiInsertSql: function (columns, values) {
var ret = literal('VALUES ');
ret += this.__expressionList(values.map(function (r) {
return toArray(r);
}));
return [this.insertSql(columns.map(function (c) {
return stringToIdentifier(c);
}), literal(ret))];
},
_literalString: function (v) {
return "'" + v.replace(/'/g, "''") + "'";
},
_literalJson: function (v) {
return "'" + JSON.stringify(v).replace(/'/g, "''") + "'";
},
_deleteFromSql: function () {
var self = this._static, space = self.SPACE;
return [space, self.FROM, space, this._sourceList(this.__opts.from[0])].join("");
},
_deleteUsingSql: function () {
return this._joinFromSql("USING");
},
_joinFromSql: function (type) {
var from = this.__opts.from.slice(1), join = this.__opts.join, ret = "";
if (!from.length) {
if (!isEmpty(join)) {
throw new QueryError("Need multiple FROM tables if updating/deleteing a dataset with joins");
}
} else {
var space = this._static.SPACE;
ret = [space, type.toString(), space, this._sourceList(from), this._selectJoinSql()].join("");
}
return ret;
},
_selectLockSql: function () {
if (this.__opts.lock === "share") {
return this._static.FOR_SHARE;
} else {
return this._super(arguments);
}
},
_selectWithSql: function () {
var optsWith = this.__opts["with"];
if (!isEmpty(optsWith) && optsWith.some(function (w) {
return w.recursive;
})) {
return this._static.SQL_WITH_RECURSIVE;
} else {
return this._super(arguments);
}
},
_updateFromSql: function () {
return this._joinFromSql("FROM");
},
_updateTableSql: function () {
return [this._static.SPACE, this._sourceList(this.__opts.from.slice(0, 1))].join("");
},
_quotedIdentifier: function (c) {
return format('"%s"', c);
},
__fullTextStringJoin: function (cols) {
var EMPTY_STRING = this._static.EMPTY_STRING;
cols = toArray(cols).map(function (x) {
return sql.COALESCE(x, EMPTY_STRING);
});
cols = flatten(zip(cols, array.multiply([this._static.SPACE], cols.length)));
cols.pop();
return StringExpression.fromArgs(['||'].concat(cols));
},
insert: function () {
var args = arguments;
if (this.__opts.returning) {
return this._super(arguments);
} else {
var self = this;
return this.primaryKey(this.__opts.from).chain(function (res) {
var pks = res.map(function (r) {
return r.name;
});
var ds = self.returning.apply(self, pks);
var dsPromise = ds.insert.apply(ds, args), l = res.length;
if (l) {
return dsPromise.chain(function (insertRes) {
if (l === 1) {
return insertRes.map(function (i) {
return i[pks[0]];
}).pop();
} else {
return insertRes.pop();
}
});
} else {
return dsPromise;
}
});
}
},
primaryKey: function () {
return this.db.primaryKey(this.__opts.from[0]);
},
__processFields: function (fields) {
var col, colOutputIdentifier, i = -1, l, cols = [],
outputIdentifier = this.outputIdentifier,
selfCols = ( this.__columns = []);
if (fields && fields.length) {
l = fields.length;
while (++i < l) {
colOutputIdentifier = outputIdentifier(col = fields[i].name);
selfCols[i] = colOutputIdentifier;
cols[i] = [colOutputIdentifier, colCallback, col];
}
}
return cols;
},
_literalTimestamp: function (v) {
return this.literal(literal("TIMESTAMP " + this._super(arguments) + ""));
},
_literalBuffer: function (b) {
return this.literal(literal("decode('" + b.toString("hex") + "', 'hex')"));
},
getters: {
columns: function () {
var ret;
if (this.__columns) {
ret = when(this.__columns);
} else {
var self = this;
ret = this.db.schema(this.firstSourceTable).chain(function (schema) {
var columns = (schema ? Object.keys(schema) : []);
self.__columns = columns;
return columns;
});
}
return ret.promise();
},
supportsCteInSubqueries: function () {
return true;
},
supportsDistinctOn: function () {
return true;
},
supportsModifyingJoins: function () {
return true;
},
supportsTimestampTimezones: function () {
return true;
}
}
},
"static": {
ACCESS_SHARE: 'ACCESS SHARE',
ACCESS_EXCLUSIVE: 'ACCESS EXCLUSIVE',
BOOL_FALSE: 'false',
BOOL_TRUE: 'true',
COMMA_SEPARATOR: ', ',
DELETE_CLAUSE_METHODS: Dataset.clauseMethods("delete", 'qualify with from using where returning'),
EXCLUSIVE: 'EXCLUSIVE',
EXPLAIN: 'EXPLAIN ',
EXPLAIN_ANALYZE: 'EXPLAIN ANALYZE ',
FOR_SHARE: ' FOR SHARE',
INSERT_CLAUSE_METHODS: Dataset.clauseMethods("insert", 'with into columns values returning'),
LOCK: 'LOCK TABLE %s IN %s MODE',
NULL: literal('NULL'),
QUERY_PLAN: 'QUERY PLAN',
ROW_EXCLUSIVE: 'ROW EXCLUSIVE',
ROW_SHARE: 'ROW SHARE',
SELECT_CLAUSE_METHODS: Dataset.clauseMethods("select", '' +
'qualify with distinct columns from join where group having compounds order limit lock'),
SHARE: 'SHARE',
SHARE_ROW_EXCLUSIVE: 'SHARE ROW EXCLUSIVE',
SHARE_UPDATE_EXCLUSIVE: 'SHARE UPDATE EXCLUSIVE',
SQL_WITH_RECURSIVE: "WITH RECURSIVE ",
TIMESTAMP_FORMAT: "yyyy-MM-dd HH:mm:ss.SSS",
TIME_FORMAT: "HH:mm:ss.SSS",
UPDATE_CLAUSE_METHODS: Dataset.clauseMethods("update", 'with table set from where returning'),
XOR_OP: ' # ',
CRLF: "\r\n",
BLOB_RE: /[\000-\037\047\134\177-\377]/,
WINDOW: " WINDOW ",
EMPTY_STRING: literal("''")
}
}).as(exports, "PostgresDataset");
var DB = define(Database, {
instance: {
EXCLUDE_SCHEMAS: /pg_*|information_schema/i,
PREPARED_ARG_PLACEHOLDER: new LiteralString('$'),
RE_CURRVAL_ERROR: /currval of sequence "(.*)" is not yet defined in this session|relation "(.*)" does not exist/,
SYSTEM_TABLE_REGEXP: /^pg|sql/,
type: "postgres",
constructor: function () {
this._super(arguments);
this.__primaryKeys = {};
this.__listeners = {};
},
createConnection: function (opts) {
delete opts.query;
var self = this, ret;
var conn = new pg.Client(merge({}, opts, {typeCast: false}));
conn.on("error", function (err) {
self.logWarn("Connection from " + self.uri + " errored removing from pool and reconnecting");
self.logWarn(err.stack);
ret.errored = true;
self.pool.removeConnection(ret);
getPatio().emit('error', err);
});
conn.on("end", function () {
if (!ret.closed) {
self.logWarn("Connection from " + self.uri + " unexpectedly ended");
self.pool.removeConnection(ret);
ret.closed = true;
}
});
conn.connect();
ret = new Connection(conn);
return ret;
},
closeConnection: function (conn) {
return conn.closeConnection();
},
validate: function (conn) {
return new Promise().callback(!(conn.errored)).promise();
},
listen: function (channel, cb, opts) {
opts = opts || {};
channel = this.__quoteSchemaTable(channel);
var listeningChannel = channel.toLowerCase();
var timeout = opts.timeout || 30000,
ret,
connected = true, errored = false;
var self = this;
if (this.quoteIdentifiers) {
listeningChannel = listeningChannel.replace(/^"|"$/g, "");
}
var connectionTimeout = setTimeout(function () {
if (!connected) {
errored = true;
ret.errback(new Error("Listen: Unable to connect to " + channel));
}
}, timeout);
ret = this._getConnection().chain(function (conn) {
function __listener(message) {
if (message.channel === listeningChannel) {
cb(JSON.parse(message.payload));
}
}
if (!errored) {
connected = true;
clearTimeout(connectionTimeout);
conn.connection.on('notification', __listener);
var listeners = conn.__listeners;
if (!listeners) {
listeners = conn.__listeners = {};
}
listeners[channel] = __listener;
var sql = self.__listenSql(channel);
return self.__logAndExecute(sql, function () {
return conn.query(sql);
}).chain(function () {
self.__listeners[channel] = conn;
});
}
});
return ret;
},
listenOnce: function (channel, cb, opts) {
var self = this;
var ret = new Promise(), called = false;
this.listen(channel, function (payload) {
//ensure we are not called twice
if (!called) {
called = true;
self.unListen(channel).chain(function () {
ret.callback(payload);
self = ret = null;
}).addErrback(ret);
}
}, opts).addErrback(ret);
return ret.promise();
},
unListen: function (channel) {
var ret = new Promise().callback(), conn;
channel = this.__quoteSchemaTable(channel);
if (channel in this.__listeners && (conn = this.__listeners[channel])) {
var sql = this.__unListenSql(channel), self = this;
return this.__logAndExecute(sql, sql, function () {
return conn.query(sql);
}).chain(function () {
delete self.__listeners[channel];
conn.connection.removeListener('notification', conn.__listeners[channel]);
return self._returnConnection(conn);
});
}
return ret.promise();
},
notify: function (channel, payload) {
return this.executeDdl(this.__notifySql(this.__quoteSchemaTable(channel), payload));
},
// Use the pg_* system tables to determine indexes on a table
indexes: function (table, opts) {
opts = opts || {};
var m = this.outputIdentifierFunc;
var im = this.inputIdentifierFunc;
var parts = this.__schemaAndTable(table), schema = parts[0];
table = parts[1];
return this.serverVersion().chain(function (version) {
var attNums;
if (version >= 80100) {
attNums = sql.ANY("ind__indkey");
} else {
attNums = [];
for (var i = 0; i < 32; i++) {
attNums.push(new Subscript("ind__indkey", [i]));
}
}
var orderRange = [];
for (var j = 0; j < 32; j++) {
orderRange.push(new Subscript("ind__indkey", [j]));
}
orderRange = sql["case"](orderRange, 32, "att__attnum");
var ds = this.metadataDataset.from("pg_class___tab")
.join("pg_index___ind", [
[identifier("indrelid"), identifier("oid")],
[im(table), "relname"]
])
.join("pg_class___indc", [
[identifier("oid"), identifier("indexrelid")]
])
.join("pg_attribute___att", [
[identifier("attrelid"), identifier("tab__oid")],
[identifier("attnum"), attNums]
])
.filter({"indc__relkind": 'i', "ind__indisprimary": false, indexprs: null, indpred: null})
.order("indc__relname", orderRange)
.select("indc__relname___name", "ind__indisunique___unique", "att__attname___column");
if (schema) {
ds = ds.join("pg_namespace___nsp", {oid: identifier("tab__relnamespace"), nspname: schema.toString()});
}
if (version >= 80200) {
ds = ds.filter({indisvalid: true});
}
if (version >= 80300) {
ds = ds.filter({indisready: true, indcheckxmin: false});
}
var indexes = {};
return ds.forEach(function (r) {
var ident = m(r.name), i = indexes[ident];
if (!i) {
i = indexes[ident] = {columns: [], unique: r.unique};
}
i.columns.push(r.column);
}).chain(function () {
return indexes;
});
});
},
locks: function () {
return this.dataset.from("pg_class").join("pg_locks", {relation: identifier("relfilenode")}).select("pg_class__relname", identifier("pg_locks").all());
},
// Get version of postgres server, used for determined capabilities.
serverVersion: function () {
if (!this.__serverVersion) {
var self = this;
this.__serverVersion = this.get(identifier("version").sqlFunction).chain(function (version) {
var m = version.match(/PostgreSQL (\d+)\.(\d+)(?:(?:rc\d+)|\.(\d+))?/);
version = (parseInt(m[1], 10) * 10000) + (parseInt(m[2], 10) * 100) + parseInt(m[3], 10);
self._serverVersion = version;
return version;
});
}
return this.__serverVersion.promise();
},
/**
* Return an array of table names in the current database.
* The dataset used is passed to the block if one is provided,
* otherwise, an a promise resolved with an array of table names.
*
* Options:
* @param {Object} [opts = {}] options
* @param {String|patio.sql.Identifier} [opts.schema] The schema to search (default_schema by default)
* @param {Function} [cb = null] an optional callback that is invoked with the dataset to retrieve tables.
* @return {Promise} a promise resolved with the table names or the result of the cb if one is provided.
*/
tables: function (opts, cb) {
return this.__pgClassRelname('r', opts, cb);
},
/**
* Return an array of view names in the current database.
*
* Options:
* @param {Object} [opts = {}] options
* @param {String|patio.sql.Identifier} [opts.schema] The schema to search (default_schema by default)
* @return {Promise} a promise resolved with the view names.
*/
views: function (opts) {
return this.__pgClassRelname('v', opts);
},
primaryKey: function (table, opts) {
var ret, quotedTable = this.__quoteSchemaTable(table).toString(), pks = this.__primaryKeys;
if (pks.hasOwnProperty(quotedTable.toString())) {
ret = pks[quotedTable];
} else {
ret = (pks[quotedTable] = this.__primarykey(table));
}
return ret.promise();
},
createMaterializedView: function (name, query, opts) {
opts = opts || {};
opts.materialized = true;
return this.createView(name, query, opts);
},
dropMaterializedView: function (names, opts) {
var args = argsToArray(arguments);
if (isHash(args[args.length - 1])) {
opts = args.pop();
} else {
opts = {};
}
opts.materialized = true;
return this.dropView(args, opts);
},
refreshMaterializedView: function (names, opts) {
if (isArray(names)) {
var self = this, withNoData = opts.noData;
return asyncArray(names).forEach(function (name) {
var sql = "REFRESH MATERIALIZED VIEW %s";
withNoData && (sql += " WITH NO DATA");
return self.executeDdl(format(sql, self.__quoteSchemaTable(name)));
}, null, 1);
} else {
var args = argsToArray(arguments);
opts = isHash(args[args.length - 1]) ? args.pop() : {};
return this.refreshMaterializedView(args, opts);
}
},
__primarykey: function (table) {
var parts = this.__schemaAndTable(table);
var m2 = this.inputIdentifierFunc;
var schema = parts[0];
table = parts[1];
var ds = this.from(table)
.select("pg_attribute__attname___name")
.from("pg_index", "pg_class", "pg_attribute", "pg_namespace")
.where([
[identifier("pg_class__oid"), identifier("pg_attribute__attrelid")],
[identifier("pg_class__relnamespace"), identifier("pg_namespace__oid")],
[identifier("pg_class__oid"), identifier("pg_index__indrelid")],
[identifier("pg_index__indkey").sqlSubscript(0), identifier("pg_attribute__attnum")],
[identifier("indisprimary"), true],
[identifier("pg_class__relname"), m2(table.toString())]
]);
if (schema) {
ds.filter({"pg_namespace__nspname": m2(schema)});
}
return ds.all();
},
_indKeySql: function (key, version) {
var ret = sql.identifier(key);
if (version < 90000) {
ret = sql.literal("string_to_array(textin(int2vectorout(?)), ' ')", ret);
}
return ret;
},
schemaParseTable: function (tableName, opts) {
var self = this,
m = this.outputIdentifierFunc,
m2 = this.inputIdentifierFunc;
return this.serverVersion().chain(function (serverVersion) {
var ds = self.metadataDataset
.select(
"pg_attribute__attname___name",
sql["format_type"]("pg_type__oid", "pg_attribute__atttypmod").as("dbtype"),
sql["pg_get_expr"]("pg_attrdef__adbin", "pg_class__oid").as(literal('"default"')),
sql.NOT("pg_attribute__attnotnull").as("allownull"),
sql.COALESCE(BooleanExpression.fromValuePairs({"pg_attribute__attnum": sql.ANY(self._indKeySql("pg_index__indkey", serverVersion))}), false).as("primarykey"),
"pg_namespace__nspname"
).from("pg_class")
.join("pg_attribute", {attrelid: identifier("oid")})
.join("pg_type", {oid: identifier("atttypid")})
.join("pg_namespace", {oid: identifier("pg_class__relnamespace")})
.leftOuterJoin("pg_attrdef", {adrelid: identifier("pg_class__oid"), adnum: identifier("pg_attribute__attnum")})
.leftOuterJoin("pg_index", {indrelid: identifier("pg_class__oid"), indisprimary: true})
.filter({"pg_attribute__attisdropped": false})
.filter({"pg_attribute__attnum": {gt: 0}})
.filter({"pg_class__relname": m2(tableName)})
.order("pg_attribute__attnum");
ds = self.__filterSchema(ds, opts);
var currentSchema = null;
return ds.map(function (row) {
row.allowNull = row.allownull;
delete row.allownull;
row.primaryKey = row.primarykey;
delete row.primarykey;
row.dbType = row.dbtype;
delete row.dbtype;
var sch = row.nspname;
delete row.nspname;
if (currentSchema) {
if (sch !== currentSchema) {
var error = new Error("columns from two tables were returned please specify a schema");
self.logError(error);
}
} else {
currentSchema = sch;
}
if (isBlank(row["default"])) {
row["default"] = null;
}
row.type = self.schemaColumnType(row.dbType);
var fieldName = m(row.name);
delete row.name;
return [fieldName, row];
});
});
},
__commitTransaction: function (conn, opts) {
opts = opts || {};
var s = opts.prepare;
if (s && this.__transactionDepth <= 1) {
return this.__logConnectionExecute(conn, ["PREPARE TRANSACTION ", this.literal(s)].join(""));
} else {
return this._super(arguments);
}
},
//Backbone of the tables and views support.
__pgClassRelname: function (type, opts, cb) {
var ret;
var ds = this.metadataDataset.from("pg_class")
.filter({relkind: type}).select("relname")
.exclude({relname: {like: this.SYSTEM_TABLE_REGEXP}})
.join("pg_namespace", {oid: identifier("relnamespace")});
ds = this.__filterSchema(ds, opts);
var m = this.outputIdentifierFunc;
if (cb) {
ret = when(cb(ds));
} else {
ret = ds.map(function (r) {
return m(r.relname);
});
}
return ret.promise();
},
//If opts includes a :schema option, or a default schema is used, restrict the dataset to
// that schema. Otherwise, just exclude the default PostgreSQL schemas except for public.
__filterSchema: function (ds, opts) {
opts = opts || {};
var schema = opts.schema, ret = ds;
if (schema) {
ds = ds.filter({"pg_namespace__nspname": schema});
} else {
ds = ds.exclude({"pg_namespace__nspname": this.EXCLUDE_SCHEMAS});
}
return ds;
},
__notifySql: function (channel, payload) {
return format("NOTIFY %s %s", channel, payload ? ", " + this.literal(JSON.stringify(payload)) : "");
},
__listenSql: function (channel) {
return format("LISTEN %s", channel);
},
__unListenSql: function (channel) {
return format("UNLISTEN %s", channel);
},
__dropViewSql: function (name, opts) {
var sql = "DROP";
if (opts.materialized) {
sql += " MATERIALIZED";
}
sql += " VIEW";
if (opts.ifExists) {
sql += " IF EXISTS";
}
sql += " %s";
if (opts.cascade) {
sql += " CASCADE";
}
return format(sql, this.__quoteSchemaTable(name));
},
__createViewSql: function (name, source, opts) {
var sql = "CREATE";
opts = opts || {};
if (opts.replace) {
sql += " OR REPLACE";
}
if (opts.materialized) {
sql += " MATERIALIZED";
} else if (opts.recursize) {
sql += " RECURSIVE";
} else if (opts.temporary || opts.temp) {
sql += " TEMPORARY";
}
sql += " VIEW %s AS %s";
return format(sql, this.__quoteSchemaTable(name), source);
},
__indexDefinitionSql: function (tableName, index) {
tableName = stringToIdentifier(tableName);
var cols = index.columns.map(function (col) {
return stringToIdentifier(col);
}),
indexName = index.name || this.__defaultIndexName(tableName, cols),
o = index.opclass,
indexType = index.type,
unique = index.unique ? "UNIQUE" : "",
filter = index.where || index.filter,
expr;
filter = filter ? ["WHERE ", this.__filterExpr(filter)].join("") : "";
if (isDefined(o)) {
expr = ["(", cols.map(function (c) {
return [this.literal(c), o].join(" ");
}, this).join(", "), ")"].join("");
} else {
expr = this.literal(toArray(cols));
}
switch (indexType) {
case "fullText":
expr = ["(to_tsvector(", this.literal(index.language || "simple"), ", ", this.literal(this.dataset.__fullTextStringJoin(cols)), "))"].join("");
indexType = "gin";
break;
case "spatial" :
indexType = "gist";
break;
}
return ["CREATE", unique, "INDEX", this.__quoteIdentifier(indexName), "ON", this.__quoteSchemaTable(tableName), indexType ? "USING " + indexType : "", expr, filter].join(" ");
},
/*
todo might need this?
__insertResult:function (conn, table, values) {
},
*/
__renameTableSql: function (name, newName) {
return ["ALTER TABLE ", this.__quoteSchemaTable(name), " RENAME TO ", this.__quoteIdentifier(this.__schemaAndTable(newName).pop())].join("");
},
__schemaAutoincrementingPrimaryKey: function (schema) {
return this._super(arguments) && schema.dbType.match(/^(?:integer|bigint)$/i) && schema["default"].match(/^nextval/i);
},
__typeLiteralGenericNumeric: function (column) {
return column.size ? format("numeric(%s)", array.toArray(column.size).join(', ')) : column.isInt ? "integer" : column.isDouble ? "double precision" : "numeric";
},
__typeLiteralGenericDateTime: function (column) {
return "timestamp";
},
//handle bigserial
__typeLiteralGenericBigint: function (column) {
return column.serial ? "bigserial" : this.__typeLiteralSpecific(column);
},
__typeLiteralGenericBlob: function (column) {
return "bytea";
},
//handle serial type
__typeLiteralGenericInteger: function (column) {
return column.serial ? "serial" : this.__typeLiteralSpecific(column);
},
// PostgreSQL prefers the text datatype. If a fixed size is requested,
// the char type is used. If the text type is specifically
// disallowed or there is a size specified, use the varchar type.
// Otherwise use the type type.
__typeLiteralGenericString: function (column) {
if (column.fixed) {
return ["char(", column.size || 255, ")"].join("");
} else if (column.text === false || column.size) {
return ["varchar(", column.size || 255, ")"].join("");
} else {
return 'text';
}
},
// Allow __createTableSql to be passed options for utilizing
// Postgres' table inheritance
__createTableSql: function (name, generator, options) {
options = options || {};
var inherits = options.inherits,
inheritsStr = !isUndefined(inherits) ? " INHERITS (" + inherits + ")" : "";
return format(" %s%s", this._super(arguments), inheritsStr);
},
getters: {
connectionExecuteMethod: function () {
return "query";
},
dataset: function () {
return new DS(this);
},
serialPrimaryKeyOptions: function () {
return {
primaryKey: true,
serial: true,
type: this.defaultPrimaryKeyType
};
},
supportsSavepoints: function () {
return true;
},
supportsTransactionIsolationLevels: function () {
return true;
},
identifierInputMethodDefault: function () {
return null;
},
identifierOutputMethodDefault: function () {
return null;
}
}
},
"static": {
init: function () {
this.setAdapterType("pg");
}
}
}).as(exports, "PostgresDatabase");