UNPKG

patio

Version:
1,095 lines (1,016 loc) 42.4 kB
var comb = require("comb"), define = comb.define, merge = comb.merge, hitch = comb.hitch, when = comb.when, isBoolean = comb.isBoolean, isEmpty = comb.isEmpty, isArray = comb.isArray, isUndefined = comb.isUndefined, isPromiseLike = comb.isPromiseLike, isUndefinedOrNull = comb.isUndefinedOrNull, argsToArray = comb.argsToArray, isFunction = comb.isFunction, format = comb.string.format, Promise = comb.Promise, isNull = comb.isNull, Queue = comb.collections.Queue, sql = require("../sql").sql, PromiseList = comb.PromiseList, errors = require("../errors"), NotImplemented = errors.NotImplemented, stream = require("stream"), PassThroughStream = stream.PassThrough, utils = require("../utils"), pipeAll = utils.pipeAll, resolveOrPromisfyFunction = utils.resolveOrPromisfyFunction; var Database = define(null, { instance: { /**@lends patio.Database.prototype*/ /** * The method name to invoke on a connection. The method name * should be overrode by an adapter if the method to execute * a query is different for the adapter specific connection class. */ connectionExecuteMethod: "execute", /** * The <b>BEGIN</b> SQL fragment used to signify the start of a transaciton. */ SQL_BEGIN: 'BEGIN', /** * The <b>COMMIT</b> SQL fragment used to signify the end of a transaction and the final commit. */ SQL_COMMIT: 'COMMIT', /** * The <b>RELEASE SAVEPOINT</b> SQL fragment used by trasactions when using save points. * The adapter should override this SQL fragment if the adapters SQL is different. * <p> * <b>This fragment will not be used if {@link patio.Database#supportsSavepoints} is false.</b> * </p> */ SQL_RELEASE_SAVEPOINT: 'RELEASE SAVEPOINT autopoint_%d', /** * The <b>ROLLBACK</b> SQL fragment used to rollback a database transaction. * This should be overrode by adapters if the SQL for the adapters * database is different. */ SQL_ROLLBACK: 'ROLLBACK', /** * The <b>ROLLBACK TO SAVEPOINT</b> SQL fragment used to rollback a database transaction * to a particular save point. * This should be overrode by adapters if the SQL for the adapters * database is different. * * <p> * <b>This fragment will not be used if {@link patio.Database#supportsSavepoints} is false.</b> * </p> */ SQL_ROLLBACK_TO_SAVEPOINT: 'ROLLBACK TO SAVEPOINT autopoint_%d', /** * The <b>SAVEPOINT</b> SQL fragment used for creating a save point in a * database transaction. * <p> * <b>This fragment will not be used if {@link patio.Database#supportsSavepoints} is false.</b> * </p> */ SQL_SAVEPOINT: 'SAVEPOINT autopoint_%d', /** * Object containing different database transaction isolation levels. * This object is used to look up the proper SQL when starting a new transaction * and setting the isolation level in the options. * @field */ TRANSACTION_ISOLATION_LEVELS: { uncommitted: 'READ UNCOMMITTED', committed: 'READ COMMITTED', repeatable: 'REPEATABLE READ', serializable: 'SERIALIZABLE' }, /** * @ignore */ POSTGRES_DEFAULT_RE: /^(?:B?('.*')::[^']+|\((-?\d+(?:\.\d+)?)\))$/, /** * @ignore */ MSSQL_DEFAULT_RE: /^(?:\(N?('.*')\)|\(\((-?\d+(?:\.\d+)?)\)\))$/, /** * @ignore */ MYSQL_TIMESTAMP_RE: /^CURRENT_(?:DATE|TIMESTAMP)?$/, /** * @ignore */ STRING_DEFAULT_RE: /^'(.*)'$/, /** * @ignore */ POSTGRES_TIME_PATTERN: "HH:mm:ss", /** * @ignore */ POSTGRES_DATE_TIME_PATTERN: "yyyy-MM-dd HH:mm:ss.SSZ", __transactions: null, /** * @ignore */ constructor: function () { this._super(arguments); this.__transactions = []; this.__transactionQueue = new Queue(); }, /** * Executes the given SQL on the database. This method should be implemented by adapters. * <b>This method should not be called directly by user code.</b> */ execute: function (sql, opts, conn) { var ret; if (opts.stream) { ret = this.__executeStreamed(sql, opts, conn); } else { ret = this.__executePromised(sql, opts, conn); } return ret; }, __executeStreamed: function (sql, opts, conn) { var self = this, ret; if (conn) { var cleanUp = function cleanUp(err) { if (err) { conn.errored = true; } self._returnConnection(conn); ret.removeListener("end", cleanUp); ret.removeListener("error", cleanUp); self = conn = sql = ret = null; }; ret = this.__logAndExecute(sql, opts, function () { return conn.stream(sql, opts); }); ret.on("end", cleanUp); ret.on("error", cleanUp); } else { ret = new PassThroughStream({objectMode: true}); this._getConnection().chain(function (conn) { var queryStream = self.__executeStreamed(sql, opts, conn); function fieldHandler(fields) { ret.emit("fields", fields); queryStream.removeListener("fields", fieldHandler); queryStream = self = ret = null; } queryStream.on("fields", fieldHandler); pipeAll(queryStream, ret); }, function (err) { ret.emit("error", err); }); } return ret; }, __executePromised: function (sql, opts, conn) { var self = this, ret; if (conn) { ret = this.__logAndExecute(sql, opts, function () { return conn[opts.stream ? "stream" : "query"](sql, opts); }); ret.both(function () { var ret = self._returnConnection(conn); self = conn = sql = null; return ret; }); } else { ret = this._getConnection().chain(function (conn) { return self.__executePromised(sql, opts, conn); }); } return ret; }, /** * Return a Promise that is resolved with an object containing index information. * <p> * The keys are index names. Values are objects with two keys, columns and unique. The value of columns * is an array of column names. The value of unique is true or false * depending on if the index is unique. * </p> * * <b>Should not include the primary key index, functional indexes, or partial indexes.</b> * * @example * DB.indexes("artists").chain(function(indexes){ * //e.g. indexes === {artists_name_ukey : {columns : [name], unique : true}}; * }) **/ indexes: function (table, opts) { throw new NotImplemented("indexes should be overridden by adapters"); }, /** * Proxy for {@link patio.Dataset#get}. */ get: function () { return this.dataset.get.apply(this.dataset, arguments); }, /** * @ignore * //todo implement prepared statements * * Call the prepared statement with the given name with the given object * of arguments. * * DB.from("items").filter({id : 1}).prepare("first", "sa"); * DB.call("sa") //=> SELECT * FROM items WHERE id = 1 * */ call: function (psName, hash) { hash = hash || {}; this.preparedStatements[psName](hash); }, /** * Method that should be used when submitting any DDL (Data DefinitionLanguage) SQL, * such as {@link patio.Database#createTable}. By default, calls {@link patio.Database#executeDui}. * <b>This method should not be called directly by user code.</b> */ executeDdl: function (sql, opts) { opts = opts || {}; return this.executeDui(sql, opts); }, /** * Method that should be used when issuing a DELETE, UPDATE, or INSERT * statement. By default, calls {@link patio.Database#execute}. * <b>This method should not be called directly by user code.</b> **/ executeDui: function (sql, opts) { opts = opts || {}; return this.execute(sql, opts); }, /** * Method that should be used when issuing a INSERT * statement. By default, calls {@link patio.Database#executeDui}. * <b>This method should not be called directly by user code.</b> **/ executeInsert: function (sql, opts) { opts = opts || {}; return this.executeDui(sql, opts); }, /** * Runs the supplied SQL statement string on the database server.. * * @example * DB.run("SET some_server_variable = 42") * * @param {String} sql the SQL to run. * @return {Promise} a promise that is resolved with the result of the query. **/ run: function (sql, opts) { opts = opts || {}; return this.executeDdl(sql, opts); }, /** * Parse the schema from the database. * * @example * * DB.schema("artists").chain(function(schema){ * //example schema * { * id : { * type : "integer", * primaryKey : true, * "default" : "nextval('artist_id_seq'::regclass)", * jsDefault : null, * dbType : "integer", * allowNull : false * }, * name : { * type : "string", * primaryKey : false, * "default" : null, * jsDefault : null, * dbType : "text", * allowNull : false * } * } * }) * * @param {String|patio.sql.Identifier|patio.sql.QualifiedIdentifier} table the table to get the schema for. * @param {Object} [opts=null] Additinal options. * @param {boolean} [opts.reload=false] Set to true to ignore any cached results. * @param {String|patio.sql.Identifier} [opts.schema] An explicit schema to use. It may also be implicitly provided * via the table name. * * @return {Promise} Returns a Promise that is resolved with the schema for the given table as an object * where the key is the column name and the value is and object containg column information. The default * column information returned. * <ul> * <li>allowNull : Whether NULL is an allowed value for the column.</li> * <li>dbType : The database type for the column, as a database specific string.</li> * <li>"default" : The database default for the column, as a database specific string.</li> * <li>primaryKey : Whether the columns is a primary key column. If this column is not present, * it means that primary key information is unavailable, not that the column * is not a primary key.</li> * <li>jsDefault : The database default for the column, as a javascript object. In many cases, complex * database defaults cannot be parsed into javascript objects.</li> * <li>type : A string specifying the type, such as "integer" or "string".</li> * <ul> * */ schema: function (table, opts) { if (!isFunction(this.schemaParseTable)) { throw new Error("Schema parsing is not implemented on this database"); } opts = opts || {}; var schemaParts = this.__schemaAndTable(table); var sch = schemaParts[0], tableName = schemaParts[1]; var quotedName = this.__quoteSchemaTable(table); opts = sch && !opts.schema ? merge({schema: sch}, opts) : opts; if (opts.reload) { delete this.schemas[quotedName]; } var self = this; return this.schemaParseTable(tableName, opts).chain(function (cols) { if (!cols || cols.length === 0) { throw new Error("Error parsing schema, " + table + " no columns returns, table probably doesnt exist"); } else { var schema = {}; cols.forEach(function (c) { var name = c[0]; c = c[1]; c.jsDefault = self.__columnSchemaToJsDefault(c["default"], c.type); schema[name] = c; }); return schema; } }); }, /** * Remove the cached schema for the given table name * @example * DB.schema("artists").chain(function(){ * DB.removeCachedSchema("artists"); * }); * @param {String|patio.sql.Identifier|patio.sql.QualifiedIdentifier} the table to remove from this * databases cached schemas. */ removeCachedSchema: function (table) { if (this.schemas && !isEmpty(this.schemas)) { delete this.schemas[this.__quoteSchemaTable(table)]; } }, /** * Determine if a table exists. * @example * comb.executeInOrder(DB, function(DB){ * return { * table1Exists : DB.tableExists("table1"), * table2Exists : DB.tableExists("table2") * }; * }).chain(function(ret){ * //ret.table1Exists === true * //ret.table2Exists === false * }); * @param {String|patio.sql.Identifier|patio.sql.QualifiedIdentifier} the table to remove from this * * @return {Promise} a promise resolved with a boolean indicating if the table exists. */ tableExists: function (table, cb) { const transformedTableName = this.outputIdentifierFunc(table); return this.tables().chain(tables => tables.includes(transformedTableName)).classic(cb).promise(); }, /** * Returns a promise with a list of tables names in this database. This method * should be implemented by the adapter. * * @example * DB.tables().chain(function(tables){ * //e.g. tables === ["table1", "table2", "table3"]; * }); * * @return {Promise} a promise that is resolved with a list of tablenames. */ tables: function () { throw new NotImplemented("tables should be implemented by the adapter"); }, /** * Starts a database transaction. When a database transaction is used, * either all statements are successful or none of the statements are * successful. * <p> * <b>Note</b> that MySQL MyISAM tables do not support transactions.</p> * </p> * * ``` * //normal transaction * DB.transaction(function() { * return comb.when( * this.execute('DROP TABLE test;'), * this.execute('DROP TABLE test2;') * ); * }); * * //transaction with a save point. * DB.transaction(function() { * return this.transaction({savepoint : true}, function() { * return comb.when( * this.execute('DROP TABLE test;'), * this.execute('DROP TABLE test2;') * ); * }); *}); * ``` * * Using a promise. * * ``` * var ds = db.from("user"); * db.transaction(function(){ * return ds.insert({ * firstName:"Jane", * lastName:"Gorgenson", * password:"password", * dateOfBirth:new Date(1956, 1, 3) * }).chain(function(){ * return ds.forEach(function(user){ * return ds.where({id:user.id}).update({firstName:user.firstName + 1}); * }); * }); * }); * ``` * * Using the done method * ``` * var ds = db.from("user"); * db.transaction(function(db, done){ * ds.insert({ * firstName:"Jane", * lastName:"Gorgenson", * password:"password", * dateOfBirth:new Date(1956, 1, 3) * }).chain(function(){ * ds.forEach(function(user){ * return ds.where({id:user.id}).update({firstName:user.firstName + 1}); * }).classic(done) * }); * }); * ``` * * ``` * //WITH ISOLATION LEVELS * * db.supportsTransactionIsolationLevels = true; * //BEGIN * //SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED * //DROP TABLE test1' * //COMMIT * DB.transaction({isolation:"uncommited"}, function(d) { * return d.run("DROP TABLE test1"); * }); * * //BEGIN * //SET TRANSACTION ISOLATION LEVEL READ COMMITTED * //DROP TABLE test1 * //COMMIT * DB.transaction({isolation:"committed"}, function(d) { * return d.run("DROP TABLE test1"); * }); * * //BEGIN * //SET TRANSACTION ISOLATION LEVEL REPEATABLE READ' * //DROP TABLE test1 * //COMMIT * DB.transaction({isolation:"repeatable"}, function(d) { * return d.run("DROP TABLE test1"); * }); * * //BEGIN * //SET TRANSACTION ISOLATION LEVEL SERIALIZABLE * //DROP TABLE test1 * //COMMIT * DB.transaction({isolation:"serializable"}, function(d) { * return d.run("DROP TABLE test1"); * }); * * //With an Error * //BEGIN * //DROP TABLE test * //ROLLBACK * DB.transaction(function(d) { * d.execute('DROP TABLE test'); * throw "Error"; * }); * ``` * * @param {Object} [opts={}] options to use when performing the transaction. * @param {String} [opts.isolated] This will ensure that the transaction will be run on its own connection and * not part of another transaction if one is already in progress. * @param {String} [opts.isolation] The transaction isolation level to use for this transaction, * should be "uncommitted", "committed", "repeatable", or "serializable", * used if given and the database/adapter supports customizable * transaction isolation levels. * @param {String} [opts.prepare] A string to use as the transaction identifier for a * prepared transaction (two-phase commit), if the database/adapter * supports prepared transactions. * @param {Boolean} [opts.savepoint] Whether to create a new savepoint for this transaction, * only respected if the database/adapter supports savepoints. By * default patio will reuse an existing transaction, so if you want to * use a savepoint you must use this option. * @param {Function} cb a function used to perform the transaction. This function is * called in the scope of the database by default so one can use this. The funciton is also * called with the database as the first argument, and a function to be called when the tranaction is complete. * If you return a promise from the transaction block then you do not need to call the done cb. * * * @return {Promise} a promise that is resolved once the transaction is complete. **/ transaction: function (opts, cb) { if (isFunction(opts)) { cb = opts; opts = {}; } else { opts = opts || {}; } var ret; if (!this.__alreadyInTransaction) { this.__alreadyInTransaction = true; ret = this.__transaction(null, opts, cb); } else { ret = this.__enqueueTransaction(opts, cb); } return ret.promise(); }, __enqueueTransaction: function (opts, cb) { var ret = new Promise(), self = this; var transaction = function () { self.__transaction(null, opts, cb).chain(ret.callback, ret.errback); }; this.__transactionQueue.enqueue(transaction); return ret.promise(); }, __transactionProxy: function (cb, conn) { var promises = []; var repl = []; //set up our proxy methos ["transaction", "execute"].forEach(function (n) { var orig = this[n]; repl.push({name: n, orig: orig}); this[n] = function (arg1, arg2) { var ret; try { if (n === "transaction") { if (comb.isHash(arg1) && arg1.isolated) { return this.__enqueueTransaction(arg1, arg2); } else { //if its a transaction with no options then we just create a promise from what ever is returned if (isFunction(arg1)) { ret = resolveOrPromisfyFunction(arg1, this, this); } else { if (this.supportsSavepoints && arg1.savepoint) { //if we support save points there is a save point option then we //use __transaction again with the previous connection ret = this.__transaction(conn, arg1, arg2); } else { //other wise use the function passed in to get the returned promise ret = resolveOrPromisfyFunction(arg2, this, this); } } } } else { ret = orig.apply(this, argsToArray(arguments).concat(conn)); } } catch (e) { ret = new Promise().errback(e); } if (comb.isInstanceOf(ret, stream.Stream)) { promises.push(comb.promisfyStream(ret)); } else { promises.push((ret = when(ret))); ret = ret.promise(); } return ret; }; }, this); try { promises.push(resolveOrPromisfyFunction(cb, this, this)); } catch (e) { promises.push(new Promise().errback(e)); } if (promises.length === 0) { promises.push(new Promise().callback()); } var self = this; return new PromiseList(promises).both(function () { repl.forEach(function (o) { self[o.name] = o.orig; }); }).promise(); }, _getConnection: function () { return this.pool.getConnection(); }, _returnConnection: function (conn) { if (!this.alreadyInTransaction(conn)) { this.pool.returnConnection(conn); } }, __getTransactionConnection: function (conn) { var self = this; return conn ? when(conn) : this._getConnection().chain(function (conn) { //add the connection to the transactions self.__transactions.push(conn); //reset transaction depth to 0, this is used for keeping track of save points. conn.__transactionDepth = 0; return conn; }); }, __transaction: function (conn, opts, cb) { var promise = new Promise(); try { var self = this; return this.__getTransactionConnection(conn).chain(function (conn) { return self.__beginTransaction(conn, opts).chain(function () { return self.__transactionProxy(cb, conn) .chain(function () { return self.__commitTransaction(conn).chain(function (res) { self.__finishTransactionAndCheckForMore(conn); return res; }, function errorHandler(err) { self.__finishTransactionAndCheckForMore(conn); throw err; }); }, function (err) { return self.__rollback(conn, err); }); }); }); } catch (e) { this.logError(e); promise.errback(e); } return promise.promise(); }, __transactionComplete: function (promise, type, conn) { this.__finishTransactionAndCheckForMore(conn); promise[type].apply(promise, argsToArray(arguments).slice(3)); }, __rollback: function (conn, err) { return this.__rollbackTransaction(conn, null, err) .both(hitch(this, "__finishTransactionAndCheckForMore", conn)) .chain(hitch(this, function () { if (conn.__transactionDepth <= 1) { this.__transactionError(err); } else { throw err; } })); }, __transactionError: function (err) { if (isArray(err)) { for (var i in err) { if (i in err) { var e = err[i]; if (isArray(e) && e.length === 2) { var realE = e[1]; if (realE !== "ROLLBACK") { throw realE; } break; } else { throw e; } } } } else { if (err !== "ROLLBACK") { throw err; } } }, __finishTransactionAndCheckForMore: function (conn) { if (this.alreadyInTransaction(conn)) { if (!this.supportsSavepoints || ((conn.__transactionDepth -= 1) <= 0)) { if (conn) { this.pool.returnConnection(conn); } var index, transactions = this.__transactions; if ((index = transactions.indexOf(conn)) > -1) { transactions.splice(index, 1); } if (!this.__transactionQueue.isEmpty) { this.__transactionQueue.dequeue()(); } else { this.__alreadyInTransaction = false; } } } }, //SQL to start a new savepoint __beginSavepointSql: function (depth) { return format(this._static.SQL_SAVEPOINT, depth); }, // Start a new database connection on the given connection __beginNewTransaction: function (conn, opts) { var self = this; return this.__logConnectionExecute(conn, this.beginTransactionSql).chain(function () { return self.__setTransactionIsolation(conn, opts); }); }, //Start a new database transaction or a new savepoint on the given connection. __beginTransaction: function (conn, opts) { var ret; if (this.supportsSavepoints) { if (conn.__transactionDepth > 0) { ret = this.__logConnectionExecute(conn, this.__beginSavepointSql(conn.__transactionDepth)); } else { ret = this.__beginNewTransaction(conn, opts); } conn.__transactionDepth += 1; } else { ret = this.__beginNewTransaction(conn, opts); } return ret; }, // SQL to commit a savepoint __commitSavepointSql: function (depth) { return format(this.SQL_RELEASE_SAVEPOINT, depth); }, //Commit the active transaction on the connection __commitTransaction: function (conn, opts) { opts = opts || {}; if (this.supportsSavepoints) { var depth = conn.__transactionDepth; var sql = null; if (depth > 1) { sql = this.__commitSavepointSql(depth - 1); } else { this.__commiting = true; sql = this.commitTransactionSql; } return this.__logConnectionExecute(conn, (sql)); } else { this.__commiting = true; return this.__logConnectionExecute(conn, this.commitTransactionSql); } }, //SQL to rollback to a savepoint __rollbackSavepointSql: function (depth) { return format(this.SQL_ROLLBACK_TO_SAVEPOINT, depth); }, //Rollback the active transaction on the connection __rollbackTransaction: function (conn, opts) { opts = opts || {}; if (this.supportsSavepoints) { var sql, depth = conn.__transactionDepth; if (depth > 1) { sql = this.__rollbackSavepointSql(depth - 1); } else { this.__commiting = true; sql = this.rollbackTransactionSql; } return this.__logConnectionExecute(conn, sql); } else { this.__commiting = false; return this.__logConnectionExecute(conn, this.rollbackTransactionSql); } }, // Set the transaction isolation level on the given connection __setTransactionIsolation: function (conn, opts) { var level; var ret; if (this.supportsTransactionIsolationLevels && !isUndefinedOrNull(level = isUndefinedOrNull(opts.isolation) ? this.transactionIsolationLevel : opts.isolation)) { ret = this.__logConnectionExecute(conn, this.__setTransactionIsolationSql(level)); } else { ret = new Promise().callback(); } return ret.promise(); }, // SQL to set the transaction isolation level __setTransactionIsolationSql: function (level) { return format("SET TRANSACTION ISOLATION LEVEL %s", this.TRANSACTION_ISOLATION_LEVELS[level]); }, //Convert the given default, which should be a database specific string, into //a javascript object. __columnSchemaToJsDefault: function (def, type) { if (isNull(def) || isUndefined(def)) { return null; } var origDefault = def, m, datePattern, dateTimePattern, timeStampPattern, timePattern; if (this.type === "postgres" && (m = def.match(this.POSTGRES_DEFAULT_RE)) !== null) { def = m[1] || m[2]; dateTimePattern = this.POSTGRES_DATE_TIME_PATTERN; timePattern = this.POSTGRES_TIME_PATTERN; } if (this.type === "mssql" && (m = def.match(this.MSSQL_DEFAULT_RE)) !== null) { def = m[1] || m[2]; } if (["string", "blob", "date", "datetime", "year", "timestamp", "time", "enum"].indexOf(type) !== -1) { if (this.type === "mysql") { if (["date", "datetime", "time", "timestamp"].indexOf(type) !== -1 && def.match(this.MYSQL_TIMESTAMP_RE)) { return null; } origDefault = def = "'" + def + "'".replace("\\", "\\\\"); } if (!(m = def.match(this.STRING_DEFAULT_RE))) { return null; } def = m[1].replace("''", "'"); } var ret = null; try { switch (type) { case "boolean": if (def.match(/[f0]/i)) { ret = false; } else if (def.match(/[t1]/i)) { ret = true; } else if (isBoolean(def)) { ret = def; } break; case "blob": ret = new Buffer(def); break; case "string": case "enum": ret = def; break; case "integer": ret = parseInt(def, 10); if (isNaN(ret)) { ret = null; } break; case "float": case "decimal": ret = parseFloat(def, 10); if (isNaN(ret)) { ret = null; } break; case "year" : ret = this.patio.stringToYear(def); break; case "date": ret = this.patio.stringToDate(def, datePattern); break; case "timestamp": ret = this.patio.stringToTimeStamp(def, timeStampPattern); break; case "datetime": ret = this.patio.stringToDateTime(def, dateTimePattern); break; case "time": ret = this.patio.stringToTime(def, timePattern); break; } } catch (e) { } return ret; }, /** * Match the database's column type to a javascript type via a * regular expression, and return the javascript type as a string * such as "integer" or "string". * @private */ schemaColumnType: function (dbType) { var ret = dbType, m; if (dbType.match(/^interval$/i)) { ret = "interval"; } else if (dbType.match(/^(character( varying)?|n?(var)?char)/i)) { ret = "string"; } else if (dbType.match(/^int(eger)?|(big|small|tiny)int/i)) { ret = "integer"; } else if (dbType.match(/^date$/i)) { ret = "date"; } else if (dbType.match(/^year/i)) { ret = "year"; } else if (dbType.match(/^((small)?datetime|timestamp( with(out)? time zone)?)$/i)) { ret = "datetime"; } else if (dbType.match(/^time( with(out)? time zone)?$/i)) { ret = "time"; } else if (dbType.match(/^(bit|boolean)$/i)) { ret = "boolean"; } else if (dbType.match(/^(real|float|double( precision)?)$/i)) { ret = "float"; } else if ((m = dbType.match(/^(?:(?:(?:num(?:ber|eric)?|decimal|double)(?:\(\d+,\s*(\d+)\))?)|(?:small)?money)/i))) { ret = m[1] && m[1] === '0' ? "integer" : "decimal"; } else if (dbType.match(/n?text/i)) { ret = "text"; } else if (dbType.match(/bytea|[bc]lob|image|(var)?binary/i)) { ret = "blob"; } else if (dbType.match(/^enum/i)) { ret = "enum"; } else if (dbType.match(/^set/i)) { ret = "set"; } else if (dbType.match(/^json/i)) { ret = "json"; } return ret; }, /** * Returns true if this DATABASE is currently in a transaction. * * @param opts * @return {Boolean} true if this dabase is currently in a transaction. */ alreadyInTransaction: function (conn, opts) { opts = opts || {}; return this.__transactions.indexOf(conn) !== -1 && (!this.supportsSavepoints || !opts.savepoint); }, /**@ignore*/ getters: { /**@lends patio.Database.prototype*/ /** * SQL to BEGIN a transaction. * See {@link patio.Database#SQL_BEGIN} for default, * @field * @type String */ beginTransactionSql: function () { return this.SQL_BEGIN; }, /** * SQL to COMMIT a transaction. * See {@link patio.Database#SQL_COMMIT} for default, * @field * @type String */ commitTransactionSql: function () { return this.SQL_COMMIT; }, /** * SQL to ROLLBACK a transaction. * See {@link patio.Database#SQL_ROLLBACK} for default, * @field * @type String */ rollbackTransactionSql: function () { return this.SQL_ROLLBACK; }, /** * Return a function for the dataset's {@link patio.Dataset#outputIdentifierMethod}. * Used in metadata parsing to make sure the returned information is in the * correct format. * * @field * @type Function */ outputIdentifierFunc: function () { var ds = this.dataset; return function (ident) { return ds.outputIdentifier(ident); }; }, /** * Return a function for the dataset's {@link patio.Dataset#inputIdentifierMethod}. * Used in metadata parsing to make sure the returned information is in the * correct format. * * @field * @type Function */ inputIdentifierFunc: function () { var ds = this.dataset; return function (ident) { return ds.inputIdentifier(ident); }; }, /** * Return a dataset that uses the default identifier input and output methods * for this database. Used when parsing metadata so that column are * returned as expected. * * @field * @type patio.Dataset */ metadataDataset: function () { if (this.__metadataDataset) { return this.__metadataDataset; } var ds = this.dataset; ds.identifierInputMethod = this.identifierInputMethod; ds.identifierOutputMethod = this.identifierOutputMethod; this.__metadataDataset = ds; return ds; } } }, "static": { SQL_BEGIN: 'BEGIN', SQL_COMMIT: 'COMMIT', SQL_RELEASE_SAVEPOINT: 'RELEASE SAVEPOINT autopoint_%d', SQL_ROLLBACK: 'ROLLBACK', SQL_ROLLBACK_TO_SAVEPOINT: 'ROLLBACK TO SAVEPOINT autopoint_%d', SQL_SAVEPOINT: 'SAVEPOINT autopoint_%d', TRANSACTION_BEGIN: 'Transaction.begin', TRANSACTION_COMMIT: 'Transaction.commit', TRANSACTION_ROLLBACK: 'Transaction.rollback', TRANSACTION_ISOLATION_LEVELS: { uncommitted: 'READ UNCOMMITTED', committed: 'READ COMMITTED', repeatable: 'REPEATABLE READ', serializable: 'SERIALIZABLE' }, POSTGRES_DEFAULT_RE: /^(?:B?('.*')::[^']+|\((-?\d+(?:\.\d+)?)\))$/, MSSQL_DEFAULT_RE: /^(?:\(N?('.*')\)|\(\((-?\d+(?:\.\d+)?)\)\))$/, MYSQL_TIMESTAMP_RE: /^CURRENT_(?:DATE|TIMESTAMP)?$/, STRING_DEFAULT_RE: /^'(.*)'$/ } }).as(module);