diff --git a/dist/browser-connector.js b/dist/browser-connector.js index 1c33d91d..ae1c7e9d 100644 --- a/dist/browser-connector.js +++ b/dist/browser-connector.js @@ -114,8 +114,6 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; - var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - var _helpers = __webpack_require__(10); var helpers = _interopRequireWildcard(_helpers); @@ -124,7 +122,7 @@ var _mapdClientV2 = _interopRequireDefault(_mapdClientV); - var _processQueryResults = __webpack_require__(117); + var _processQueryResults = __webpack_require__(118); var _processQueryResults2 = _interopRequireDefault(_processQueryResults); @@ -132,9 +130,7 @@ function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - - var _ref = isNodeRuntime() && __webpack_require__(116) || window, + var _ref = isNodeRuntime() && __webpack_require__(117) || window, TDatumType = _ref.TDatumType, TEncodingType = _ref.TEncodingType, TPixel = _ref.TPixel; // eslint-disable-line global-require @@ -143,7 +139,7 @@ var MapDThrift = isNodeRuntime() && __webpack_require__(13); // eslint-disable-line global-require var Thrift = isNodeRuntime() && __webpack_require__(14) || window.Thrift; // eslint-disable-line global-require var thriftWrapper = Thrift; - var parseUrl = isNodeRuntime() && __webpack_require__(59).parse; // eslint-disable-line global-require + var parseUrl = isNodeRuntime() && __webpack_require__(60).parse; // eslint-disable-line global-require if (isNodeRuntime()) { // Because browser Thrift and Node Thrift are exposed slightly differently. Thrift = Thrift.Thrift; @@ -152,1245 +148,837 @@ } - var COMPRESSION_LEVEL_DEFAULT = 3; - - function arrayify(maybeArray) { - return Array.isArray(maybeArray) ? maybeArray : [maybeArray]; - } - - function isNodeRuntime() { - return typeof window === "undefined"; + // Set a global Connector function when Connector is brought in via script tag. + if (( false ? "undefined" : _typeof(module)) === "object" && module.exports) { + if (!isNodeRuntime()) { + window.Connector = Connector; + } } + module.exports = Connector; + exports.default = Connector; - var MapdCon = function () { - function MapdCon() { - var _this = this; - - _classCallCheck(this, MapdCon); - - this.updateQueryTimes = function (conId, queryId, estimatedQueryTime, execution_time_ms) { - _this.queryTimes[queryId] = execution_time_ms; - }; - - this.getFrontendViews = function (callback) { - if (_this._sessionId) { - _this._client[0].get_frontend_views(_this._sessionId[0], callback); - } else { - callback(new Error("No Session ID")); - } - }; - - this.getFrontendViewsAsync = function () { - return new Promise(function (resolve, reject) { - _this.getFrontendViews(function (error, views) { - if (error) { - reject(error); - } else { - resolve(views); - } - }); - }); - }; - - this.getFrontendView = function (viewName, callback) { - if (_this._sessionId && viewName) { - _this._client[0].get_frontend_view(_this._sessionId[0], viewName, callback); - } else { - callback(new Error("No Session ID")); - } - }; - - this.getFrontendViewAsync = function (viewName) { - return new Promise(function (resolve, reject) { - _this.getFrontendView(viewName, function (err, view) { - if (err) { - reject(err); - } else { - resolve(view); - } - }); - }); - }; - - this.getServerStatus = function (callback) { - _this._client[0].get_server_status(_this._sessionId[0], callback); - }; - - this.getServerStatusAsync = function () { - return new Promise(function (resolve, reject) { - _this.getServerStatus(function (err, result) { - if (err) { - reject(err); - } else { - resolve(result); - } - }); - }); - }; - - this.deleteFrontendViewAsync = function (viewName) { - return new Promise(function (resolve, reject) { - _this.deleteFrontendView(viewName, function (err) { - if (err) { - reject(err); - } else { - resolve(viewName); - } - }); - }); - }; - this.getLinkView = function (link, callback) { - _this._client[0].get_link_view(_this._sessionId[0], link, callback); - }; - - this.getLinkViewAsync = function (link) { - return new Promise(function (resolve, reject) { - _this.getLinkView(link, function (err, theLink) { - if (err) { - reject(err); - } else { - resolve(theLink); - } - }); - }); - }; - - this.queryAsync = this.query; - - this.createTableAsync = function (tableName, rowDescObj, tableType) { - return new Promise(function (resolve, reject) { - _this.createTable(tableName, rowDescObj, tableType, function (err) { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); - }; - - this.importTableAsync = this.importTableAsyncWrapper(false); - this.importTableGeoAsync = this.importTableAsyncWrapper(true); - - this._host = null; - this._user = null; - this._password = null; - this._port = null; - this._dbName = null; - this._client = null; - this._sessionId = null; - this._protocol = null; - this._datumEnum = {}; - this._logging = false; - this._platform = "mapd"; - this._nonce = 0; - this._balanceStrategy = "adaptive"; - this._numConnections = 0; - this._lastRenderCon = 0; - this.queryTimes = {}; - this.serverQueueTimes = null; - this.serverPingTimes = null; - this.pingCount = null; - this.DEFAULT_QUERY_TIME = 50; - this.NUM_PINGS_PER_SERVER = 4; - this.importerRowDesc = null; - - // invoke initialization methods - this.invertDatumTypes(); - - this.processResults = function () { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var result = arguments[1]; - var callback = arguments[2]; - - var processor = (0, _processQueryResults2.default)(_this._logging, _this.updateQueryTimes); - var processResultsObject = processor(options, _this._datumEnum, result, callback); - return processResultsObject; - }; - - // return this to allow chaining off of instantiation - return this; - } + var COMPRESSION_LEVEL_DEFAULT = 3; + var DEFAULT_QUERY_TIME = 50; + + function Connector() { + // initialize variables + var _datumEnum = {}; + var _queryTimes = {}; + var _client = null; + var _dbName = null; + var _host = null; + var _logging = false; + var _nonce = 0; + var _password = null; + var _port = null; + var _protocol = null; + var _sessionId = null; + var _user = null; + + // invoke initialization methods + publicizeMethods(this, [connect, createFrontendView, createLink, createTable, dbName, deleteFrontendView, detectColumnTypes, disconnect, getFields, getFrontendView, getFrontendViews, getLinkView, getPixel, getServerStatus, getTables, host, importShapeTable, importTable, logging, password, port, protocol, query, renderVega, sessionId, user, validateQuery]); + invertDatumTypes(_datumEnum); + + // return this to allow chaining off of instantiation + return this; + // public methods /** * Create a connection to the server, generating a client and session id. - * @param {Function} callback A callback that takes `(err, success)` as its signature. Returns con singleton on success. - * @return {MapdCon} Object + * @param {Function} callback (error, session) => {…} + * @returns {undefined} * * @example Connect to a MapD server: - * var con = new MapdCon() + * var con = new Connector() * .host('localhost') * .port('8080') * .dbName('myDatabase') * .user('foo') * .password('bar') - * .connect((err, con) => console.log(con.sessionId())); + * .connect((error, con) => console.log(con.sessionId())); * * // ["om9E9Ujgbhl6wIzWgLENncjWsaXRDYLy"] */ + function connect(callback) { + var _this = this; - - _createClass(MapdCon, [{ - key: "connect", - value: function connect(callback) { - var _this2 = this; - - if (this._sessionId) { - this.disconnect(); - } - - // TODO: should be its own function - var allAreArrays = Array.isArray(this._host) && Array.isArray(this._port) && Array.isArray(this._user) && Array.isArray(this._password) && Array.isArray(this._dbName); - if (!allAreArrays) { - return callback("All connection parameters must be arrays."); - } - - this._client = []; - this._sessionId = []; - - if (!this._user[0]) { - return callback("Please enter a username."); - } else if (!this._password[0]) { - return callback("Please enter a password."); - } else if (!this._dbName[0]) { - return callback("Please enter a database."); - } else if (!this._host[0]) { - return callback("Please enter a host name."); - } else if (!this._port[0]) { - return callback("Please enter a port."); - } - - // now check to see if length of all arrays are the same and > 0 - var hostLength = this._host.length; - if (hostLength < 1) { - return callback("Must have at least one server to connect to."); - } - if (hostLength !== this._port.length || hostLength !== this._user.length || hostLength !== this._password.length || hostLength !== this._dbName.length) { - return callback("Array connection parameters must be of equal length."); - } - - if (!this._protocol) { - this._protocol = this._host.map(function () { - return window.location.protocol.replace(":", ""); - }); + // eslint-disable-line consistent-return + if (_sessionId) { + disconnect(); + } + + if ([_host, _port, _user, _password, _dbName].some(Array.isArray)) { + console.warn("Connection parameters as arrays is deprecated; use single values."); // eslint-disable-line no-console + _host = Array.isArray(_host) ? _host[0] : _host; + _port = Array.isArray(_port) ? _port[0] : _port; + _user = Array.isArray(_user) ? _user[0] : _user; + _password = Array.isArray(_password) ? _password[0] : _password; + _dbName = Array.isArray(_dbName) ? _dbName[0] : _dbName; + } + + if (!_user) { + return callback("Please enter a username."); + } else if (!_password) { + return callback("Please enter a password."); + } else if (!_dbName) { + return callback("Please enter a database."); + } else if (!_host) { + return callback("Please enter a host name."); + } else if (!_port) { + return callback("Please enter a port."); + } + + _client = null; + _sessionId = null; + _protocol = _protocol || window.location.protocol.replace(":", ""); + + var transportUrl = _protocol + "://" + _host + ":" + _port; + var client = null; + + if (isNodeRuntime()) { + var _parseUrl = parseUrl(transportUrl), + parsedProtocol = _parseUrl.protocol, + parsedHost = _parseUrl.hostname, + parsedPort = _parseUrl.port; + + var connection = thriftWrapper.createHttpConnection(parsedHost, parsedPort, { + transport: thriftWrapper.TBufferedTransport, + protocol: thriftWrapper.TJSONProtocol, + path: "/", + headers: { Connection: "close" }, + https: parsedProtocol === "https:" + }); + connection.on("error", console.error); // eslint-disable-line no-console + client = thriftWrapper.createClient(MapDThrift, connection); + resetThriftClientOnArgumentErrorForMethods(this, client, Object.keys(this)); + } else { + var thriftTransport = new Thrift.Transport(transportUrl); + var thriftProtocol = new Thrift.Protocol(thriftTransport); + client = new _mapdClientV2.default(thriftProtocol); + } + client.connect(_user, _password, _dbName, function (error, newSessionId) { + // eslint-disable-line no-loop-func + if (error) { + return callback(normalizeError(error)); } + _client = client; + _sessionId = newSessionId; + return callback(null, _this); + }); + } + /** + * Disconnect from the server then clears the client and session values. + * @param {Function} callback (error) => {…} + * @returns {undefined} + * + * @example Disconnect from the server: + * + * con.sessionId() // ["om9E9Ujgbhl6wIzWgLENncjWsaXRDYLy"] + * con.disconnect((err) => { + * console.error(err); + * con.sessionId() === null; + * }) + */ + function disconnect() { + var _this2 = this; - var transportUrls = this.getEndpoints(); - - var _loop = function _loop(h) { - var client = null; - - if (isNodeRuntime()) { - var _parseUrl = parseUrl(transportUrls[h]), - protocol = _parseUrl.protocol, - hostname = _parseUrl.hostname, - port = _parseUrl.port; + var callback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : noop; - var connection = thriftWrapper.createHttpConnection(hostname, port, { - transport: thriftWrapper.TBufferedTransport, - protocol: thriftWrapper.TJSONProtocol, - path: "/", - headers: { Connection: "close" }, - https: protocol === "https:" - }); - connection.on("error", console.error); // eslint-disable-line no-console - client = thriftWrapper.createClient(MapDThrift, connection); - resetThriftClientOnArgumentErrorForMethods(_this2, client, ["connect", "createFrontendViewAsync", "createLinkAsync", "createTableAsync", "dbName", "deleteFrontendViewAsync", "detectColumnTypesAsync", "disconnect", "getFields", "getFrontendViewAsync", "getFrontendViewsAsync", "getLinkViewAsync", "getResultRowForPixel", "getServerStatusAsync", "getTablesAsync", "host", "importTableAsync", "importTableGeoAsync", "logging", "password", "port", "protocol", "query", "renderVega", "sessionId", "user", "validateQuery"]); - } else { - var thriftTransport = new Thrift.Transport(transportUrls[h]); - var thriftProtocol = new Thrift.Protocol(thriftTransport); - client = new _mapdClientV2.default(thriftProtocol); + if (_sessionId !== null) { + _client.disconnect(_sessionId, function (error) { + // eslint-disable-line no-loop-func + if (error) { + return callback(error, _this2); } - - client.connect(_this2._user[h], _this2._password[h], _this2._dbName[h], function (error, sessionId) { - if (error) { - callback(error); - return; - } - _this2._client.push(client); - _this2._sessionId.push(sessionId); - _this2._numConnections = _this2._client.length; - callback(null, _this2); - }); - }; - - for (var h = 0; h < hostLength; h++) { - _loop(h); - } - - return this; + _sessionId = null; + _client = null; + return callback(null, _this2); + }); } - }, { - key: "convertFromThriftTypes", - value: function convertFromThriftTypes(fields) { - var fieldsArray = []; - // silly to change this from map to array - // - then later it turns back to map - for (var key in fields) { - if (fields.hasOwnProperty(key)) { - fieldsArray.push({ - name: key, - type: this._datumEnum[fields[key].col_type.type], - is_array: fields[key].col_type.is_array, - is_dict: fields[key].col_type.encoding === TEncodingType.DICT // eslint-disable-line no-undef - }); - } - } - return fieldsArray; - } - - /** - * Disconnect from the server then clears the client and session values. - * @return {MapdCon} Object - * @param {Function} callback A callback that takes `(err, success)` as its signature. Returns con singleton on success. - * - * @example Disconnect from the server: - * - * con.sessionId() // ["om9E9Ujgbhl6wIzWgLENncjWsaXRDYLy"] - * con.disconnect((err, con) => console.log(err, con)) - * con.sessionId() === null; - */ - - }, { - key: "disconnect", - value: function disconnect(callback) { - var _this3 = this; - - if (this._sessionId !== null) { - for (var c = 0; c < this._client.length; c++) { - this._client[c].disconnect(this._sessionId[c], function (error) { - // Success will return NULL - - if (error) { - return callback(error, _this3); - } - _this3._sessionId = null; - _this3._client = null; - _this3._numConnections = 0; - _this3.serverPingTimes = null; - return callback(null, _this3); - }); - } - } - return this; + } + /** + * Get a dashboard object containing a value for the view_state property. + * This object contains a value for the view_state property, + * but not for the view_name property. + * @param {String} viewName the name of the dashboard + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example Get a specific dashboard from the server: + * + * con.getFrontendView('dashboard_name').then((result) => console.log(result)) + * // {TFrontendView} + */ + function getFrontendView(viewName, callback) { + if (_sessionId && viewName) { + _client.get_frontend_view(_sessionId, viewName, callback); + return; + } else { + callback(new Error("No Session ID")); + return; } - - /** - * Get the recent dashboards as a list of TFrontendView objects. - * These objects contain a value for the view_name property, - * but not for the view_state property. - * @return {Promise} An array which has all saved dashboards. - * - * @example Get the list of dashboards from the server: - * - * con.getFrontendViewsAsync().then((results) => console.log(results)) - * // [TFrontendView, TFrontendView] - */ - - - /** - * Get a dashboard object containing a value for the view_state property. - * This object contains a value for the view_state property, - * but not for the view_name property. - * @param {String} viewName the name of the dashboard - * @return {Promise.} An object that contains all data and metadata related to the dashboard - * - * @example Get a specific dashboard from the server: - * - * con.getFrontendViewAsync('dashboard_name').then((result) => console.log(result)) - * // {TFrontendView} - */ - - - /** - * Get the status of the server as a TServerStatus object. - * This includes whether the server is read-only, - * has backend rendering enabled, and the version number. - * @return {Promise.} - * - * @example Get the server status: - * - * con.getServerStatusAsync().then((result) => console.log(result)) - * // { - * // "read_only": false, - * // "version": "3.0.0dev-20170503-40e2de3", - * // "rendering_enabled": true, - * // "start_time": 1493840131 - * // } - */ - - }, { - key: "createFrontendViewAsync", - - - /** - * Add a new dashboard to the server. - * @param {String} viewName - the name of the new dashboard - * @param {String} viewState - the base64-encoded state string of the new dashboard - * @param {String} imageHash - the numeric hash of the dashboard thumbnail - * @param {String} metaData - Stringified metaData related to the view - * @return {Promise} Returns empty if success - * - * @example Add a new dashboard to the server: - * - * con.createFrontendViewAsync('newSave', 'viewstateBase64', null, 'metaData').then(res => console.log(res)) - */ - value: function createFrontendViewAsync(viewName, viewState, imageHash, metaData) { - var _this4 = this; - - if (!this._sessionId) { - return new Promise(function (resolve, reject) { - reject(new Error("You are not connected to a server. Try running the connect method first.")); - }); - } - - return Promise.all(this._client.map(function (client, i) { - return new Promise(function (resolve, reject) { - client.create_frontend_view(_this4._sessionId[i], viewName, viewState, imageHash, metaData, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - }); - })); + } + /** + * Get the recent dashboards as a list of TFrontendView objects. + * These objects contain a value for the view_name property, + * but not for the view_state property. + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example Get the list of dashboards from the server: + * + * con.getFrontendViews().then((results) => console.log(results)) + * // [TFrontendView, TFrontendView] + */ + function getFrontendViews(callback) { + if (_sessionId) { + _client.get_frontend_views(_sessionId, callback); + } else { + callback(new Error("No Session ID")); + return; } - }, { - key: "deleteFrontendView", - value: function deleteFrontendView(viewName, callback) { - var _this5 = this; - - if (!this._sessionId) { - throw new Error("You are not connected to a server. Try running the connect method first."); - } - try { - this._client.forEach(function (client, i) { - // do we want to try each one individually so if we fail we keep going? - client.delete_frontend_view(_this5._sessionId[i], viewName, callback); - }); - } catch (err) { - console.log("ERROR: Could not delete the frontend view. Check your session id.", err); - } - } - - /** - * Delete a dashboard object containing a value for the view_state property. - * @param {String} viewName - the name of the dashboard - * @return {Promise.} Name of dashboard successfully deleted - * - * @example Delete a specific dashboard from the server: - * - * con.deleteFrontendViewAsync('dashboard_name').then(res => console.log(res)) - */ - - }, { - key: "createLinkAsync", - - - /** - * Create a short hash to make it easy to share a link to a specific dashboard. - * @param {String} viewState - the base64-encoded state string of the new dashboard - * @param {String} metaData - Stringified metaData related to the link - * @return {Promise.} link - A short hash of the dashboard used for URLs - * - * @example Create a link to the current state of a dashboard: - * - * con.createLinkAsync("eyJuYW1lIjoibXlkYXNoYm9hcmQifQ==", 'metaData').then(res => console.log(res)); - * // ["28127951"] - */ - value: function createLinkAsync(viewState, metaData) { - var _this6 = this; - - return Promise.all(this._client.map(function (client, i) { - return new Promise(function (resolve, reject) { - client.create_link(_this6._sessionId[i], viewState, metaData, function (error, data) { - if (error) { - reject(error); - } else { - var result = data.split(",").reduce(function (links, link) { - if (links.indexOf(link) === -1) { - links.push(link); - } - return links; - }, []); - if (!result || result.length !== 1) { - reject(new Error("Different links were created on connection")); - } else { - resolve(result.join()); - } - } - }); - }); - })); + } + /** + * Get the status of the server as a TServerStatus object. + * This includes whether the server is read-only, + * has backend rendering enabled, and the version number. + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example Get the server status: + * + * con.getServerStatus().then((result) => console.log(result)) + * // { + * // "read_only": false, + * // "version": "3.0.0dev-20170503-40e2de3", + * // "rendering_enabled": true, + * // "start_time": 1493840131 + * // } + */ + function getServerStatus(callback) { + _client.get_server_status(_sessionId, callback); + } + /** + * Add a new dashboard to the server. + * @param {String} viewName - the name of the new dashboard + * @param {String} viewState - the base64-encoded state string of the new dashboard + * @param {String} imageHash - the numeric hash of the dashboard thumbnail + * @param {String} metaData - Stringified metaData related to the view + * @param {Function} callback (error) => {…} success returns null + * @returns {undefined} + * + * @example Add a new dashboard to the server: + * + * con.createFrontendView('newSave', 'viewstateBase64', null, 'metaData').then(res => console.log(res)) + */ + function createFrontendView(viewName, viewState, imageHash, metaData, callback) { + if (!_sessionId) { + return callback(new Error("You are not connected to a server. Try running the connect method first.")); } - - /** - * Get a fully-formed dashboard object from a generated share link. - * This object contains the given link for the view_name property, - * @param {String} link - the short hash of the dashboard, see {@link createLink} - * @return {Promise.} Object of the dashboard and metadata - * - * @example Get a dashboard from a link: - * - * con.getLinkViewAsync('28127951').then(res => console.log(res)) - * // { - * // "view_name": "28127951", - * // "view_state": "eyJuYW1lIjoibXlkYXNoYm9hcmQifQ==", - * // "image_hash": "", - * // "update_time": "2017-04-28T21:34:01Z", - * // "view_metadata": "metaData" - * // } - */ - - }, { - key: "detectColumnTypes", - value: function detectColumnTypes(fileName, copyParams, callback) { - var thriftCopyParams = helpers.convertObjectToThriftCopyParams(copyParams); - this._client[0].detect_column_types(this._sessionId[0], fileName, thriftCopyParams, callback); - } - - /** - * Asynchronously get the data from an importable file, - * such as a .csv or plaintext file with a header. - * @param {String} fileName - the name of the importable file - * @param {TCopyParams} copyParams - see {@link TCopyParams} - * @returns {Promise.} An object which has copy_params and row_set - * - * @example Get data from table_data.csv: - * - * var copyParams = new TCopyParams(); - * con.detectColumnTypesAsync('table_data.csv', copyParams).then(res => console.log(res)) - * // TDetectResult {row_set: TRowSet, copy_params: TCopyParams} - * - */ - - }, { - key: "detectColumnTypesAsync", - value: function detectColumnTypesAsync(fileName, copyParams) { - var _this7 = this; - - return new Promise(function (resolve, reject) { - _this7.detectColumnTypes.bind(_this7, fileName, copyParams)(function (err, res) { - if (err) { - reject(err); - } else { - _this7.importerRowDesc = res.row_set.row_desc; - resolve(res); - } - }); - }); + return _client.create_frontend_view(_sessionId, viewName, viewState, imageHash, metaData, callback); + } + /** + * Delete a dashboard object containing a value for the view_state property. + * @param {String} viewName - the name of the dashboard + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example Delete a specific dashboard from the server: + * + * con.deleteFrontendView('dashboard_name').then(res => console.log(res)) + */ + function deleteFrontendView(viewName, callback) { + if (!_sessionId) { + return callback(new Error("You are not connected to a server. Try running the connect method first.")); } - - /** - * Submit a query to the database and process the results. - * @param {String} query The query to perform - * @param {Object} options the options for the query - * @param {Function} callback that takes `(err, result) => result` - * @returns {Object} The result of the query - * - * @example create a query - * - * var query = "SELECT count(*) AS n FROM tweets_nov_feb WHERE country='CO'"; - * var options = {}; - * - * con.query(query, options, function(err, result) { - * console.log(result) - * }); - * - */ - - }, { - key: "query", - value: function query(_query, options, callback) { - var _this8 = this; - - var columnarResults = true; - var eliminateNullRows = false; - var queryId = null; - var returnTiming = false; - var limit = -1; - if (options) { - columnarResults = options.hasOwnProperty("columnarResults") ? options.columnarResults : columnarResults; - eliminateNullRows = options.hasOwnProperty("eliminateNullRows") ? options.eliminateNullRows : eliminateNullRows; - queryId = options.hasOwnProperty("queryId") ? options.queryId : queryId; - returnTiming = options.hasOwnProperty("returnTiming") ? options.returnTiming : returnTiming; - limit = options.hasOwnProperty("limit") ? options.limit : limit; - } - - var lastQueryTime = queryId in this.queryTimes ? this.queryTimes[queryId] : this.DEFAULT_QUERY_TIME; - - var curNonce = (this._nonce++).toString(); - - var conId = 0; - - var processResultsOptions = { - returnTiming: returnTiming, - eliminateNullRows: eliminateNullRows, - query: _query, - queryId: queryId, - conId: conId, - estimatedQueryTime: lastQueryTime - }; - - try { - if (callback) { - this._client[conId].sql_execute(this._sessionId[conId], _query, columnarResults, curNonce, limit, function (error, result) { - if (error) { - callback(error); - } else { - _this8.processResults(processResultsOptions, result, callback); - } - }); - return curNonce; - } else if (!callback) { - var SQLExecuteResult = this._client[conId].sql_execute(this._sessionId[conId], _query, columnarResults, curNonce, limit); - return this.processResults(processResultsOptions, SQLExecuteResult); - } - } catch (err) { - if (err.name === "NetworkError") { - this.removeConnection(conId); - if (this._numConnections === 0) { - err.msg = "No remaining database connections"; - throw err; - } - this.query(_query, options, callback); - } else if (callback) { - callback(err); - } else { - throw err; - } - } - } - - /** @deprecated will default to query */ - - }, { - key: "validateQuery", - - - /** - * Submit a query to validate whether the backend can create a result set based on the SQL statement. - * @param {String} query The query to perform - * @returns {Promise.} The result of whether the query is valid - * - * @example create a query - * - * var query = "SELECT count(*) AS n FROM tweets_nov_feb WHERE country='CO'"; - * - * con.validateQuery(query).then(res => console.log(res)) - * - * // [{ - * // "name": "n", - * // "type": "INT", - * // "is_array": false, - * // "is_dict": false - * // }] - * - */ - value: function validateQuery(query) { - var _this9 = this; - - return new Promise(function (resolve, reject) { - _this9._client[0].sql_validate(_this9._sessionId[0], query, function (error, res) { - if (error) { - reject(error); - } else { - resolve(_this9.convertFromThriftTypes(res)); - } - }); - }); + try { + // eslint-disable-line no-restricted-syntax + return _client.delete_frontend_view(_sessionId, viewName, callback); + } catch (err) { + return callback(new Error("Could not delete the frontend view; check your session id.", err)); } - }, { - key: "removeConnection", - value: function removeConnection(conId) { - if (conId < 0 || conId >= this.numConnections) { - var err = { - msg: "Remove connection id invalid" - }; - throw err; + } + /** + * Create a short hash to make it easy to share a link to a specific dashboard. + * @param {String} viewState - the base64-encoded state string of the new dashboard + * @param {String} metaData - Stringified metaData related to the link + * @param {Function} callback (error, id) => {…} + * @returns {undefined} + * + * @example Create a link to the current state of a dashboard: + * + * con.createLink("eyJuYW1lIjoibXlkYXNoYm9hcmQifQ==", 'metaData').then(res => console.log(res)); + * // ["28127951"] + */ + function createLink(viewState, metaData, callback) { + return _client.create_link(_sessionId, viewState, metaData, function (error, data) { + if (error) { + return callback(normalizeError(error)); } - this._client.splice(conId, 1); - this._sessionId.splice(conId, 1); - this._numConnections--; - } - }, { - key: "getTables", - value: function getTables(callback) { - this._client[0].get_tables(this._sessionId[0], function (error, tables) { - if (error) { - callback(error); - } else { - callback(null, tables.map(function (table) { - return { - name: table, - label: "obs" - }; - })); - } - }); - } - - /** - * Get the names of the databases that exist on the current session's connectdion. - * @return {Promise.} list of table objects containing the label and table names. - * - * @example Get the list of tables from a connection: - * - * con.getTablesAsync().then(res => console.log(res)) - * - * // [{ - * // label: 'obs', // deprecated property - * // name: 'myDatabaseName' - * // }, - * // ...] - */ - - }, { - key: "getTablesAsync", - value: function getTablesAsync() { - var _this10 = this; - - return new Promise(function (resolve, reject) { - _this10.getTables.bind(_this10)(function (error, tables) { - if (error) { - reject(error); - } else { - resolve(tables); - } - }); - }); - } - - /** - * Create an array-like object from {@link TDatumType} by - * flipping the string key and numerical value around. - * - * @returns {Undefined} This function does not return anything - */ - - }, { - key: "invertDatumTypes", - value: function invertDatumTypes() { - var datumType = TDatumType; // eslint-disable-line no-undef - for (var key in datumType) { - if (datumType.hasOwnProperty(key)) { - this._datumEnum[datumType[key]] = key; - } - } - } - - /** - * Get a list of field objects for a given table. - * @param {String} tableName - name of table containing field names - * @param {Function} callback - (err, results) - * @return {Array} fields - the formmatted list of field objects - * - * @example Get the list of fields from a specific table: - * - * con.getFields('flights', (err, res) => console.log(res)) - * // [{ - * name: 'fieldName', - * type: 'BIGINT', - * is_array: false, - * is_dict: false - * }, ...] - */ - - }, { - key: "getFields", - value: function getFields(tableName, callback) { - var _this11 = this; - - this._client[0].get_table_details(this._sessionId[0], tableName, function (error, fields) { - if (fields) { - var rowDict = fields.row_desc.reduce(function (accum, value) { - accum[value.col_name] = value; - return accum; - }, {}); - callback(null, _this11.convertFromThriftTypes(rowDict)); - } else { - callback(new Error("Table (" + tableName + ") not found" + error)); + var result = data.split(",").reduce(function (links, link) { + // eslint-disable-line max-nested-callbacks + if (!links.includes(link)) { + links.push(link); } - }); - } - }, { - key: "createTable", - value: function createTable(tableName, rowDescObj, tableType, callback) { - if (!this._sessionId) { - throw new Error("You are not connected to a server. Try running the connect method first."); + return links; + }, []); + if (!result || result.length !== 1) { + return callback(new Error("Different links were created on connection")); + } else { + return callback(null, result.join()); } - - var thriftRowDesc = helpers.mutateThriftRowDesc(rowDescObj, this.importerRowDesc); - - for (var c = 0; c < this._numConnections; c++) { - this._client[c].create_table(this._sessionId[c], tableName, thriftRowDesc, tableType, function (err) { - if (err) { - callback(err); - } else { - callback(); - } - }); + }); + } + /** + * Get a fully-formed dashboard object from a generated share link. + * This object contains the given link for the view_name property, + * @param {String} link - the short hash of the dashboard, see {@link createLink} + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example Get a dashboard from a link: + * + * con.getLinkView('28127951').then(res => console.log(res)) + * // { + * // "view_name": "28127951", + * // "view_state": "eyJuYW1lIjoibXlkYXNoYm9hcmQifQ==", + * // "image_hash": "", + * // "update_time": "2017-04-28T21:34:01Z", + * // "view_metadata": "metaData" + * // } + */ + function getLinkView(link, callback) { + _client.get_link_view(_sessionId, link, function (error, data) { + if (error) { + return callback(normalizeError(error)); + } else { + return callback(null, data); } - } - - /** - * Create a table and persist it to the backend. - * @param {String} tableName - desired name of the new table - * @param {Array} rowDescObj - fields of the new table - * @param {Number} tableType - the types of tables a user can import into the db - * @return {Promise.} it will either catch an error or return undefined on success - * - * @example Create a new table: - * - * con.createTable('mynewtable', [TColumnType, TColumnType, ...], 0).then(res => console.log(res)); - * // undefined - */ + }); + } + /** + * Asynchronously get the data from an importable file, + * such as a .csv or plaintext file with a header. + * @param {String} fileName - the name of the importable file + * @param {TCopyParams} copyParams - see {@link TCopyParams} + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example Get data from table_data.csv: + * + * var copyParams = new TCopyParams(); + * con.detectColumnTypes('table_data.csv', copyParams).then(res => console.log(res)) + * // TDetectResult {row_set: TRowSet, copy_params: TCopyParams} + * + */ + function detectColumnTypes(fileName, copyParams, callback) { + var thriftCopyParams = helpers.convertObjectToThriftCopyParams(copyParams); + _client.detect_column_types(_sessionId, fileName, thriftCopyParams, callback); + } + /** + * Submit a query to the database and process the results. + * @param {String} sql The query to perform + * @param {Object} options the options for the query + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example create a query + * + * var query = "SELECT count(*) AS n FROM tweets_nov_feb WHERE country='CO'"; + * var options = {}; + * + * con.query(query, options, function(error, data) { + * console.log(data) + * }); + * + */ + function query(sql, options, callback) { + var columnarResults = true; + var eliminateNullRows = false; + var queryId = null; + var returnTiming = false; + var limit = -1; + if (options) { + columnarResults = options.hasOwnProperty("columnarResults") ? options.columnarResults : columnarResults; + eliminateNullRows = options.hasOwnProperty("eliminateNullRows") ? options.eliminateNullRows : eliminateNullRows; + queryId = options.hasOwnProperty("queryId") ? options.queryId : queryId; + returnTiming = options.hasOwnProperty("returnTiming") ? options.returnTiming : returnTiming; + limit = options.hasOwnProperty("limit") ? options.limit : limit; + } + + var lastQueryTime = queryId in _queryTimes ? _queryTimes[queryId] : DEFAULT_QUERY_TIME; + + var curNonce = (_nonce++).toString(); + + var processResultsOptions = { + returnTiming: returnTiming, + eliminateNullRows: eliminateNullRows, + sql: sql, + queryId: queryId, + conId: 0, + estimatedQueryTime: lastQueryTime + }; - }, { - key: "importTable", - value: function importTable(tableName, fileName, copyParams, rowDescObj, isShapeFile, callback) { - if (!this._sessionId) { - throw new Error("You are not connected to a server. Try running the connect method first."); + _client.sql_execute(_sessionId, sql, columnarResults, curNonce, limit, function (error, result) { + if (error) { + return callback(normalizeError(error)); + } else { + return processResults(result, callback, _logging, _datumEnum, processResultsOptions); } - - var thriftCopyParams = helpers.convertObjectToThriftCopyParams(copyParams); - var thriftRowDesc = helpers.mutateThriftRowDesc(rowDescObj, this.importerRowDesc); - - var thriftCallBack = function thriftCallBack(err, res) { - if (err) { - callback(err); - } else { - callback(null, res); - } - }; - - for (var c = 0; c < this._numConnections; c++) { - if (isShapeFile) { - this._client[c].import_geo_table(this._sessionId[c], tableName, fileName, thriftCopyParams, thriftRowDesc, thriftCallBack); - } else { - this._client[c].import_table(this._sessionId[c], tableName, fileName, thriftCopyParams, thriftCallBack); - } + }); + } + /** + * Submit a query to validate whether the backend can create a result set based on the SQL statement. + * @param {String} sql The query to perform + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example create a query + * + * var query = "SELECT count(*) AS n FROM tweets_nov_feb WHERE country='CO'"; + * + * con.validateQuery(query).then(res => console.log(res)) + * + * // [{ + * // "name": "n", + * // "type": "INT", + * // "is_array": false, + * // "is_dict": false + * // }] + * + */ + function validateQuery(sql, callback) { + _client.sql_validate(_sessionId, sql, function (error, data) { + if (error) { + return callback(normalizeError(error)); + } else { + return callback(null, convertFromThriftTypes(data, _datumEnum)); } + }); + } + /** + * Get the names of the databases that exist on the current session's connectdion. + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example Get the list of tables from a connection: + * + * con.getTables().then(res => console.log(res)) + * + * // [{ + * // label: 'obs', // deprecated property + * // name: 'myDatabaseName' + * // }, + * // ...] + */ + function getTables(callback) { + _client.get_tables(_sessionId, function (error, tables) { + if (error) { + return callback(normalizeError(error)); + } else { + return callback(null, tables.map(function (table) { + return { name: table, label: "obs" }; + })); + } + }); + } + /** + * Get a list of field objects for a given table. + * @param {String} tableName - name of table containing field names + * @param {Function} callback - (error, fields) => {…} + * @returns {undefined} + * + * @example Get the list of fields from a specific table: + * + * con.getFields('flights', (error, res) => console.log(res)) + * // [{ + * name: 'fieldName', + * type: 'BIGINT', + * is_array: false, + * is_dict: false + * }, ...] + */ + function getFields(tableName, callback) { + _client.get_table_details(_sessionId, tableName, function (error, fields) { + if (fields) { + var rowDict = fields.row_desc.reduce(function (accum, value) { + accum[value.col_name] = value; + return accum; + }, {}); + return callback(null, convertFromThriftTypes(rowDict, _datumEnum)); + } else { + return callback(normalizeError(error)); + } + }); + } + /** + * Create a table and persist it to the backend. + * @param {String} tableName - desired name of the new table + * @param {Array} rowDescObj - fields of the new table + * @param {Number} tableType - the types of tables a user can import into the db + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example Create a new table: + * + * con.createTable('mynewtable', [TColumnType, TColumnType, ...], 0).then(res => console.log(res)); + * // undefined + */ + function createTable(tableName, rowDescObj, tableType, callback) { + if (!_sessionId) { + return callback(new Error("You are not connected to a server. Try running the connect method first.")); } - }, { - key: "importTableAsyncWrapper", - value: function importTableAsyncWrapper(isShapeFile) { - var _this12 = this; + var thriftRowDesc = helpers.mutateThriftRowDesc(rowDescObj, null); + return _client.create_table(_sessionId, tableName, thriftRowDesc, tableType, callback); + } + /** + * Import a delimited table from a file. + * @param {String} tableName - desired name of the new table + * @param {String} fileName - name of imported file + * @param {TCopyParams} copyParams - see {@link TCopyParams} + * @param {TColumnType[]} rowDescObj -- a colleciton of metadata related to the table headers + * @param {Function} callback (error, data) => {…} + * @param {Boolean} isShapeFile false by default, enabled to import shape data. + * @returns {undefined} + */ + function importTable(tableName, fileName, copyParams, rowDescObj, callback) { + var isShapeFile = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false; - return function (tableName, fileName, copyParams, headers) { - return new Promise(function (resolve, reject) { - _this12.importTable(tableName, fileName, copyParams, headers, isShapeFile, function (err, link) { - if (err) { - reject(err); - } else { - resolve(link); - } - }); - }); - }; + if (!_sessionId) { + return callback(new Error("You are not connected to a server. Try running the connect method first.")); } - /** - * Import a delimited table from a file. - * @param {String} tableName - desired name of the new table - * @param {String} fileName - * @param {TCopyParams} copyParams - see {@link TCopyParams} - * @param {TColumnType[]} headers -- a colleciton of metadata related to the table headers - */ - - - /** - * Import a geo table from a file. - * @param {String} tableName - desired name of the new table - * @param {String} fileName - * @param {TCopyParams} copyParams - see {@link TCopyParams} - * @param {TColumnType[]} headers -- a colleciton of metadata related to the table headers - */ - - }, { - key: "renderVega", - - - /** - * Use for backend rendering. This method will fetch a PNG image - * that is a render of the vega json object. - * - * @param {Number} widgetid the widget id of the calling widget - * @param {String} vega the vega json - * @param {Object} options the options for the render query - * @param {Number} options.compressionLevel the png compression level. - * range 1 (low compression, faster) to 10 (high compression, slower). - * Default 3. - * @param {Function} callback takes `(err, success)` as its signature. Returns con singleton on success. - * - * @returns {Image} Base 64 Image - */ - value: function renderVega(widgetid, vega, options, callback) /* istanbul ignore next */{ - var _this13 = this; - - var queryId = null; - var compressionLevel = COMPRESSION_LEVEL_DEFAULT; - if (options) { - queryId = options.hasOwnProperty("queryId") ? options.queryId : queryId; - compressionLevel = options.hasOwnProperty("compressionLevel") ? options.compressionLevel : compressionLevel; - } - - var lastQueryTime = queryId in this.queryTimes ? this.queryTimes[queryId] : this.DEFAULT_QUERY_TIME; - - var curNonce = (this._nonce++).toString(); - - var conId = 0; - this._lastRenderCon = conId; - - var processResultsOptions = { - isImage: true, - query: "render: " + vega, - queryId: queryId, - conId: conId, - estimatedQueryTime: lastQueryTime - }; + var thriftCopyParams = helpers.convertObjectToThriftCopyParams(copyParams); + var thriftRowDesc = helpers.mutateThriftRowDesc(rowDescObj, null); - try { - if (!callback) { - var renderResult = this._client[conId].render_vega(this._sessionId[conId], widgetid, vega, compressionLevel, curNonce); - return this.processResults(processResultsOptions, renderResult); - } - - this._client[conId].render_vega(this._sessionId[conId], widgetid, vega, compressionLevel, curNonce, function (error, result) { - if (error) { - callback(error); - } else { - _this13.processResults(processResultsOptions, result, callback); - } - }); - } catch (err) { - throw err; - } - - return curNonce; + if (isShapeFile) { + return _client.import_geo_table(_sessionId, tableName, fileName, thriftCopyParams, thriftRowDesc, callback); + } else { + return _client.import_table(_sessionId, tableName, fileName, thriftCopyParams, callback); + } + } + /** + * Import a geo table from a file. + * @param {String} tableName - desired name of the new table + * @param {String} fileName - name of imported file + * @param {TCopyParams} copyParams - see {@link TCopyParams} + * @param {TColumnType[]} rowDescObj -- a colleciton of metadata related to the table headers + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + */ + function importShapeTable(tableName, fileName, copyParams, rowDescObj, callback) { + return importTable(tableName, fileName, copyParams, rowDescObj, callback, true); + } + /** + * Use for backend rendering. This method will fetch a PNG image + * that is a render of the vega json object. + * + * @param {Number} widgetid the widget id of the calling widget + * @param {String} vega the vega json + * @param {Object} options the options for the render query + * @param {Number} options.compressionLevel the png compression level. + * range 1 (low compression, faster) to 10 (high compression, slower). + * Default 3. + * @param {Function} callback (error, Base64Image) => {…} + * @returns {undefined} + */ + function renderVega(widgetid, vega, options, callback) { + var queryId = null; + var compressionLevel = COMPRESSION_LEVEL_DEFAULT; + if (options) { + queryId = options.hasOwnProperty("queryId") ? options.queryId : queryId; + compressionLevel = options.hasOwnProperty("compressionLevel") ? options.compressionLevel : compressionLevel; } - /** - * Used primarily for backend rendered maps, this method will fetch the row - * for a specific table that was last rendered at a pixel. - * - * @param {widgetId} Number - the widget id of the caller - * @param {TPixel} pixel - the pixel (lower left-hand corner is pixel (0,0)) - * @param {String} tableName - the table containing the geo data - * @param {Object} tableColNamesMap - object of tableName -> array of col names - * @param {Array} callbacks - * @param {Number} [pixelRadius=2] - the radius around the primary pixel to search - */ + var lastQueryTime = queryId in _queryTimes ? _queryTimes[queryId] : DEFAULT_QUERY_TIME; - }, { - key: "getResultRowForPixel", - value: function getResultRowForPixel(widgetId, pixel, tableColNamesMap, callbacks) /* istanbul ignore next */{ - var pixelRadius = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 2; + var curNonce = (_nonce++).toString(); - if (!(pixel instanceof TPixel)) { - pixel = new TPixel(pixel); - } - var columnFormat = true; // BOOL - var curNonce = (this._nonce++).toString(); - try { - if (!callbacks) { - return this.processPixelResults(undefined, // eslint-disable-line no-undefined - this._client[this._lastRenderCon].get_result_row_for_pixel(this._sessionId[this._lastRenderCon], widgetId, pixel, tableColNamesMap, columnFormat, pixelRadius, curNonce)); - } - this._client[this._lastRenderCon].get_result_row_for_pixel(this._sessionId[this._lastRenderCon], widgetId, pixel, tableColNamesMap, columnFormat, pixelRadius, curNonce, this.processPixelResults.bind(this, callbacks)); - } catch (err) { - throw err; + var processResultsOptions = { + isImage: true, + query: "render: " + vega, + queryId: queryId, + conId: 0, + estimatedQueryTime: lastQueryTime + }; + + _client.render_vega(_sessionId, widgetid, vega, compressionLevel, curNonce, function (error, result) { + if (error) { + return callback(normalizeError(error)); } - return curNonce; - } - - /** - * Formats the pixel results into the same pattern as textual results. - * - * @param {Array} callbacks a collection of callbacks - * @param {Object} error an error if one was thrown, otherwise null - * @param {Array|Object} results unformatted results of pixel rowId information - * - * @returns {Object} An object with the pixel results formatted for display - */ - - }, { - key: "processPixelResults", - value: function processPixelResults(callbacks, error, results) { - callbacks = Array.isArray(callbacks) ? callbacks : [callbacks]; - results = Array.isArray(results) ? results.pixel_rows : [results]; - var numPixels = results.length; - var processResultsOptions = { - isImage: false, - eliminateNullRows: false, - query: "pixel request", - queryId: -2 - }; - for (var p = 0; p < numPixels; p++) { - results[p].row_set = this.processResults(processResultsOptions, results[p]); - } - if (!callbacks) { - return results; - } - callbacks.pop()(error, results); - } - - /** - * Get or set the session ID used by the server to serve the correct data. - * This is typically set by {@link connect} and should not be set manually. - * @param {Number} sessionId - The session ID of the current connection - * @return {Number|MapdCon} - The session ID or the MapdCon itself - * - * @example Get the session id: - * - * con.sessionId(); - * // sessionID === 3145846410 - * - * @example Set the session id: - * var con = new MapdCon().connect().sessionId(3415846410); - * // NOTE: It is generally unsafe to set the session id manually. - */ - - }, { - key: "sessionId", - value: function sessionId(_sessionId) { - if (!arguments.length) { - return this._sessionId; - } - this._sessionId = _sessionId; - return this; - } + return processResults(result, callback, _logging, _datumEnum, processResultsOptions); + }); + } + /** + * Used primarily for backend rendered maps, this method will fetch the row + * for a specific table that was last rendered at a pixel. + * @param {Number} widgetId - the widget id of the caller + * @param {TPixel} pixel - the pixel (lower left-hand corner is pixel (0,0)) + * @param {Object} tableColNamesMap - object of tableName -> array of col names + * @param {Function} callback (error, results) => {…} + * @param {Number} [pixelRadius=2] - the radius around the primary pixel to search + * @returns {undefined} + */ + function getPixel(widgetId, pixel, tableColNamesMap, callback) { + var pixelRadius = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 2; - /** - * Get or set the connection server hostname. - * This is is typically the first method called after instantiating a new MapdCon. - * @param {String} host - The hostname address - * @return {String|MapdCon} - The hostname or the MapdCon itself - * - * @example Set the hostname: - * var con = new MapdCon().host('localhost'); - * - * @example Get the hostname: - * var host = con.host(); - * // host === 'localhost' - */ - - }, { - key: "host", - value: function host(_host) { - if (!arguments.length) { - return this._host; - } - this._host = arrayify(_host); - return this; + if (Array.isArray(callback)) { + console.warn("getPixel callbacks array deprecated; pass single callback instead."); // eslint-disable-line no-console + callback = callback[0]; } - - /** - * Get or set the connection port. - * @param {String} port - The port to connect on - * @return {String|MapdCon} - The port or the MapdCon itself - * - * @example Set the port: - * var con = new MapdCon().port('8080'); - * - * @example Get the port: - * var port = con.port(); - * // port === '8080' - */ - - }, { - key: "port", - value: function port(_port) { - if (!arguments.length) { - return this._port; - } - this._port = arrayify(_port); - return this; + if (!(pixel instanceof TPixel)) { + pixel = new TPixel(pixel); } - - /** - * Get or set the username to authenticate with. - * @param {String} user - The username to authenticate with - * @return {String|MapdCon} - The username or the MapdCon itself - * - * @example Set the username: - * var con = new MapdCon().user('foo'); - * - * @example Get the username: - * var username = con.user(); - * // user === 'foo' - */ - - }, { - key: "user", - value: function user(_user) { - if (!arguments.length) { - return this._user; - } - this._user = arrayify(_user); - return this; + var columnFormat = true; + var curNonce = String(_nonce++); + _client.get_result_row_for_pixel(_sessionId, widgetId, pixel, tableColNamesMap, columnFormat, pixelRadius, curNonce, processPixelResults.bind(this, callback, _logging, _datumEnum)); + } + /** + * Get or set the session ID used by the server to serve the correct data. + * This is typically set by {@link connect} and should not be set manually. + * @param {Number} newSessionId - The session ID of the current connection + * @returns {Number|Connector} - The session ID or the Connector itself + * + * @example Get the session id: + * + * con.sessionId(); + * // sessionID === 3145846410 + * + * @example Set the session id: + * var con = new Connector().connect().sessionId(3415846410); + * // NOTE: It is generally unsafe to set the session id manually. + */ + function sessionId(newSessionId) { + if (!arguments.length) { + return _sessionId; } - - /** - * Get or set the user's password to authenticate with. - * @param {String} password - The password to authenticate with - * @return {String|MapdCon} - The password or the MapdCon itself - * - * @example Set the password: - * var con = new MapdCon().password('bar'); - * - * @example Get the username: - * var password = con.password(); - * // password === 'bar' - */ - - }, { - key: "password", - value: function password(_password) { - if (!arguments.length) { - return this._password; - } - this._password = arrayify(_password); - return this; + _sessionId = newSessionId; + return this; + } + /** + * Get or set the connection server hostname. + * This is is typically the first method called after instantiating a new Connector. + * @param {String} hostname - The hostname address + * @returns {String|Connector} - The hostname or the Connector itself + * + * @example Set the hostname: + * var con = new Connector().host('localhost'); + * + * @example Get the hostname: + * var host = con.host(); + * // host === 'localhost' + */ + function host(hostname) { + if (!arguments.length) { + return _host; } - - /** - * Get or set the name of the database to connect to. - * @param {String} dbName - The database to connect to - * @return {String|MapdCon} - The name of the database or the MapdCon itself - * - * @example Set the database name: - * var con = new MapdCon().dbName('myDatabase'); - * - * @example Get the database name: - * var dbName = con.dbName(); - * // dbName === 'myDatabase' - */ - - }, { - key: "dbName", - value: function dbName(_dbName) { - if (!arguments.length) { - return this._dbName; - } - this._dbName = arrayify(_dbName); - return this; + _host = hostname; + return this; + } + /** + * Get or set the connection port. + * @param {String} thePort - The port to connect on + * @returns {String|Connector} - The port or the Connector itself + * + * @example Set the port: + * var con = new Connector().port('8080'); + * + * @example Get the port: + * var port = con.port(); + * // port === '8080' + */ + function port(thePort) { + if (!arguments.length) { + return _port; } - - /** - * Whether the raw queries strings will be logged to the console. - * Used primarily for debugging and defaults to false. - * @param {Boolean} logging - Set to true to enable logging - * @return {Boolean|MapdCon} - The current logging flag or MapdCon itself - * - * @example Set logging to true: - * var con = new MapdCon().logging(true); - * - * @example Get the logging flag: - * var isLogging = con.logging(); - * // isLogging === true - */ - - }, { - key: "logging", - value: function logging(_logging) { - if (typeof _logging === "undefined") { - return this._logging; - } else if (typeof _logging !== "boolean") { - return "logging can only be set with boolean values"; - } - this._logging = _logging; - var isEnabledTxt = _logging ? "enabled" : "disabled"; - return "SQL logging is now " + isEnabledTxt; - } - - /** - * The name of the platform. - * @param {String} platform - The platform, default is "mapd" - * @return {String|MapdCon} - The platform or the MapdCon itself - * - * @example Set the platform name: - * var con = new MapdCon().platform('myPlatform'); - * - * @example Get the platform name: - * var platform = con.platform(); - * // platform === 'myPlatform' - */ - - }, { - key: "platform", - value: function platform(_platform) { - if (!arguments.length) { - return this._platform; - } - this._platform = _platform; - return this; + _port = thePort; + return this; + } + /** + * Get or set the username to authenticate with. + * @param {String} username - The username to authenticate with + * @returns {String|Connector} - The username or the Connector itself + * + * @example Set the username: + * var con = new Connector().user('foo'); + * + * @example Get the username: + * var username = con.user(); + * // user === 'foo' + */ + function user(username) { + if (!arguments.length) { + return _user; } - - /** - * Get the number of connections that are currently open. - * @return {Number} - number of open connections - * - * @example Get the number of connections: - * - * var numConnections = con.numConnections(); - * // numConnections === 1 - */ - - }, { - key: "numConnections", - value: function numConnections() { - return this._numConnections; - } - - /** - * The protocol to use for requests. - * @param {String} protocol - http or https - * @return {String|MapdCon} - protocol or MapdCon itself - * - * @example Set the protocol: - * var con = new MapdCon().protocol('http'); - * - * @example Get the protocol: - * var protocol = con.protocol(); - * // protocol === 'http' - */ - - }, { - key: "protocol", - value: function protocol(_protocol) { - if (!arguments.length) { - return this._protocol; - } - this._protocol = arrayify(_protocol); - return this; + _user = username; + return this; + } + /** + * Get or set the user's password to authenticate with. + * @param {String} pass - The password to authenticate with + * @returns {String|Connector} - The password or the Connector itself + * + * @example Set the password: + * var con = new Connector().password('bar'); + * + * @example Get the username: + * var password = con.password(); + * // password === 'bar' + */ + function password(pass) { + if (!arguments.length) { + return _password; } + _password = pass; + return this; + } + /** + * Get or set the name of the database to connect to. + * @param {String} db - The database to connect to + * @returns {String|Connector} - The name of the database or the Connector itself + * + * @example Set the database name: + * var con = new Connector().dbName('myDatabase'); + * + * @example Get the database name: + * var dbName = con.dbName(); + * // dbName === 'myDatabase' + */ + function dbName(db) { + if (!arguments.length) { + return _dbName; + } + _dbName = db; + return this; + } + /** + * Whether the raw queries strings will be logged to the console. + * Used primarily for debugging and defaults to false. + * @param {Boolean} loggingEnabled - Set to true to enable logging + * @returns {Boolean|Connector} - The current logging flag or Connector itself + * + * @example Set logging to true: + * var con = new Connector().logging(true); + * + * @example Get the logging flag: + * var isLogging = con.logging(); + * // isLogging === true + */ + function logging(loggingEnabled) { + if (typeof loggingEnabled === "undefined") { + return _logging; + } else if (typeof loggingEnabled !== "boolean") { + return "logging can only be set with boolean values"; + } + _logging = loggingEnabled; + var isEnabledTxt = loggingEnabled ? "enabled" : "disabled"; + return "SQL logging is now " + isEnabledTxt; + } + /** + * The protocol to use for requests. + * @param {String} theProtocol - http or https + * @returns {String|Connector} - protocol or Connector itself + * + * @example Set the protocol: + * var con = new Connector().protocol('http'); + * + * @example Get the protocol: + * var protocol = con.protocol(); + * // protocol === 'http' + */ + function protocol(theProtocol) { + if (!arguments.length) { + return _protocol; + } + _protocol = theProtocol; + return this; + } + } + + // helper functions + + function noop() {/* noop */} - /** - * Generates a list of endpoints from the connection params. - * @return {Array} - list of endpoints - * - * @example Get the endpoints: - * var con = new MapdCon().protocol('http').host('localhost').port('8000'); - * var endpoints = con.getEndpoints(); - * // endpoints === [ 'http://localhost:8000' ] - */ + function isNodeRuntime() { + return typeof window === "undefined"; + } - }, { - key: "getEndpoints", - value: function getEndpoints() { - var _this14 = this; + function publicizeMethods(theClass, methods) { + methods.forEach(function (method) { + theClass[method.name] = method; + }); + } - return this._host.map(function (host, i) { - return _this14._protocol[i] + "://" + host + ":" + _this14._port[i]; + function convertFromThriftTypes(fields, _datumEnum) { + var fieldsArray = []; + for (var key in fields) { + if (fields.hasOwnProperty(key)) { + fieldsArray.push({ + name: key, + type: _datumEnum[fields[key].col_type.type], + is_array: fields[key].col_type.is_array, + is_dict: fields[key].col_type.encoding === TEncodingType.DICT }); } - }]); + } + return fieldsArray; + } - return MapdCon; - }(); + function updateQueryTimes(queryTimes) { + return function (conId, queryId, estimatedQueryTime, execution_time_ms) { + queryTimes[queryId] = execution_time_ms; + }; + } + + function processPixelResults(callback, _logging, _datumEnum, error, results) { + // eslint-disable-line consistent-return + if (error) { + return callback(normalizeError(error)); + } + results = Array.isArray(results) ? results.pixel_rows : [results]; + var numPixels = results.length; + var processResultsOptions = { + isImage: false, + eliminateNullRows: false, + query: "pixel request", + queryId: -2 + }; + var numResultsProcessed = 0; + for (var p = 0; p < numPixels; p++) { + processResults(results[p], aggregatingCallback(p), _logging, _datumEnum, processResultsOptions); + } + function aggregatingCallback(index) { + return function (processResultsError, row_set) { + results[index].row_set = row_set; + if (processResultsError) { + numResultsProcessed = -Infinity; // avoid invoking callback again + return callback(processResultsError); + } else if (numResultsProcessed === numPixels - 1) { + return callback(null, results); + } else { + return numResultsProcessed++; + } + }; + } + } + + function processResults(result, callback, _logging, _datumEnum) { + var options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {}; + + var processor = (0, _processQueryResults2.default)(_logging, updateQueryTimes); + var processResultsObject = processor(options, _datumEnum, result, callback); + return processResultsObject; + } + + function invertDatumTypes(datumEnum) { + var datumType = TDatumType; + for (var key in datumType) { + if (datumType.hasOwnProperty(key)) { + datumEnum[datumType[key]] = key; + } + } + } function resetThriftClientOnArgumentErrorForMethods(connector, client, methodNames) { methodNames.forEach(function (methodName) { @@ -1416,14 +1004,13 @@ }); } - // Set a global mapdcon function when mapdcon is brought in via script tag. - if (( false ? "undefined" : _typeof(module)) === "object" && module.exports) { - if (!isNodeRuntime()) { - window.MapdCon = MapdCon; + function normalizeError(error) { + if (isNodeRuntime()) { + return new Error(error.name + " " + error.error_msg); + } else { + return new Error("TMapDException " + error.message); } } - module.exports = MapdCon; - exports.default = MapdCon; /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(9)(module))) /***/ }), @@ -1512,8 +1099,8 @@ }; MapDClientV2.prototype.get_result_row_for_pixel = function () { - var getResultRowForPixelWithErrorHandling = (0, _wrapWithErrorHandling.wrapWithErrorHandling)(this, "get_result_row_for_pixel"); - return getResultRowForPixelWithErrorHandling.apply(undefined, arguments); + var getPixelWithErrorHandling = (0, _wrapWithErrorHandling.wrapWithErrorHandling)(this, "get_result_row_for_pixel"); + return getPixelWithErrorHandling.apply(undefined, arguments); }; MapDClientV2.prototype.delete_frontend_view = function () { @@ -1600,7 +1187,7 @@ exports.wrapMethod = wrapMethod; exports.wrapWithErrorHandling = wrapWithErrorHandling; var MapDClient = typeof window !== "undefined" && window.MapDClient || __webpack_require__(13).Client; // eslint-disable-line global-require - var TMapDException = typeof window !== "undefined" && window.TMapDException || __webpack_require__(116).TMapDException; // eslint-disable-line global-require + var TMapDException = typeof window !== "undefined" && window.TMapDException || __webpack_require__(117).TMapDException; // eslint-disable-line global-require var Thrift = typeof window !== "undefined" && window.Thrift || __webpack_require__(14).Thrift; // eslint-disable-line global-require function isResultError(result) { @@ -1672,7 +1259,7 @@ var Thrift = thrift.Thrift; var Q = thrift.Q; - var ttypes = __webpack_require__(116); + var ttypes = __webpack_require__(117); //HELPER FUNCTIONS AND STRUCTURES var MapD_connect_args = function MapD_connect_args(args) { @@ -12455,28 +12042,28 @@ exports.createHttpConnection = httpConnection.createHttpConnection; exports.createHttpClient = httpConnection.createHttpClient; - var wsConnection = __webpack_require__(66); + var wsConnection = __webpack_require__(67); exports.WSConnection = wsConnection.WSConnection; exports.createWSConnection = wsConnection.createWSConnection; exports.createWSClient = wsConnection.createWSClient; - var xhrConnection = __webpack_require__(75); + var xhrConnection = __webpack_require__(76); exports.XHRConnection = xhrConnection.XHRConnection; exports.createXHRConnection = xhrConnection.createXHRConnection; exports.createXHRClient = xhrConnection.createXHRClient; - var server = __webpack_require__(76); + var server = __webpack_require__(77); exports.createServer = server.createServer; exports.createMultiplexServer = server.createMultiplexServer; - var web_server = __webpack_require__(77); + var web_server = __webpack_require__(78); exports.createWebServer = web_server.createWebServer; exports.Int64 = __webpack_require__(32); - exports.Q = __webpack_require__(114); + exports.Q = __webpack_require__(115); - var mprocessor = __webpack_require__(113); - var mprotocol = __webpack_require__(115); + var mprocessor = __webpack_require__(114); + var mprotocol = __webpack_require__(116); exports.Multiplexer = mprotocol.Multiplexer; exports.MultiplexedProcessor = mprocessor.MultiplexedProcessor; @@ -12484,11 +12071,11 @@ * Export transport and protocol so they can be used outside of a * cassandra/server context */ - exports.TFramedTransport = __webpack_require__(69); + exports.TFramedTransport = __webpack_require__(70); exports.TBufferedTransport = __webpack_require__(23); exports.TBinaryProtocol = __webpack_require__(30); - exports.TJSONProtocol = __webpack_require__(72); - exports.TCompactProtocol = __webpack_require__(71); + exports.TJSONProtocol = __webpack_require__(73); + exports.TCompactProtocol = __webpack_require__(72); /***/ }), @@ -16242,22 +15829,22 @@ function byteLength (b64) { // base64 is 4/3 + up to two characters of the original data - return b64.length * 3 / 4 - placeHoldersCount(b64) + return (b64.length * 3 / 4) - placeHoldersCount(b64) } function toByteArray (b64) { - var i, j, l, tmp, placeHolders, arr + var i, l, tmp, placeHolders, arr var len = b64.length placeHolders = placeHoldersCount(b64) - arr = new Arr(len * 3 / 4 - placeHolders) + arr = new Arr((len * 3 / 4) - placeHolders) // if there are placeholders, only get up to the last complete 4 chars l = placeHolders > 0 ? len - 4 : len var L = 0 - for (i = 0, j = 0; i < l; i += 4, j += 3) { + for (i = 0; i < l; i += 4) { tmp = (revLookup[b64.charCodeAt(i)] << 18) | (revLookup[b64.charCodeAt(i + 1)] << 12) | (revLookup[b64.charCodeAt(i + 2)] << 6) | revLookup[b64.charCodeAt(i + 3)] arr[L++] = (tmp >> 16) & 0xFF arr[L++] = (tmp >> 8) & 0xFF @@ -17364,7 +16951,7 @@ */ var util = __webpack_require__(16); var http = __webpack_require__(35); - var https = __webpack_require__(65); + var https = __webpack_require__(66); var EventEmitter = __webpack_require__(21).EventEmitter; var thrift = __webpack_require__(15); @@ -17590,9 +17177,9 @@ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(global) {var ClientRequest = __webpack_require__(36) - var extend = __webpack_require__(57) - var statusCodes = __webpack_require__(58) - var url = __webpack_require__(59) + var extend = __webpack_require__(58) + var statusCodes = __webpack_require__(59) + var url = __webpack_require__(60) var http = exports @@ -17678,7 +17265,7 @@ var inherits = __webpack_require__(38) var response = __webpack_require__(39) var stream = __webpack_require__(40) - var toArrayBuffer = __webpack_require__(56) + var toArrayBuffer = __webpack_require__(57) var IncomingMessage = response.IncomingMessage var rStates = response.readyStates @@ -18284,24 +17871,46 @@ exports = module.exports = __webpack_require__(41); exports.Stream = exports; exports.Readable = exports; - exports.Writable = __webpack_require__(49); - exports.Duplex = __webpack_require__(48); - exports.Transform = __webpack_require__(54); - exports.PassThrough = __webpack_require__(55); + exports.Writable = __webpack_require__(50); + exports.Duplex = __webpack_require__(49); + exports.Transform = __webpack_require__(55); + exports.PassThrough = __webpack_require__(56); /***/ }), /* 41 */ /***/ (function(module, exports, __webpack_require__) { - /* WEBPACK VAR INJECTION */(function(process) {'use strict'; + /* WEBPACK VAR INJECTION */(function(process) {// Copyright Joyent, Inc. and other Node contributors. + // + // Permission is hereby granted, free of charge, to any person obtaining a + // copy of this software and associated documentation files (the + // "Software"), to deal in the Software without restriction, including + // without limitation the rights to use, copy, modify, merge, publish, + // distribute, sublicense, and/or sell copies of the Software, and to permit + // persons to whom the Software is furnished to do so, subject to the + // following conditions: + // + // The above copyright notice and this permission notice shall be included + // in all copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + // USE OR OTHER DEALINGS IN THE SOFTWARE. - module.exports = Readable; + 'use strict'; /**/ + var processNextTick = __webpack_require__(42); /**/ + module.exports = Readable; + /**/ var isArray = __webpack_require__(27); /**/ @@ -18324,9 +17933,16 @@ var Stream = __webpack_require__(43); /**/ - var Buffer = __webpack_require__(24).Buffer; + // TODO(bmeurer): Change this back to const once hole checks are + // properly optimized away early in Ignition+TurboFan. /**/ - var bufferShim = __webpack_require__(44); + var Buffer = __webpack_require__(44).Buffer; + function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); + } + function _isUint8Array(obj) { + return Object.prototype.toString.call(obj) === '[object Uint8Array]' || Buffer.isBuffer(obj); + } /**/ /**/ @@ -18345,6 +17961,7 @@ /**/ var BufferList = __webpack_require__(47); + var destroyImpl = __webpack_require__(48); var StringDecoder; util.inherits(Readable, Stream); @@ -18366,7 +17983,7 @@ } function ReadableState(options, stream) { - Duplex = Duplex || __webpack_require__(48); + Duplex = Duplex || __webpack_require__(49); options = options || {}; @@ -18383,7 +18000,7 @@ this.highWaterMark = hwm || hwm === 0 ? hwm : defaultHwm; // cast to ints. - this.highWaterMark = ~~this.highWaterMark; + this.highWaterMark = Math.floor(this.highWaterMark); // A linked list is used to store data chunks instead of an array because the // linked list can remove elements from the beginning faster than @@ -18397,10 +18014,10 @@ this.endEmitted = false; this.reading = false; - // a flag to be able to tell if the onwrite cb is called immediately, - // or on a later tick. We set this to true at first, because any - // actions that shouldn't happen until "later" should generally also - // not happen before the first write call. + // a flag to be able to tell if the event 'readable'/'data' is emitted + // immediately, or on a later tick. We set this to true at first, because + // any actions that shouldn't happen until "later" should generally also + // not happen before the first read call. this.sync = true; // whenever we return null, then we set a flag to say @@ -18410,15 +18027,14 @@ this.readableListening = false; this.resumeScheduled = false; + // has it been destroyed + this.destroyed = false; + // Crypto is kind of old and crusty. Historically, its default string // encoding is 'binary' so we have to make this configurable. // Everything else in the universe uses 'utf8', though. this.defaultEncoding = options.defaultEncoding || 'utf8'; - // when piping, we only care about 'readable' events that happen - // after read()ing all the bytes and not getting any pushback. - this.ranOut = false; - // the number of writers that are awaiting a drain event in .pipe()s this.awaitDrain = 0; @@ -18428,14 +18044,14 @@ this.decoder = null; this.encoding = null; if (options.encoding) { - if (!StringDecoder) StringDecoder = __webpack_require__(53).StringDecoder; + if (!StringDecoder) StringDecoder = __webpack_require__(54).StringDecoder; this.decoder = new StringDecoder(options.encoding); this.encoding = options.encoding; } } function Readable(options) { - Duplex = Duplex || __webpack_require__(48); + Duplex = Duplex || __webpack_require__(49); if (!(this instanceof Readable)) return new Readable(options); @@ -18444,87 +18060,129 @@ // legacy this.readable = true; - if (options && typeof options.read === 'function') this._read = options.read; + if (options) { + if (typeof options.read === 'function') this._read = options.read; + + if (typeof options.destroy === 'function') this._destroy = options.destroy; + } Stream.call(this); } + Object.defineProperty(Readable.prototype, 'destroyed', { + get: function () { + if (this._readableState === undefined) { + return false; + } + return this._readableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._readableState) { + return; + } + + // backward compatibility, the user is explicitly + // managing destroyed + this._readableState.destroyed = value; + } + }); + + Readable.prototype.destroy = destroyImpl.destroy; + Readable.prototype._undestroy = destroyImpl.undestroy; + Readable.prototype._destroy = function (err, cb) { + this.push(null); + cb(err); + }; + // Manually shove something into the read() buffer. // This returns true if the highWaterMark has not been hit yet, // similar to how Writable.write() returns true if you should // write() some more. Readable.prototype.push = function (chunk, encoding) { var state = this._readableState; + var skipChunkCheck; - if (!state.objectMode && typeof chunk === 'string') { - encoding = encoding || state.defaultEncoding; - if (encoding !== state.encoding) { - chunk = bufferShim.from(chunk, encoding); - encoding = ''; + if (!state.objectMode) { + if (typeof chunk === 'string') { + encoding = encoding || state.defaultEncoding; + if (encoding !== state.encoding) { + chunk = Buffer.from(chunk, encoding); + encoding = ''; + } + skipChunkCheck = true; } + } else { + skipChunkCheck = true; } - return readableAddChunk(this, state, chunk, encoding, false); + return readableAddChunk(this, chunk, encoding, false, skipChunkCheck); }; // Unshift should *always* be something directly out of read() Readable.prototype.unshift = function (chunk) { - var state = this._readableState; - return readableAddChunk(this, state, chunk, '', true); - }; - - Readable.prototype.isPaused = function () { - return this._readableState.flowing === false; + return readableAddChunk(this, chunk, null, true, false); }; - function readableAddChunk(stream, state, chunk, encoding, addToFront) { - var er = chunkInvalid(state, chunk); - if (er) { - stream.emit('error', er); - } else if (chunk === null) { + function readableAddChunk(stream, chunk, encoding, addToFront, skipChunkCheck) { + var state = stream._readableState; + if (chunk === null) { state.reading = false; onEofChunk(stream, state); - } else if (state.objectMode || chunk && chunk.length > 0) { - if (state.ended && !addToFront) { - var e = new Error('stream.push() after EOF'); - stream.emit('error', e); - } else if (state.endEmitted && addToFront) { - var _e = new Error('stream.unshift() after end event'); - stream.emit('error', _e); - } else { - var skipAdd; - if (state.decoder && !addToFront && !encoding) { - chunk = state.decoder.write(chunk); - skipAdd = !state.objectMode && chunk.length === 0; - } - - if (!addToFront) state.reading = false; - - // Don't add to the buffer if we've decoded to an empty string chunk and - // we're not in object mode - if (!skipAdd) { - // if we want the data now, just emit it. - if (state.flowing && state.length === 0 && !state.sync) { - stream.emit('data', chunk); - stream.read(0); + } else { + var er; + if (!skipChunkCheck) er = chunkInvalid(state, chunk); + if (er) { + stream.emit('error', er); + } else if (state.objectMode || chunk && chunk.length > 0) { + if (typeof chunk !== 'string' && Object.getPrototypeOf(chunk) !== Buffer.prototype && !state.objectMode) { + chunk = _uint8ArrayToBuffer(chunk); + } + + if (addToFront) { + if (state.endEmitted) stream.emit('error', new Error('stream.unshift() after end event'));else addChunk(stream, state, chunk, true); + } else if (state.ended) { + stream.emit('error', new Error('stream.push() after EOF')); + } else { + state.reading = false; + if (state.decoder && !encoding) { + chunk = state.decoder.write(chunk); + if (state.objectMode || chunk.length !== 0) addChunk(stream, state, chunk, false);else maybeReadMore(stream, state); } else { - // update the buffer info. - state.length += state.objectMode ? 1 : chunk.length; - if (addToFront) state.buffer.unshift(chunk);else state.buffer.push(chunk); - - if (state.needReadable) emitReadable(stream); + addChunk(stream, state, chunk, false); } } - - maybeReadMore(stream, state); + } else if (!addToFront) { + state.reading = false; } - } else if (!addToFront) { - state.reading = false; } return needMoreData(state); } + function addChunk(stream, state, chunk, addToFront) { + if (state.flowing && state.length === 0 && !state.sync) { + stream.emit('data', chunk); + stream.read(0); + } else { + // update the buffer info. + state.length += state.objectMode ? 1 : chunk.length; + if (addToFront) state.buffer.unshift(chunk);else state.buffer.push(chunk); + + if (state.needReadable) emitReadable(stream); + } + maybeReadMore(stream, state); + } + + function chunkInvalid(state, chunk) { + var er; + if (!_isUint8Array(chunk) && typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { + er = new TypeError('Invalid non-string/buffer chunk'); + } + return er; + } + // if it's past the high water mark, we can push in some more. // Also, if we have no data yet, we can stand some // more bytes. This is to work around cases where hwm=0, @@ -18536,9 +18194,13 @@ return !state.ended && (state.needReadable || state.length < state.highWaterMark || state.length === 0); } + Readable.prototype.isPaused = function () { + return this._readableState.flowing === false; + }; + // backwards compatibility. Readable.prototype.setEncoding = function (enc) { - if (!StringDecoder) StringDecoder = __webpack_require__(53).StringDecoder; + if (!StringDecoder) StringDecoder = __webpack_require__(54).StringDecoder; this._readableState.decoder = new StringDecoder(enc); this._readableState.encoding = enc; return this; @@ -18684,14 +18346,6 @@ return ret; }; - function chunkInvalid(state, chunk) { - var er = null; - if (!Buffer.isBuffer(chunk) && typeof chunk !== 'string' && chunk !== null && chunk !== undefined && !state.objectMode) { - er = new TypeError('Invalid non-string/buffer chunk'); - } - return er; - } - function onEofChunk(stream, state) { if (state.ended) return; if (state.decoder) { @@ -18779,14 +18433,17 @@ var doEnd = (!pipeOpts || pipeOpts.end !== false) && dest !== process.stdout && dest !== process.stderr; - var endFn = doEnd ? onend : cleanup; + var endFn = doEnd ? onend : unpipe; if (state.endEmitted) processNextTick(endFn);else src.once('end', endFn); dest.on('unpipe', onunpipe); - function onunpipe(readable) { + function onunpipe(readable, unpipeInfo) { debug('onunpipe'); if (readable === src) { - cleanup(); + if (unpipeInfo && unpipeInfo.hasUnpiped === false) { + unpipeInfo.hasUnpiped = true; + cleanup(); + } } } @@ -18812,7 +18469,7 @@ dest.removeListener('error', onerror); dest.removeListener('unpipe', onunpipe); src.removeListener('end', onend); - src.removeListener('end', cleanup); + src.removeListener('end', unpipe); src.removeListener('data', ondata); cleanedUp = true; @@ -18905,6 +18562,7 @@ Readable.prototype.unpipe = function (dest) { var state = this._readableState; + var unpipeInfo = { hasUnpiped: false }; // if we're not piping anywhere, then do nothing. if (state.pipesCount === 0) return this; @@ -18920,7 +18578,7 @@ state.pipes = null; state.pipesCount = 0; state.flowing = false; - if (dest) dest.emit('unpipe', this); + if (dest) dest.emit('unpipe', this, unpipeInfo); return this; } @@ -18935,7 +18593,7 @@ state.flowing = false; for (var i = 0; i < len; i++) { - dests[i].emit('unpipe', this); + dests[i].emit('unpipe', this, unpipeInfo); }return this; } @@ -18947,7 +18605,7 @@ state.pipesCount -= 1; if (state.pipesCount === 1) state.pipes = state.pipes[0]; - dest.emit('unpipe', this); + dest.emit('unpipe', this, unpipeInfo); return this; }; @@ -18968,7 +18626,7 @@ if (!state.reading) { processNextTick(nReadingNextTick, this); } else if (state.length) { - emitReadable(this, state); + emitReadable(this); } } } @@ -19169,7 +18827,7 @@ // This function is designed to be inlinable, so please take care when making // changes to the function body. function copyFromBuffer(n, list) { - var ret = bufferShim.allocUnsafe(n); + var ret = Buffer.allocUnsafe(n); var p = list.head; var c = 1; p.data.copy(ret); @@ -19292,116 +18950,69 @@ /* 44 */ /***/ (function(module, exports, __webpack_require__) { - /* WEBPACK VAR INJECTION */(function(global) {'use strict'; + /* eslint-disable node/no-deprecated-api */ + var buffer = __webpack_require__(24) + var Buffer = buffer.Buffer - var buffer = __webpack_require__(24); - var Buffer = buffer.Buffer; - var SlowBuffer = buffer.SlowBuffer; - var MAX_LEN = buffer.kMaxLength || 2147483647; - exports.alloc = function alloc(size, fill, encoding) { - if (typeof Buffer.alloc === 'function') { - return Buffer.alloc(size, fill, encoding); + // alternative to using Object.keys for old browsers + function copyProps (src, dst) { + for (var key in src) { + dst[key] = src[key] } - if (typeof encoding === 'number') { - throw new TypeError('encoding must not be number'); + } + if (Buffer.from && Buffer.alloc && Buffer.allocUnsafe && Buffer.allocUnsafeSlow) { + module.exports = buffer + } else { + // Copy properties from require('buffer') + copyProps(buffer, exports) + exports.Buffer = SafeBuffer + } + + function SafeBuffer (arg, encodingOrOffset, length) { + return Buffer(arg, encodingOrOffset, length) + } + + // Copy static methods from Buffer + copyProps(Buffer, SafeBuffer) + + SafeBuffer.from = function (arg, encodingOrOffset, length) { + if (typeof arg === 'number') { + throw new TypeError('Argument must not be a number') } + return Buffer(arg, encodingOrOffset, length) + } + + SafeBuffer.alloc = function (size, fill, encoding) { if (typeof size !== 'number') { - throw new TypeError('size must be a number'); - } - if (size > MAX_LEN) { - throw new RangeError('size is too large'); - } - var enc = encoding; - var _fill = fill; - if (_fill === undefined) { - enc = undefined; - _fill = 0; + throw new TypeError('Argument must be a number') } - var buf = new Buffer(size); - if (typeof _fill === 'string') { - var fillBuf = new Buffer(_fill, enc); - var flen = fillBuf.length; - var i = -1; - while (++i < size) { - buf[i] = fillBuf[i % flen]; + var buf = Buffer(size) + if (fill !== undefined) { + if (typeof encoding === 'string') { + buf.fill(fill, encoding) + } else { + buf.fill(fill) } } else { - buf.fill(_fill); + buf.fill(0) } - return buf; + return buf } - exports.allocUnsafe = function allocUnsafe(size) { - if (typeof Buffer.allocUnsafe === 'function') { - return Buffer.allocUnsafe(size); - } + + SafeBuffer.allocUnsafe = function (size) { if (typeof size !== 'number') { - throw new TypeError('size must be a number'); - } - if (size > MAX_LEN) { - throw new RangeError('size is too large'); + throw new TypeError('Argument must be a number') } - return new Buffer(size); + return Buffer(size) } - exports.from = function from(value, encodingOrOffset, length) { - if (typeof Buffer.from === 'function' && (!global.Uint8Array || Uint8Array.from !== Buffer.from)) { - return Buffer.from(value, encodingOrOffset, length); - } - if (typeof value === 'number') { - throw new TypeError('"value" argument must not be a number'); - } - if (typeof value === 'string') { - return new Buffer(value, encodingOrOffset); - } - if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) { - var offset = encodingOrOffset; - if (arguments.length === 1) { - return new Buffer(value); - } - if (typeof offset === 'undefined') { - offset = 0; - } - var len = length; - if (typeof len === 'undefined') { - len = value.byteLength - offset; - } - if (offset >= value.byteLength) { - throw new RangeError('\'offset\' is out of bounds'); - } - if (len > value.byteLength - offset) { - throw new RangeError('\'length\' is out of bounds'); - } - return new Buffer(value.slice(offset, offset + len)); - } - if (Buffer.isBuffer(value)) { - var out = new Buffer(value.length); - value.copy(out, 0, 0, value.length); - return out; - } - if (value) { - if (Array.isArray(value) || (typeof ArrayBuffer !== 'undefined' && value.buffer instanceof ArrayBuffer) || 'length' in value) { - return new Buffer(value); - } - if (value.type === 'Buffer' && Array.isArray(value.data)) { - return new Buffer(value.data); - } - } - throw new TypeError('First argument must be a string, Buffer, ' + 'ArrayBuffer, Array, or array-like object.'); - } - exports.allocUnsafeSlow = function allocUnsafeSlow(size) { - if (typeof Buffer.allocUnsafeSlow === 'function') { - return Buffer.allocUnsafeSlow(size); - } + SafeBuffer.allocUnsafeSlow = function (size) { if (typeof size !== 'number') { - throw new TypeError('size must be a number'); + throw new TypeError('Argument must be a number') } - if (size >= MAX_LEN) { - throw new RangeError('size is too large'); - } - return new SlowBuffer(size); + return buffer.SlowBuffer(size) } - /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) /***/ }), /* 45 */ @@ -19529,73 +19140,181 @@ 'use strict'; - var Buffer = __webpack_require__(24).Buffer; /**/ - var bufferShim = __webpack_require__(44); - /**/ - module.exports = BufferList; + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - function BufferList() { - this.head = null; - this.tail = null; - this.length = 0; + var Buffer = __webpack_require__(44).Buffer; + /**/ + + function copyBuffer(src, target, offset) { + src.copy(target, offset); } - BufferList.prototype.push = function (v) { - var entry = { data: v, next: null }; - if (this.length > 0) this.tail.next = entry;else this.head = entry; - this.tail = entry; - ++this.length; - }; + module.exports = function () { + function BufferList() { + _classCallCheck(this, BufferList); - BufferList.prototype.unshift = function (v) { - var entry = { data: v, next: this.head }; - if (this.length === 0) this.tail = entry; - this.head = entry; - ++this.length; - }; + this.head = null; + this.tail = null; + this.length = 0; + } - BufferList.prototype.shift = function () { - if (this.length === 0) return; - var ret = this.head.data; - if (this.length === 1) this.head = this.tail = null;else this.head = this.head.next; - --this.length; - return ret; - }; + BufferList.prototype.push = function push(v) { + var entry = { data: v, next: null }; + if (this.length > 0) this.tail.next = entry;else this.head = entry; + this.tail = entry; + ++this.length; + }; - BufferList.prototype.clear = function () { - this.head = this.tail = null; - this.length = 0; - }; + BufferList.prototype.unshift = function unshift(v) { + var entry = { data: v, next: this.head }; + if (this.length === 0) this.tail = entry; + this.head = entry; + ++this.length; + }; - BufferList.prototype.join = function (s) { - if (this.length === 0) return ''; - var p = this.head; - var ret = '' + p.data; - while (p = p.next) { - ret += s + p.data; - }return ret; - }; + BufferList.prototype.shift = function shift() { + if (this.length === 0) return; + var ret = this.head.data; + if (this.length === 1) this.head = this.tail = null;else this.head = this.head.next; + --this.length; + return ret; + }; + + BufferList.prototype.clear = function clear() { + this.head = this.tail = null; + this.length = 0; + }; + + BufferList.prototype.join = function join(s) { + if (this.length === 0) return ''; + var p = this.head; + var ret = '' + p.data; + while (p = p.next) { + ret += s + p.data; + }return ret; + }; + + BufferList.prototype.concat = function concat(n) { + if (this.length === 0) return Buffer.alloc(0); + if (this.length === 1) return this.head.data; + var ret = Buffer.allocUnsafe(n >>> 0); + var p = this.head; + var i = 0; + while (p) { + copyBuffer(p.data, ret, i); + i += p.data.length; + p = p.next; + } + return ret; + }; + + return BufferList; + }(); + +/***/ }), +/* 48 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + /**/ - BufferList.prototype.concat = function (n) { - if (this.length === 0) return bufferShim.alloc(0); - if (this.length === 1) return this.head.data; - var ret = bufferShim.allocUnsafe(n >>> 0); - var p = this.head; - var i = 0; - while (p) { - p.data.copy(ret, i); - i += p.data.length; - p = p.next; + var processNextTick = __webpack_require__(42); + /**/ + + // undocumented cb() API, needed for core, not for public API + function destroy(err, cb) { + var _this = this; + + var readableDestroyed = this._readableState && this._readableState.destroyed; + var writableDestroyed = this._writableState && this._writableState.destroyed; + + if (readableDestroyed || writableDestroyed) { + if (cb) { + cb(err); + } else if (err && (!this._writableState || !this._writableState.errorEmitted)) { + processNextTick(emitErrorNT, this, err); + } + return; } - return ret; + + // we set destroyed to true before firing error callbacks in order + // to make it re-entrance safe in case destroy() is called within callbacks + + if (this._readableState) { + this._readableState.destroyed = true; + } + + // if this is a duplex stream mark the writable part as destroyed as well + if (this._writableState) { + this._writableState.destroyed = true; + } + + this._destroy(err || null, function (err) { + if (!cb && err) { + processNextTick(emitErrorNT, _this, err); + if (_this._writableState) { + _this._writableState.errorEmitted = true; + } + } else if (cb) { + cb(err); + } + }); + } + + function undestroy() { + if (this._readableState) { + this._readableState.destroyed = false; + this._readableState.reading = false; + this._readableState.ended = false; + this._readableState.endEmitted = false; + } + + if (this._writableState) { + this._writableState.destroyed = false; + this._writableState.ended = false; + this._writableState.ending = false; + this._writableState.finished = false; + this._writableState.errorEmitted = false; + } + } + + function emitErrorNT(self, err) { + self.emit('error', err); + } + + module.exports = { + destroy: destroy, + undestroy: undestroy }; /***/ }), -/* 48 */ +/* 49 */ /***/ (function(module, exports, __webpack_require__) { + // Copyright Joyent, Inc. and other Node contributors. + // + // Permission is hereby granted, free of charge, to any person obtaining a + // copy of this software and associated documentation files (the + // "Software"), to deal in the Software without restriction, including + // without limitation the rights to use, copy, modify, merge, publish, + // distribute, sublicense, and/or sell copies of the Software, and to permit + // persons to whom the Software is furnished to do so, subject to the + // following conditions: + // + // The above copyright notice and this permission notice shall be included + // in all copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + // USE OR OTHER DEALINGS IN THE SOFTWARE. + // a duplex stream is just a stream that is both readable and writable. // Since JS doesn't have multiple prototypal inheritance, this class // prototypally inherits from Readable, and then parasitically from @@ -19605,6 +19324,10 @@ /**/ + var processNextTick = __webpack_require__(42); + /**/ + + /**/ var objectKeys = Object.keys || function (obj) { var keys = []; for (var key in obj) { @@ -19615,17 +19338,13 @@ module.exports = Duplex; - /**/ - var processNextTick = __webpack_require__(42); - /**/ - /**/ var util = __webpack_require__(45); util.inherits = __webpack_require__(38); /**/ var Readable = __webpack_require__(41); - var Writable = __webpack_require__(49); + var Writable = __webpack_require__(50); util.inherits(Duplex, Readable); @@ -19666,6 +19385,34 @@ self.end(); } + Object.defineProperty(Duplex.prototype, 'destroyed', { + get: function () { + if (this._readableState === undefined || this._writableState === undefined) { + return false; + } + return this._readableState.destroyed && this._writableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (this._readableState === undefined || this._writableState === undefined) { + return; + } + + // backward compatibility, the user is explicitly + // managing destroyed + this._readableState.destroyed = value; + this._writableState.destroyed = value; + } + }); + + Duplex.prototype._destroy = function (err, cb) { + this.push(null); + this.end(); + + processNextTick(cb, err); + }; + function forEach(xs, f) { for (var i = 0, l = xs.length; i < l; i++) { f(xs[i], i); @@ -19673,21 +19420,64 @@ } /***/ }), -/* 49 */ +/* 50 */ /***/ (function(module, exports, __webpack_require__) { - /* WEBPACK VAR INJECTION */(function(process, setImmediate) {// A bit simpler than readable streams. + /* WEBPACK VAR INJECTION */(function(process, setImmediate) {// Copyright Joyent, Inc. and other Node contributors. + // + // Permission is hereby granted, free of charge, to any person obtaining a + // copy of this software and associated documentation files (the + // "Software"), to deal in the Software without restriction, including + // without limitation the rights to use, copy, modify, merge, publish, + // distribute, sublicense, and/or sell copies of the Software, and to permit + // persons to whom the Software is furnished to do so, subject to the + // following conditions: + // + // The above copyright notice and this permission notice shall be included + // in all copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + // USE OR OTHER DEALINGS IN THE SOFTWARE. + + // A bit simpler than readable streams. // Implement an async ._write(chunk, encoding, cb), and it'll handle all // the drain event emission and buffering. 'use strict'; - module.exports = Writable; - /**/ + var processNextTick = __webpack_require__(42); /**/ + module.exports = Writable; + + /* */ + function WriteReq(chunk, encoding, cb) { + this.chunk = chunk; + this.encoding = encoding; + this.callback = cb; + this.next = null; + } + + // It seems a linked list but it is not + // there will be only 2 of these for each stream + function CorkedRequest(state) { + var _this = this; + + this.next = null; + this.entry = null; + this.finish = function () { + onCorkedFinish(_this, state); + }; + } + /* */ + /**/ var asyncWrite = !process.browser && ['v0.10', 'v0.9.'].indexOf(process.version.slice(0, 5)) > -1 ? setImmediate : processNextTick; /**/ @@ -19705,7 +19495,7 @@ /**/ var internalUtil = { - deprecate: __webpack_require__(52) + deprecate: __webpack_require__(53) }; /**/ @@ -19713,24 +19503,24 @@ var Stream = __webpack_require__(43); /**/ - var Buffer = __webpack_require__(24).Buffer; /**/ - var bufferShim = __webpack_require__(44); + var Buffer = __webpack_require__(44).Buffer; + function _uint8ArrayToBuffer(chunk) { + return Buffer.from(chunk); + } + function _isUint8Array(obj) { + return Object.prototype.toString.call(obj) === '[object Uint8Array]' || Buffer.isBuffer(obj); + } /**/ + var destroyImpl = __webpack_require__(48); + util.inherits(Writable, Stream); function nop() {} - function WriteReq(chunk, encoding, cb) { - this.chunk = chunk; - this.encoding = encoding; - this.callback = cb; - this.next = null; - } - function WritableState(options, stream) { - Duplex = Duplex || __webpack_require__(48); + Duplex = Duplex || __webpack_require__(49); options = options || {}; @@ -19748,7 +19538,10 @@ this.highWaterMark = hwm || hwm === 0 ? hwm : defaultHwm; // cast to ints. - this.highWaterMark = ~~this.highWaterMark; + this.highWaterMark = Math.floor(this.highWaterMark); + + // if _final has been called + this.finalCalled = false; // drain event flag. this.needDrain = false; @@ -19759,6 +19552,9 @@ // when 'finish' is emitted this.finished = false; + // has it been destroyed + this.destroyed = false; + // should we decode strings into buffers before passing to _write? // this is here so that some node-core streams can optimize string // handling at a lower level. @@ -19840,7 +19636,7 @@ Object.defineProperty(WritableState.prototype, 'buffer', { get: internalUtil.deprecate(function () { return this.getBuffer(); - }, '_writableState.buffer is deprecated. Use _writableState.getBuffer ' + 'instead.') + }, '_writableState.buffer is deprecated. Use _writableState.getBuffer ' + 'instead.', 'DEP0003') }); } catch (_) {} })(); @@ -19864,7 +19660,7 @@ } function Writable(options) { - Duplex = Duplex || __webpack_require__(48); + Duplex = Duplex || __webpack_require__(49); // Writable ctor is applied to Duplexes, too. // `realHasInstance` is necessary because using plain `instanceof` @@ -19886,6 +19682,10 @@ if (typeof options.write === 'function') this._write = options.write; if (typeof options.writev === 'function') this._writev = options.writev; + + if (typeof options.destroy === 'function') this._destroy = options.destroy; + + if (typeof options.final === 'function') this._final = options.final; } Stream.call(this); @@ -19926,7 +19726,11 @@ Writable.prototype.write = function (chunk, encoding, cb) { var state = this._writableState; var ret = false; - var isBuf = Buffer.isBuffer(chunk); + var isBuf = _isUint8Array(chunk) && !state.objectMode; + + if (isBuf && !Buffer.isBuffer(chunk)) { + chunk = _uint8ArrayToBuffer(chunk); + } if (typeof encoding === 'function') { cb = encoding; @@ -19971,7 +19775,7 @@ function decodeChunk(state, chunk, encoding) { if (!state.objectMode && state.decodeStrings !== false && typeof chunk === 'string') { - chunk = bufferShim.from(chunk, encoding); + chunk = Buffer.from(chunk, encoding); } return chunk; } @@ -19981,8 +19785,12 @@ // If we return false, then we need a drain event, so set that flag. function writeOrBuffer(stream, state, isBuf, chunk, encoding, cb) { if (!isBuf) { - chunk = decodeChunk(state, chunk, encoding); - if (Buffer.isBuffer(chunk)) encoding = 'buffer'; + var newChunk = decodeChunk(state, chunk, encoding); + if (chunk !== newChunk) { + isBuf = true; + encoding = 'buffer'; + chunk = newChunk; + } } var len = state.objectMode ? 1 : chunk.length; @@ -19994,7 +19802,13 @@ if (state.writing || state.corked) { var last = state.lastBufferedRequest; - state.lastBufferedRequest = new WriteReq(chunk, encoding, cb); + state.lastBufferedRequest = { + chunk: chunk, + encoding: encoding, + isBuf: isBuf, + callback: cb, + next: null + }; if (last) { last.next = state.lastBufferedRequest; } else { @@ -20019,10 +19833,26 @@ function onwriteError(stream, state, sync, er, cb) { --state.pendingcb; - if (sync) processNextTick(cb, er);else cb(er); - stream._writableState.errorEmitted = true; - stream.emit('error', er); + if (sync) { + // defer the callback if we are being called synchronously + // to avoid piling up things on the stack + processNextTick(cb, er); + // this can emit finish, and it will always happen + // after error + processNextTick(finishMaybe, stream, state); + stream._writableState.errorEmitted = true; + stream.emit('error', er); + } else { + // the caller expect this to happen before if + // it is async + cb(er); + stream._writableState.errorEmitted = true; + stream.emit('error', er); + // this can emit finish, but finish must + // always follow error + finishMaybe(stream, state); + } } function onwriteStateUpdate(state) { @@ -20087,11 +19917,14 @@ holder.entry = entry; var count = 0; + var allBuffers = true; while (entry) { buffer[count] = entry; + if (!entry.isBuf) allBuffers = false; entry = entry.next; count += 1; } + buffer.allBuffers = allBuffers; doWrite(stream, state, true, state.length, buffer, '', holder.finish); @@ -20165,23 +19998,37 @@ function needFinish(state) { return state.ending && state.length === 0 && state.bufferedRequest === null && !state.finished && !state.writing; } - - function prefinish(stream, state) { - if (!state.prefinished) { + function callFinal(stream, state) { + stream._final(function (err) { + state.pendingcb--; + if (err) { + stream.emit('error', err); + } state.prefinished = true; stream.emit('prefinish'); + finishMaybe(stream, state); + }); + } + function prefinish(stream, state) { + if (!state.prefinished && !state.finalCalled) { + if (typeof stream._final === 'function') { + state.pendingcb++; + state.finalCalled = true; + processNextTick(callFinal, stream, state); + } else { + state.prefinished = true; + stream.emit('prefinish'); + } } } function finishMaybe(stream, state) { var need = needFinish(state); if (need) { + prefinish(stream, state); if (state.pendingcb === 0) { - prefinish(stream, state); state.finished = true; stream.emit('finish'); - } else { - prefinish(stream, state); } } return need; @@ -20197,33 +20044,53 @@ stream.writable = false; } - // It seems a linked list but it is not - // there will be only 2 of these for each stream - function CorkedRequest(state) { - var _this = this; + function onCorkedFinish(corkReq, state, err) { + var entry = corkReq.entry; + corkReq.entry = null; + while (entry) { + var cb = entry.callback; + state.pendingcb--; + cb(err); + entry = entry.next; + } + if (state.corkedRequestsFree) { + state.corkedRequestsFree.next = corkReq; + } else { + state.corkedRequestsFree = corkReq; + } + } - this.next = null; - this.entry = null; - this.finish = function (err) { - var entry = _this.entry; - _this.entry = null; - while (entry) { - var cb = entry.callback; - state.pendingcb--; - cb(err); - entry = entry.next; + Object.defineProperty(Writable.prototype, 'destroyed', { + get: function () { + if (this._writableState === undefined) { + return false; } - if (state.corkedRequestsFree) { - state.corkedRequestsFree.next = _this; - } else { - state.corkedRequestsFree = _this; + return this._writableState.destroyed; + }, + set: function (value) { + // we ignore the value if the stream + // has not been initialized yet + if (!this._writableState) { + return; } - }; - } - /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(17), __webpack_require__(50).setImmediate)) + + // backward compatibility, the user is explicitly + // managing destroyed + this._writableState.destroyed = value; + } + }); + + Writable.prototype.destroy = destroyImpl.destroy; + Writable.prototype._undestroy = destroyImpl.undestroy; + Writable.prototype._destroy = function (err, cb) { + this.end(); + cb(err); + }; + + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(17), __webpack_require__(51).setImmediate)) /***/ }), -/* 50 */ +/* 51 */ /***/ (function(module, exports, __webpack_require__) { var apply = Function.prototype.apply; @@ -20276,13 +20143,13 @@ }; // setimmediate attaches itself to the global object - __webpack_require__(51); + __webpack_require__(52); exports.setImmediate = setImmediate; exports.clearImmediate = clearImmediate; /***/ }), -/* 51 */ +/* 52 */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(global, process) {(function (global, undefined) { @@ -20475,7 +20342,7 @@ /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()), __webpack_require__(17))) /***/ }), -/* 52 */ +/* 53 */ /***/ (function(module, exports) { /* WEBPACK VAR INJECTION */(function(global) { @@ -20549,13 +20416,12 @@ /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) /***/ }), -/* 53 */ +/* 54 */ /***/ (function(module, exports, __webpack_require__) { 'use strict'; - var Buffer = __webpack_require__(24).Buffer; - var bufferShim = __webpack_require__(44); + var Buffer = __webpack_require__(44).Buffer; var isEncoding = Buffer.isEncoding || function (encoding) { encoding = '' + encoding; @@ -20632,7 +20498,7 @@ } this.lastNeed = 0; this.lastTotal = 0; - this.lastChar = bufferShim.allocUnsafe(nb); + this.lastChar = Buffer.allocUnsafe(nb); } StringDecoder.prototype.write = function (buf) { @@ -20827,9 +20693,30 @@ } /***/ }), -/* 54 */ +/* 55 */ /***/ (function(module, exports, __webpack_require__) { + // Copyright Joyent, Inc. and other Node contributors. + // + // Permission is hereby granted, free of charge, to any person obtaining a + // copy of this software and associated documentation files (the + // "Software"), to deal in the Software without restriction, including + // without limitation the rights to use, copy, modify, merge, publish, + // distribute, sublicense, and/or sell copies of the Software, and to permit + // persons to whom the Software is furnished to do so, subject to the + // following conditions: + // + // The above copyright notice and this permission notice shall be included + // in all copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + // USE OR OTHER DEALINGS IN THE SOFTWARE. + // a transform stream is a readable/writable stream where you do // something with the data. Sometimes it's called a "filter", // but that's not a great name for it, since that implies a thing where @@ -20876,7 +20763,7 @@ module.exports = Transform; - var Duplex = __webpack_require__(48); + var Duplex = __webpack_require__(49); /**/ var util = __webpack_require__(45); @@ -20903,7 +20790,9 @@ var cb = ts.writecb; - if (!cb) return stream.emit('error', new Error('no writecb in Transform class')); + if (!cb) { + return stream.emit('error', new Error('write callback called multiple times')); + } ts.writechunk = null; ts.writecb = null; @@ -20996,6 +20885,15 @@ } }; + Transform.prototype._destroy = function (err, cb) { + var _this = this; + + Duplex.prototype._destroy.call(this, err, function (err2) { + cb(err2); + _this.emit('close'); + }); + }; + function done(stream, er, data) { if (er) return stream.emit('error', er); @@ -21014,9 +20912,30 @@ } /***/ }), -/* 55 */ +/* 56 */ /***/ (function(module, exports, __webpack_require__) { + // Copyright Joyent, Inc. and other Node contributors. + // + // Permission is hereby granted, free of charge, to any person obtaining a + // copy of this software and associated documentation files (the + // "Software"), to deal in the Software without restriction, including + // without limitation the rights to use, copy, modify, merge, publish, + // distribute, sublicense, and/or sell copies of the Software, and to permit + // persons to whom the Software is furnished to do so, subject to the + // following conditions: + // + // The above copyright notice and this permission notice shall be included + // in all copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + // USE OR OTHER DEALINGS IN THE SOFTWARE. + // a passthrough stream. // basically just the most minimal sort of Transform stream. // Every written chunk gets output as-is. @@ -21025,7 +20944,7 @@ module.exports = PassThrough; - var Transform = __webpack_require__(54); + var Transform = __webpack_require__(55); /**/ var util = __webpack_require__(45); @@ -21045,7 +20964,7 @@ }; /***/ }), -/* 56 */ +/* 57 */ /***/ (function(module, exports, __webpack_require__) { var Buffer = __webpack_require__(24).Buffer @@ -21078,7 +20997,7 @@ /***/ }), -/* 57 */ +/* 58 */ /***/ (function(module, exports) { module.exports = extend @@ -21103,7 +21022,7 @@ /***/ }), -/* 58 */ +/* 59 */ /***/ (function(module, exports) { module.exports = { @@ -21173,7 +21092,7 @@ /***/ }), -/* 59 */ +/* 60 */ /***/ (function(module, exports, __webpack_require__) { // Copyright Joyent, Inc. and other Node contributors. @@ -21199,8 +21118,8 @@ 'use strict'; - var punycode = __webpack_require__(60); - var util = __webpack_require__(61); + var punycode = __webpack_require__(61); + var util = __webpack_require__(62); exports.parse = urlParse; exports.resolve = urlResolve; @@ -21275,7 +21194,7 @@ 'gopher:': true, 'file:': true }, - querystring = __webpack_require__(62); + querystring = __webpack_require__(63); function urlParse(url, parseQueryString, slashesDenoteHost) { if (url && util.isObject(url) && url instanceof Url) return url; @@ -21911,7 +21830,7 @@ /***/ }), -/* 60 */ +/* 61 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(module, global) {/*! https://mths.be/punycode v1.3.2 by @mathias */ @@ -22446,7 +22365,7 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(9)(module), (function() { return this; }()))) /***/ }), -/* 61 */ +/* 62 */ /***/ (function(module, exports) { 'use strict'; @@ -22468,17 +22387,17 @@ /***/ }), -/* 62 */ +/* 63 */ /***/ (function(module, exports, __webpack_require__) { 'use strict'; - exports.decode = exports.parse = __webpack_require__(63); - exports.encode = exports.stringify = __webpack_require__(64); + exports.decode = exports.parse = __webpack_require__(64); + exports.encode = exports.stringify = __webpack_require__(65); /***/ }), -/* 63 */ +/* 64 */ /***/ (function(module, exports) { // Copyright Joyent, Inc. and other Node contributors. @@ -22564,7 +22483,7 @@ /***/ }), -/* 64 */ +/* 65 */ /***/ (function(module, exports) { // Copyright Joyent, Inc. and other Node contributors. @@ -22634,7 +22553,7 @@ /***/ }), -/* 65 */ +/* 66 */ /***/ (function(module, exports, __webpack_require__) { var http = __webpack_require__(35); @@ -22654,7 +22573,7 @@ /***/ }), -/* 66 */ +/* 67 */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(Buffer) {/* @@ -22676,14 +22595,14 @@ * under the License. */ var util = __webpack_require__(16); - var WebSocket = __webpack_require__(67); + var WebSocket = __webpack_require__(68); var EventEmitter = __webpack_require__(21).EventEmitter; var thrift = __webpack_require__(15); - var ttransport = __webpack_require__(68); - var tprotocol = __webpack_require__(70); + var ttransport = __webpack_require__(69); + var tprotocol = __webpack_require__(71); var TBufferedTransport = __webpack_require__(23); - var TJSONProtocol = __webpack_require__(72); + var TJSONProtocol = __webpack_require__(73); var InputBufferUnderrunError = __webpack_require__(29); var createClient = __webpack_require__(33); @@ -22947,7 +22866,7 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 67 */ +/* 68 */ /***/ (function(module, exports) { @@ -22996,7 +22915,7 @@ /***/ }), -/* 68 */ +/* 69 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -23019,12 +22938,12 @@ */ module.exports.TBufferedTransport = __webpack_require__(23); - module.exports.TFramedTransport = __webpack_require__(69); + module.exports.TFramedTransport = __webpack_require__(70); module.exports.InputBufferUnderrunError = __webpack_require__(29); /***/ }), -/* 69 */ +/* 70 */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(Buffer) {/* @@ -23213,7 +23132,7 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 70 */ +/* 71 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -23236,12 +23155,12 @@ */ module.exports.TBinaryProtocol = __webpack_require__(30); - module.exports.TCompactProtocol = __webpack_require__(71); - module.exports.TJSONProtocol = __webpack_require__(72); + module.exports.TCompactProtocol = __webpack_require__(72); + module.exports.TJSONProtocol = __webpack_require__(73); /***/ }), -/* 71 */ +/* 72 */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(Buffer) {/* @@ -24166,7 +24085,7 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 72 */ +/* 73 */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(Buffer) {/* @@ -24190,13 +24109,13 @@ var log = __webpack_require__(31); var Int64 = __webpack_require__(32); - var InputBufferUnderrunError = __webpack_require__(68).InputBufferUnderrunError; + var InputBufferUnderrunError = __webpack_require__(69).InputBufferUnderrunError; var Thrift = __webpack_require__(15); var Type = Thrift.Type; var util = __webpack_require__(16); - var Int64Util = __webpack_require__(73); - var json_parse = __webpack_require__(74); + var Int64Util = __webpack_require__(74); + var json_parse = __webpack_require__(75); var InputBufferUnderrunError = __webpack_require__(29); @@ -24916,7 +24835,7 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 73 */ +/* 74 */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(Buffer) {/* @@ -25014,7 +24933,7 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 74 */ +/* 75 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -25040,7 +24959,7 @@ */ var Int64 = __webpack_require__(32); - var Int64Util = __webpack_require__(73); + var Int64Util = __webpack_require__(74); var json_parse = module.exports = (function () { "use strict"; @@ -25319,7 +25238,7 @@ /***/ }), -/* 75 */ +/* 76 */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(Buffer) {/* @@ -25345,7 +25264,7 @@ var thrift = __webpack_require__(15); var TBufferedTransport = __webpack_require__(23); - var TJSONProtocol = __webpack_require__(72); + var TJSONProtocol = __webpack_require__(73); var InputBufferUnderrunError = __webpack_require__(29); var createClient = __webpack_require__(33); @@ -25606,7 +25525,7 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 76 */ +/* 77 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -25719,7 +25638,7 @@ /***/ }), -/* 77 */ +/* 78 */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(Buffer) {/* @@ -25741,13 +25660,13 @@ * under the License. */ var http = __webpack_require__(35); - var https = __webpack_require__(65); - var url = __webpack_require__(59); - var path = __webpack_require__(78); + var https = __webpack_require__(66); + var url = __webpack_require__(60); + var path = __webpack_require__(79); var fs = __webpack_require__(22); - var crypto = __webpack_require__(79); + var crypto = __webpack_require__(80); - var MultiplexedProcessor = __webpack_require__(113).MultiplexedProcessor; + var MultiplexedProcessor = __webpack_require__(114).MultiplexedProcessor; var TBufferedTransport = __webpack_require__(23); var TBinaryProtocol = __webpack_require__(30); @@ -26290,7 +26209,7 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 78 */ +/* 79 */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(process) {// Copyright Joyent, Inc. and other Node contributors. @@ -26521,10 +26440,10 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(17))) /***/ }), -/* 79 */ +/* 80 */ /***/ (function(module, exports, __webpack_require__) { - /* WEBPACK VAR INJECTION */(function(Buffer) {var rng = __webpack_require__(80) + /* WEBPACK VAR INJECTION */(function(Buffer) {var rng = __webpack_require__(81) function error () { var m = [].slice.call(arguments).join(' ') @@ -26535,9 +26454,9 @@ ].join('\n')) } - exports.createHash = __webpack_require__(82) + exports.createHash = __webpack_require__(83) - exports.createHmac = __webpack_require__(91) + exports.createHmac = __webpack_require__(92) exports.randomBytes = function(size, callback) { if (callback && callback.call) { @@ -26558,10 +26477,10 @@ return ['sha1', 'sha256', 'sha512', 'md5', 'rmd160'] } - var p = __webpack_require__(92)(exports) + var p = __webpack_require__(93)(exports) exports.pbkdf2 = p.pbkdf2 exports.pbkdf2Sync = p.pbkdf2Sync - __webpack_require__(94)(exports, module.exports); + __webpack_require__(95)(exports, module.exports); // the least I can do is make error messages for the rest of the node.js/crypto api. each(['createCredentials' @@ -26577,13 +26496,13 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 80 */ +/* 81 */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(global, Buffer) {(function() { var g = ('undefined' === typeof window ? global : window) || {} _crypto = ( - g.crypto || g.msCrypto || __webpack_require__(81) + g.crypto || g.msCrypto || __webpack_require__(82) ) module.exports = function(size) { // Modern Browsers @@ -26610,19 +26529,19 @@ /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()), __webpack_require__(24).Buffer)) /***/ }), -/* 81 */ +/* 82 */ /***/ (function(module, exports) { /* (ignored) */ /***/ }), -/* 82 */ +/* 83 */ /***/ (function(module, exports, __webpack_require__) { - /* WEBPACK VAR INJECTION */(function(Buffer) {var createHash = __webpack_require__(83) + /* WEBPACK VAR INJECTION */(function(Buffer) {var createHash = __webpack_require__(84) - var md5 = toConstructor(__webpack_require__(88)) - var rmd160 = toConstructor(__webpack_require__(90)) + var md5 = toConstructor(__webpack_require__(89)) + var rmd160 = toConstructor(__webpack_require__(91)) function toConstructor (fn) { return function () { @@ -26653,7 +26572,7 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 83 */ +/* 84 */ /***/ (function(module, exports, __webpack_require__) { var exports = module.exports = function (alg) { @@ -26663,15 +26582,15 @@ } var Buffer = __webpack_require__(24).Buffer - var Hash = __webpack_require__(84)(Buffer) + var Hash = __webpack_require__(85)(Buffer) - exports.sha1 = __webpack_require__(85)(Buffer, Hash) - exports.sha256 = __webpack_require__(86)(Buffer, Hash) - exports.sha512 = __webpack_require__(87)(Buffer, Hash) + exports.sha1 = __webpack_require__(86)(Buffer, Hash) + exports.sha256 = __webpack_require__(87)(Buffer, Hash) + exports.sha512 = __webpack_require__(88)(Buffer, Hash) /***/ }), -/* 84 */ +/* 85 */ /***/ (function(module, exports) { module.exports = function (Buffer) { @@ -26754,7 +26673,7 @@ /***/ }), -/* 85 */ +/* 86 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -26898,7 +26817,7 @@ /***/ }), -/* 86 */ +/* 87 */ /***/ (function(module, exports, __webpack_require__) { @@ -27051,7 +26970,7 @@ /***/ }), -/* 87 */ +/* 88 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(16).inherits @@ -27301,7 +27220,7 @@ /***/ }), -/* 88 */ +/* 89 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -27313,7 +27232,7 @@ * See http://pajhome.org.uk/crypt/md5 for more info. */ - var helpers = __webpack_require__(89); + var helpers = __webpack_require__(90); /* * Calculate the MD5 of an array of little-endian words, and a bit length @@ -27462,7 +27381,7 @@ /***/ }), -/* 89 */ +/* 90 */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(Buffer) {var intSize = 4; @@ -27503,7 +27422,7 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 90 */ +/* 91 */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(Buffer) { @@ -27715,10 +27634,10 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 91 */ +/* 92 */ /***/ (function(module, exports, __webpack_require__) { - /* WEBPACK VAR INJECTION */(function(Buffer) {var createHash = __webpack_require__(82) + /* WEBPACK VAR INJECTION */(function(Buffer) {var createHash = __webpack_require__(83) var zeroBuffer = new Buffer(128) zeroBuffer.fill(0) @@ -27765,10 +27684,10 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 92 */ +/* 93 */ /***/ (function(module, exports, __webpack_require__) { - var pbkdf2Export = __webpack_require__(93) + var pbkdf2Export = __webpack_require__(94) module.exports = function (crypto, exports) { exports = exports || {} @@ -27783,7 +27702,7 @@ /***/ }), -/* 93 */ +/* 94 */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(Buffer) {module.exports = function(crypto) { @@ -27874,18 +27793,18 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 94 */ +/* 95 */ /***/ (function(module, exports, __webpack_require__) { module.exports = function (crypto, exports) { exports = exports || {}; - var ciphers = __webpack_require__(95)(crypto); + var ciphers = __webpack_require__(96)(crypto); exports.createCipher = ciphers.createCipher; exports.createCipheriv = ciphers.createCipheriv; - var deciphers = __webpack_require__(112)(crypto); + var deciphers = __webpack_require__(113)(crypto); exports.createDecipher = deciphers.createDecipher; exports.createDecipheriv = deciphers.createDecipheriv; - var modes = __webpack_require__(103); + var modes = __webpack_require__(104); function listCiphers () { return Object.keys(modes); } @@ -27895,15 +27814,15 @@ /***/ }), -/* 95 */ +/* 96 */ /***/ (function(module, exports, __webpack_require__) { - /* WEBPACK VAR INJECTION */(function(Buffer) {var aes = __webpack_require__(96); - var Transform = __webpack_require__(97); + /* WEBPACK VAR INJECTION */(function(Buffer) {var aes = __webpack_require__(97); + var Transform = __webpack_require__(98); var inherits = __webpack_require__(38); - var modes = __webpack_require__(103); - var ebtk = __webpack_require__(104); - var StreamCipher = __webpack_require__(105); + var modes = __webpack_require__(104); + var ebtk = __webpack_require__(105); + var StreamCipher = __webpack_require__(106); inherits(Cipher, Transform); function Cipher(mode, key, iv) { if (!(this instanceof Cipher)) { @@ -27964,11 +27883,11 @@ return out; }; var modelist = { - ECB: __webpack_require__(106), - CBC: __webpack_require__(107), - CFB: __webpack_require__(109), - OFB: __webpack_require__(110), - CTR: __webpack_require__(111) + ECB: __webpack_require__(107), + CBC: __webpack_require__(108), + CFB: __webpack_require__(110), + OFB: __webpack_require__(111), + CTR: __webpack_require__(112) }; module.exports = function (crypto) { function createCipheriv(suite, password, iv) { @@ -28010,7 +27929,7 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 96 */ +/* 97 */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(Buffer) {var uint_max = Math.pow(2, 32); @@ -28212,10 +28131,10 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 97 */ +/* 98 */ /***/ (function(module, exports, __webpack_require__) { - /* WEBPACK VAR INJECTION */(function(Buffer) {var Transform = __webpack_require__(98).Transform; + /* WEBPACK VAR INJECTION */(function(Buffer) {var Transform = __webpack_require__(99).Transform; var inherits = __webpack_require__(38); module.exports = CipherBase; @@ -28250,7 +28169,7 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 98 */ +/* 99 */ /***/ (function(module, exports, __webpack_require__) { // Copyright Joyent, Inc. and other Node contributors. @@ -28281,10 +28200,10 @@ inherits(Stream, EE); Stream.Readable = __webpack_require__(40); - Stream.Writable = __webpack_require__(99); - Stream.Duplex = __webpack_require__(100); - Stream.Transform = __webpack_require__(101); - Stream.PassThrough = __webpack_require__(102); + Stream.Writable = __webpack_require__(100); + Stream.Duplex = __webpack_require__(101); + Stream.Transform = __webpack_require__(102); + Stream.PassThrough = __webpack_require__(103); // Backwards-compat with node 0.4.x Stream.Stream = Stream; @@ -28383,35 +28302,35 @@ /***/ }), -/* 99 */ +/* 100 */ /***/ (function(module, exports, __webpack_require__) { - module.exports = __webpack_require__(49); + module.exports = __webpack_require__(50); /***/ }), -/* 100 */ +/* 101 */ /***/ (function(module, exports, __webpack_require__) { - module.exports = __webpack_require__(48); + module.exports = __webpack_require__(49); /***/ }), -/* 101 */ +/* 102 */ /***/ (function(module, exports, __webpack_require__) { module.exports = __webpack_require__(40).Transform /***/ }), -/* 102 */ +/* 103 */ /***/ (function(module, exports, __webpack_require__) { module.exports = __webpack_require__(40).PassThrough /***/ }), -/* 103 */ +/* 104 */ /***/ (function(module, exports) { exports['aes-128-ecb'] = { @@ -28524,7 +28443,7 @@ }; /***/ }), -/* 104 */ +/* 105 */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(Buffer) { @@ -28587,11 +28506,11 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 105 */ +/* 106 */ /***/ (function(module, exports, __webpack_require__) { - /* WEBPACK VAR INJECTION */(function(Buffer) {var aes = __webpack_require__(96); - var Transform = __webpack_require__(97); + /* WEBPACK VAR INJECTION */(function(Buffer) {var aes = __webpack_require__(97); + var Transform = __webpack_require__(98); var inherits = __webpack_require__(38); inherits(StreamCipher, Transform); @@ -28619,7 +28538,7 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 106 */ +/* 107 */ /***/ (function(module, exports) { exports.encrypt = function (self, block) { @@ -28630,10 +28549,10 @@ }; /***/ }), -/* 107 */ +/* 108 */ /***/ (function(module, exports, __webpack_require__) { - var xor = __webpack_require__(108); + var xor = __webpack_require__(109); exports.encrypt = function (self, block) { var data = xor(block, self._prev); self._prev = self._cipher.encryptBlock(data); @@ -28647,7 +28566,7 @@ }; /***/ }), -/* 108 */ +/* 109 */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(Buffer) {module.exports = xor; @@ -28663,10 +28582,10 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 109 */ +/* 110 */ /***/ (function(module, exports, __webpack_require__) { - /* WEBPACK VAR INJECTION */(function(Buffer) {var xor = __webpack_require__(108); + /* WEBPACK VAR INJECTION */(function(Buffer) {var xor = __webpack_require__(109); exports.encrypt = function (self, data, decrypt) { var out = new Buffer(''); var len; @@ -28696,10 +28615,10 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 110 */ +/* 111 */ /***/ (function(module, exports, __webpack_require__) { - /* WEBPACK VAR INJECTION */(function(Buffer) {var xor = __webpack_require__(108); + /* WEBPACK VAR INJECTION */(function(Buffer) {var xor = __webpack_require__(109); function getBlock(self) { self._prev = self._cipher.encryptBlock(self._prev); return self._prev; @@ -28715,10 +28634,10 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 111 */ +/* 112 */ /***/ (function(module, exports, __webpack_require__) { - /* WEBPACK VAR INJECTION */(function(Buffer) {var xor = __webpack_require__(108); + /* WEBPACK VAR INJECTION */(function(Buffer) {var xor = __webpack_require__(109); function getBlock(self) { var out = self._cipher.encryptBlock(self._prev); incr32(self._prev); @@ -28749,15 +28668,15 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 112 */ +/* 113 */ /***/ (function(module, exports, __webpack_require__) { - /* WEBPACK VAR INJECTION */(function(Buffer) {var aes = __webpack_require__(96); - var Transform = __webpack_require__(97); + /* WEBPACK VAR INJECTION */(function(Buffer) {var aes = __webpack_require__(97); + var Transform = __webpack_require__(98); var inherits = __webpack_require__(38); - var modes = __webpack_require__(103); - var StreamCipher = __webpack_require__(105); - var ebtk = __webpack_require__(104); + var modes = __webpack_require__(104); + var StreamCipher = __webpack_require__(106); + var ebtk = __webpack_require__(105); inherits(Decipher, Transform); function Decipher(mode, key, iv) { @@ -28825,11 +28744,11 @@ } var modelist = { - ECB: __webpack_require__(106), - CBC: __webpack_require__(107), - CFB: __webpack_require__(109), - OFB: __webpack_require__(110), - CTR: __webpack_require__(111) + ECB: __webpack_require__(107), + CBC: __webpack_require__(108), + CFB: __webpack_require__(110), + OFB: __webpack_require__(111), + CTR: __webpack_require__(112) }; module.exports = function (crypto) { @@ -28873,7 +28792,7 @@ /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(24).Buffer)) /***/ }), -/* 113 */ +/* 114 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -28942,7 +28861,7 @@ /***/ }), -/* 114 */ +/* 115 */ /***/ (function(module, exports, __webpack_require__) { /* WEBPACK VAR INJECTION */(function(process, setImmediate) {// vim:ts=4:sts=4:sw=4: @@ -30850,10 +30769,10 @@ }); - /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(17), __webpack_require__(50).setImmediate)) + /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(17), __webpack_require__(51).setImmediate)) /***/ }), -/* 115 */ +/* 116 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -30932,7 +30851,7 @@ /***/ }), -/* 116 */ +/* 117 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -35056,7 +34975,7 @@ }; /***/ }), -/* 117 */ +/* 118 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -35066,11 +34985,11 @@ }); exports.default = processQueryResults; - var _processColumnarResults = __webpack_require__(118); + var _processColumnarResults = __webpack_require__(119); var _processColumnarResults2 = _interopRequireDefault(_processColumnarResults); - var _processRowResults = __webpack_require__(119); + var _processRowResults = __webpack_require__(120); var _processRowResults2 = _interopRequireDefault(_processRowResults); @@ -35158,7 +35077,7 @@ } /***/ }), -/* 118 */ +/* 119 */ /***/ (function(module, exports) { "use strict"; @@ -35282,7 +35201,7 @@ } /***/ }), -/* 119 */ +/* 120 */ /***/ (function(module, exports) { "use strict"; diff --git a/dist/node-connector.js b/dist/node-connector.js index 3ef47f5d..91348d35 100644 --- a/dist/node-connector.js +++ b/dist/node-connector.js @@ -25406,8 +25406,6 @@ module.exports = var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; - var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); - var _helpers = __webpack_require__(56); var helpers = _interopRequireWildcard(_helpers); @@ -25424,8 +25422,6 @@ module.exports = function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - var _ref = isNodeRuntime() && __webpack_require__(52) || window, TDatumType = _ref.TDatumType, TEncodingType = _ref.TEncodingType, @@ -25444,1245 +25440,837 @@ module.exports = } - var COMPRESSION_LEVEL_DEFAULT = 3; - - function arrayify(maybeArray) { - return Array.isArray(maybeArray) ? maybeArray : [maybeArray]; - } - - function isNodeRuntime() { - return typeof window === "undefined"; + // Set a global Connector function when Connector is brought in via script tag. + if (( false ? "undefined" : _typeof(module)) === "object" && module.exports) { + if (!isNodeRuntime()) { + window.Connector = Connector; + } } + module.exports = Connector; + exports.default = Connector; - var MapdCon = function () { - function MapdCon() { - var _this = this; - - _classCallCheck(this, MapdCon); - - this.updateQueryTimes = function (conId, queryId, estimatedQueryTime, execution_time_ms) { - _this.queryTimes[queryId] = execution_time_ms; - }; - - this.getFrontendViews = function (callback) { - if (_this._sessionId) { - _this._client[0].get_frontend_views(_this._sessionId[0], callback); - } else { - callback(new Error("No Session ID")); - } - }; - - this.getFrontendViewsAsync = function () { - return new Promise(function (resolve, reject) { - _this.getFrontendViews(function (error, views) { - if (error) { - reject(error); - } else { - resolve(views); - } - }); - }); - }; - - this.getFrontendView = function (viewName, callback) { - if (_this._sessionId && viewName) { - _this._client[0].get_frontend_view(_this._sessionId[0], viewName, callback); - } else { - callback(new Error("No Session ID")); - } - }; - - this.getFrontendViewAsync = function (viewName) { - return new Promise(function (resolve, reject) { - _this.getFrontendView(viewName, function (err, view) { - if (err) { - reject(err); - } else { - resolve(view); - } - }); - }); - }; - - this.getServerStatus = function (callback) { - _this._client[0].get_server_status(_this._sessionId[0], callback); - }; - - this.getServerStatusAsync = function () { - return new Promise(function (resolve, reject) { - _this.getServerStatus(function (err, result) { - if (err) { - reject(err); - } else { - resolve(result); - } - }); - }); - }; - - this.deleteFrontendViewAsync = function (viewName) { - return new Promise(function (resolve, reject) { - _this.deleteFrontendView(viewName, function (err) { - if (err) { - reject(err); - } else { - resolve(viewName); - } - }); - }); - }; - - this.getLinkView = function (link, callback) { - _this._client[0].get_link_view(_this._sessionId[0], link, callback); - }; - - this.getLinkViewAsync = function (link) { - return new Promise(function (resolve, reject) { - _this.getLinkView(link, function (err, theLink) { - if (err) { - reject(err); - } else { - resolve(theLink); - } - }); - }); - }; - - this.queryAsync = this.query; - - this.createTableAsync = function (tableName, rowDescObj, tableType) { - return new Promise(function (resolve, reject) { - _this.createTable(tableName, rowDescObj, tableType, function (err) { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); - }; - - this.importTableAsync = this.importTableAsyncWrapper(false); - this.importTableGeoAsync = this.importTableAsyncWrapper(true); - - this._host = null; - this._user = null; - this._password = null; - this._port = null; - this._dbName = null; - this._client = null; - this._sessionId = null; - this._protocol = null; - this._datumEnum = {}; - this._logging = false; - this._platform = "mapd"; - this._nonce = 0; - this._balanceStrategy = "adaptive"; - this._numConnections = 0; - this._lastRenderCon = 0; - this.queryTimes = {}; - this.serverQueueTimes = null; - this.serverPingTimes = null; - this.pingCount = null; - this.DEFAULT_QUERY_TIME = 50; - this.NUM_PINGS_PER_SERVER = 4; - this.importerRowDesc = null; - - // invoke initialization methods - this.invertDatumTypes(); - - this.processResults = function () { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var result = arguments[1]; - var callback = arguments[2]; - - var processor = (0, _processQueryResults2.default)(_this._logging, _this.updateQueryTimes); - var processResultsObject = processor(options, _this._datumEnum, result, callback); - return processResultsObject; - }; - // return this to allow chaining off of instantiation - return this; - } + var COMPRESSION_LEVEL_DEFAULT = 3; + var DEFAULT_QUERY_TIME = 50; + + function Connector() { + // initialize variables + var _datumEnum = {}; + var _queryTimes = {}; + var _client = null; + var _dbName = null; + var _host = null; + var _logging = false; + var _nonce = 0; + var _password = null; + var _port = null; + var _protocol = null; + var _sessionId = null; + var _user = null; + + // invoke initialization methods + publicizeMethods(this, [connect, createFrontendView, createLink, createTable, dbName, deleteFrontendView, detectColumnTypes, disconnect, getFields, getFrontendView, getFrontendViews, getLinkView, getPixel, getServerStatus, getTables, host, importShapeTable, importTable, logging, password, port, protocol, query, renderVega, sessionId, user, validateQuery]); + invertDatumTypes(_datumEnum); + + // return this to allow chaining off of instantiation + return this; + // public methods /** * Create a connection to the server, generating a client and session id. - * @param {Function} callback A callback that takes `(err, success)` as its signature. Returns con singleton on success. - * @return {MapdCon} Object + * @param {Function} callback (error, session) => {…} + * @returns {undefined} * * @example Connect to a MapD server: - * var con = new MapdCon() + * var con = new Connector() * .host('localhost') * .port('8080') * .dbName('myDatabase') * .user('foo') * .password('bar') - * .connect((err, con) => console.log(con.sessionId())); + * .connect((error, con) => console.log(con.sessionId())); * * // ["om9E9Ujgbhl6wIzWgLENncjWsaXRDYLy"] */ + function connect(callback) { + var _this = this; + // eslint-disable-line consistent-return + if (_sessionId) { + disconnect(); + } + + if ([_host, _port, _user, _password, _dbName].some(Array.isArray)) { + console.warn("Connection parameters as arrays is deprecated; use single values."); // eslint-disable-line no-console + _host = Array.isArray(_host) ? _host[0] : _host; + _port = Array.isArray(_port) ? _port[0] : _port; + _user = Array.isArray(_user) ? _user[0] : _user; + _password = Array.isArray(_password) ? _password[0] : _password; + _dbName = Array.isArray(_dbName) ? _dbName[0] : _dbName; + } + + if (!_user) { + return callback("Please enter a username."); + } else if (!_password) { + return callback("Please enter a password."); + } else if (!_dbName) { + return callback("Please enter a database."); + } else if (!_host) { + return callback("Please enter a host name."); + } else if (!_port) { + return callback("Please enter a port."); + } + + _client = null; + _sessionId = null; + _protocol = _protocol || window.location.protocol.replace(":", ""); + + var transportUrl = _protocol + "://" + _host + ":" + _port; + var client = null; + + if (isNodeRuntime()) { + var _parseUrl = parseUrl(transportUrl), + parsedProtocol = _parseUrl.protocol, + parsedHost = _parseUrl.hostname, + parsedPort = _parseUrl.port; + + var connection = thriftWrapper.createHttpConnection(parsedHost, parsedPort, { + transport: thriftWrapper.TBufferedTransport, + protocol: thriftWrapper.TJSONProtocol, + path: "/", + headers: { Connection: "close" }, + https: parsedProtocol === "https:" + }); + connection.on("error", console.error); // eslint-disable-line no-console + client = thriftWrapper.createClient(MapDThrift, connection); + resetThriftClientOnArgumentErrorForMethods(this, client, Object.keys(this)); + } else { + var thriftTransport = new Thrift.Transport(transportUrl); + var thriftProtocol = new Thrift.Protocol(thriftTransport); + client = new _mapdClientV2.default(thriftProtocol); + } + client.connect(_user, _password, _dbName, function (error, newSessionId) { + // eslint-disable-line no-loop-func + if (error) { + return callback(normalizeError(error)); + } + _client = client; + _sessionId = newSessionId; + return callback(null, _this); + }); + } + /** + * Disconnect from the server then clears the client and session values. + * @param {Function} callback (error) => {…} + * @returns {undefined} + * + * @example Disconnect from the server: + * + * con.sessionId() // ["om9E9Ujgbhl6wIzWgLENncjWsaXRDYLy"] + * con.disconnect((err) => { + * console.error(err); + * con.sessionId() === null; + * }) + */ + function disconnect() { + var _this2 = this; - _createClass(MapdCon, [{ - key: "connect", - value: function connect(callback) { - var _this2 = this; + var callback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : noop; - if (this._sessionId) { - this.disconnect(); + if (_sessionId !== null) { + _client.disconnect(_sessionId, function (error) { + // eslint-disable-line no-loop-func + if (error) { + return callback(error, _this2); + } + _sessionId = null; + _client = null; + return callback(null, _this2); + }); + } + } + /** + * Get a dashboard object containing a value for the view_state property. + * This object contains a value for the view_state property, + * but not for the view_name property. + * @param {String} viewName the name of the dashboard + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example Get a specific dashboard from the server: + * + * con.getFrontendView('dashboard_name').then((result) => console.log(result)) + * // {TFrontendView} + */ + function getFrontendView(viewName, callback) { + if (_sessionId && viewName) { + _client.get_frontend_view(_sessionId, viewName, callback); + return; + } else { + callback(new Error("No Session ID")); + return; + } + } + /** + * Get the recent dashboards as a list of TFrontendView objects. + * These objects contain a value for the view_name property, + * but not for the view_state property. + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example Get the list of dashboards from the server: + * + * con.getFrontendViews().then((results) => console.log(results)) + * // [TFrontendView, TFrontendView] + */ + function getFrontendViews(callback) { + if (_sessionId) { + _client.get_frontend_views(_sessionId, callback); + } else { + callback(new Error("No Session ID")); + return; + } + } + /** + * Get the status of the server as a TServerStatus object. + * This includes whether the server is read-only, + * has backend rendering enabled, and the version number. + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example Get the server status: + * + * con.getServerStatus().then((result) => console.log(result)) + * // { + * // "read_only": false, + * // "version": "3.0.0dev-20170503-40e2de3", + * // "rendering_enabled": true, + * // "start_time": 1493840131 + * // } + */ + function getServerStatus(callback) { + _client.get_server_status(_sessionId, callback); + } + /** + * Add a new dashboard to the server. + * @param {String} viewName - the name of the new dashboard + * @param {String} viewState - the base64-encoded state string of the new dashboard + * @param {String} imageHash - the numeric hash of the dashboard thumbnail + * @param {String} metaData - Stringified metaData related to the view + * @param {Function} callback (error) => {…} success returns null + * @returns {undefined} + * + * @example Add a new dashboard to the server: + * + * con.createFrontendView('newSave', 'viewstateBase64', null, 'metaData').then(res => console.log(res)) + */ + function createFrontendView(viewName, viewState, imageHash, metaData, callback) { + if (!_sessionId) { + return callback(new Error("You are not connected to a server. Try running the connect method first.")); + } + return _client.create_frontend_view(_sessionId, viewName, viewState, imageHash, metaData, callback); + } + /** + * Delete a dashboard object containing a value for the view_state property. + * @param {String} viewName - the name of the dashboard + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example Delete a specific dashboard from the server: + * + * con.deleteFrontendView('dashboard_name').then(res => console.log(res)) + */ + function deleteFrontendView(viewName, callback) { + if (!_sessionId) { + return callback(new Error("You are not connected to a server. Try running the connect method first.")); + } + try { + // eslint-disable-line no-restricted-syntax + return _client.delete_frontend_view(_sessionId, viewName, callback); + } catch (err) { + return callback(new Error("Could not delete the frontend view; check your session id.", err)); + } + } + /** + * Create a short hash to make it easy to share a link to a specific dashboard. + * @param {String} viewState - the base64-encoded state string of the new dashboard + * @param {String} metaData - Stringified metaData related to the link + * @param {Function} callback (error, id) => {…} + * @returns {undefined} + * + * @example Create a link to the current state of a dashboard: + * + * con.createLink("eyJuYW1lIjoibXlkYXNoYm9hcmQifQ==", 'metaData').then(res => console.log(res)); + * // ["28127951"] + */ + function createLink(viewState, metaData, callback) { + return _client.create_link(_sessionId, viewState, metaData, function (error, data) { + if (error) { + return callback(normalizeError(error)); } - - // TODO: should be its own function - var allAreArrays = Array.isArray(this._host) && Array.isArray(this._port) && Array.isArray(this._user) && Array.isArray(this._password) && Array.isArray(this._dbName); - if (!allAreArrays) { - return callback("All connection parameters must be arrays."); + var result = data.split(",").reduce(function (links, link) { + // eslint-disable-line max-nested-callbacks + if (!links.includes(link)) { + links.push(link); + } + return links; + }, []); + if (!result || result.length !== 1) { + return callback(new Error("Different links were created on connection")); + } else { + return callback(null, result.join()); } - - this._client = []; - this._sessionId = []; - - if (!this._user[0]) { - return callback("Please enter a username."); - } else if (!this._password[0]) { - return callback("Please enter a password."); - } else if (!this._dbName[0]) { - return callback("Please enter a database."); - } else if (!this._host[0]) { - return callback("Please enter a host name."); - } else if (!this._port[0]) { - return callback("Please enter a port."); + }); + } + /** + * Get a fully-formed dashboard object from a generated share link. + * This object contains the given link for the view_name property, + * @param {String} link - the short hash of the dashboard, see {@link createLink} + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example Get a dashboard from a link: + * + * con.getLinkView('28127951').then(res => console.log(res)) + * // { + * // "view_name": "28127951", + * // "view_state": "eyJuYW1lIjoibXlkYXNoYm9hcmQifQ==", + * // "image_hash": "", + * // "update_time": "2017-04-28T21:34:01Z", + * // "view_metadata": "metaData" + * // } + */ + function getLinkView(link, callback) { + _client.get_link_view(_sessionId, link, function (error, data) { + if (error) { + return callback(normalizeError(error)); + } else { + return callback(null, data); } + }); + } + /** + * Asynchronously get the data from an importable file, + * such as a .csv or plaintext file with a header. + * @param {String} fileName - the name of the importable file + * @param {TCopyParams} copyParams - see {@link TCopyParams} + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example Get data from table_data.csv: + * + * var copyParams = new TCopyParams(); + * con.detectColumnTypes('table_data.csv', copyParams).then(res => console.log(res)) + * // TDetectResult {row_set: TRowSet, copy_params: TCopyParams} + * + */ + function detectColumnTypes(fileName, copyParams, callback) { + var thriftCopyParams = helpers.convertObjectToThriftCopyParams(copyParams); + _client.detect_column_types(_sessionId, fileName, thriftCopyParams, callback); + } + /** + * Submit a query to the database and process the results. + * @param {String} sql The query to perform + * @param {Object} options the options for the query + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example create a query + * + * var query = "SELECT count(*) AS n FROM tweets_nov_feb WHERE country='CO'"; + * var options = {}; + * + * con.query(query, options, function(error, data) { + * console.log(data) + * }); + * + */ + function query(sql, options, callback) { + var columnarResults = true; + var eliminateNullRows = false; + var queryId = null; + var returnTiming = false; + var limit = -1; + if (options) { + columnarResults = options.hasOwnProperty("columnarResults") ? options.columnarResults : columnarResults; + eliminateNullRows = options.hasOwnProperty("eliminateNullRows") ? options.eliminateNullRows : eliminateNullRows; + queryId = options.hasOwnProperty("queryId") ? options.queryId : queryId; + returnTiming = options.hasOwnProperty("returnTiming") ? options.returnTiming : returnTiming; + limit = options.hasOwnProperty("limit") ? options.limit : limit; + } + + var lastQueryTime = queryId in _queryTimes ? _queryTimes[queryId] : DEFAULT_QUERY_TIME; + + var curNonce = (_nonce++).toString(); + + var processResultsOptions = { + returnTiming: returnTiming, + eliminateNullRows: eliminateNullRows, + sql: sql, + queryId: queryId, + conId: 0, + estimatedQueryTime: lastQueryTime + }; - // now check to see if length of all arrays are the same and > 0 - var hostLength = this._host.length; - if (hostLength < 1) { - return callback("Must have at least one server to connect to."); + _client.sql_execute(_sessionId, sql, columnarResults, curNonce, limit, function (error, result) { + if (error) { + return callback(normalizeError(error)); + } else { + return processResults(result, callback, _logging, _datumEnum, processResultsOptions); } - if (hostLength !== this._port.length || hostLength !== this._user.length || hostLength !== this._password.length || hostLength !== this._dbName.length) { - return callback("Array connection parameters must be of equal length."); + }); + } + /** + * Submit a query to validate whether the backend can create a result set based on the SQL statement. + * @param {String} sql The query to perform + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example create a query + * + * var query = "SELECT count(*) AS n FROM tweets_nov_feb WHERE country='CO'"; + * + * con.validateQuery(query).then(res => console.log(res)) + * + * // [{ + * // "name": "n", + * // "type": "INT", + * // "is_array": false, + * // "is_dict": false + * // }] + * + */ + function validateQuery(sql, callback) { + _client.sql_validate(_sessionId, sql, function (error, data) { + if (error) { + return callback(normalizeError(error)); + } else { + return callback(null, convertFromThriftTypes(data, _datumEnum)); } - - if (!this._protocol) { - this._protocol = this._host.map(function () { - return window.location.protocol.replace(":", ""); - }); + }); + } + /** + * Get the names of the databases that exist on the current session's connectdion. + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example Get the list of tables from a connection: + * + * con.getTables().then(res => console.log(res)) + * + * // [{ + * // label: 'obs', // deprecated property + * // name: 'myDatabaseName' + * // }, + * // ...] + */ + function getTables(callback) { + _client.get_tables(_sessionId, function (error, tables) { + if (error) { + return callback(normalizeError(error)); + } else { + return callback(null, tables.map(function (table) { + return { name: table, label: "obs" }; + })); } - - var transportUrls = this.getEndpoints(); - - var _loop = function _loop(h) { - var client = null; - - if (isNodeRuntime()) { - var _parseUrl = parseUrl(transportUrls[h]), - protocol = _parseUrl.protocol, - hostname = _parseUrl.hostname, - port = _parseUrl.port; - - var connection = thriftWrapper.createHttpConnection(hostname, port, { - transport: thriftWrapper.TBufferedTransport, - protocol: thriftWrapper.TJSONProtocol, - path: "/", - headers: { Connection: "close" }, - https: protocol === "https:" - }); - connection.on("error", console.error); // eslint-disable-line no-console - client = thriftWrapper.createClient(MapDThrift, connection); - resetThriftClientOnArgumentErrorForMethods(_this2, client, ["connect", "createFrontendViewAsync", "createLinkAsync", "createTableAsync", "dbName", "deleteFrontendViewAsync", "detectColumnTypesAsync", "disconnect", "getFields", "getFrontendViewAsync", "getFrontendViewsAsync", "getLinkViewAsync", "getResultRowForPixel", "getServerStatusAsync", "getTablesAsync", "host", "importTableAsync", "importTableGeoAsync", "logging", "password", "port", "protocol", "query", "renderVega", "sessionId", "user", "validateQuery"]); - } else { - var thriftTransport = new Thrift.Transport(transportUrls[h]); - var thriftProtocol = new Thrift.Protocol(thriftTransport); - client = new _mapdClientV2.default(thriftProtocol); - } - - client.connect(_this2._user[h], _this2._password[h], _this2._dbName[h], function (error, sessionId) { - if (error) { - callback(error); - return; - } - _this2._client.push(client); - _this2._sessionId.push(sessionId); - _this2._numConnections = _this2._client.length; - callback(null, _this2); - }); - }; - - for (var h = 0; h < hostLength; h++) { - _loop(h); - } - - return this; - } - }, { - key: "convertFromThriftTypes", - value: function convertFromThriftTypes(fields) { - var fieldsArray = []; - // silly to change this from map to array - // - then later it turns back to map - for (var key in fields) { - if (fields.hasOwnProperty(key)) { - fieldsArray.push({ - name: key, - type: this._datumEnum[fields[key].col_type.type], - is_array: fields[key].col_type.is_array, - is_dict: fields[key].col_type.encoding === TEncodingType.DICT // eslint-disable-line no-undef - }); - } + }); + } + /** + * Get a list of field objects for a given table. + * @param {String} tableName - name of table containing field names + * @param {Function} callback - (error, fields) => {…} + * @returns {undefined} + * + * @example Get the list of fields from a specific table: + * + * con.getFields('flights', (error, res) => console.log(res)) + * // [{ + * name: 'fieldName', + * type: 'BIGINT', + * is_array: false, + * is_dict: false + * }, ...] + */ + function getFields(tableName, callback) { + _client.get_table_details(_sessionId, tableName, function (error, fields) { + if (fields) { + var rowDict = fields.row_desc.reduce(function (accum, value) { + accum[value.col_name] = value; + return accum; + }, {}); + return callback(null, convertFromThriftTypes(rowDict, _datumEnum)); + } else { + return callback(normalizeError(error)); } - return fieldsArray; + }); + } + /** + * Create a table and persist it to the backend. + * @param {String} tableName - desired name of the new table + * @param {Array} rowDescObj - fields of the new table + * @param {Number} tableType - the types of tables a user can import into the db + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example Create a new table: + * + * con.createTable('mynewtable', [TColumnType, TColumnType, ...], 0).then(res => console.log(res)); + * // undefined + */ + function createTable(tableName, rowDescObj, tableType, callback) { + if (!_sessionId) { + return callback(new Error("You are not connected to a server. Try running the connect method first.")); } + var thriftRowDesc = helpers.mutateThriftRowDesc(rowDescObj, null); + return _client.create_table(_sessionId, tableName, thriftRowDesc, tableType, callback); + } + /** + * Import a delimited table from a file. + * @param {String} tableName - desired name of the new table + * @param {String} fileName - name of imported file + * @param {TCopyParams} copyParams - see {@link TCopyParams} + * @param {TColumnType[]} rowDescObj -- a colleciton of metadata related to the table headers + * @param {Function} callback (error, data) => {…} + * @param {Boolean} isShapeFile false by default, enabled to import shape data. + * @returns {undefined} + */ + function importTable(tableName, fileName, copyParams, rowDescObj, callback) { + var isShapeFile = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false; - /** - * Disconnect from the server then clears the client and session values. - * @return {MapdCon} Object - * @param {Function} callback A callback that takes `(err, success)` as its signature. Returns con singleton on success. - * - * @example Disconnect from the server: - * - * con.sessionId() // ["om9E9Ujgbhl6wIzWgLENncjWsaXRDYLy"] - * con.disconnect((err, con) => console.log(err, con)) - * con.sessionId() === null; - */ - - }, { - key: "disconnect", - value: function disconnect(callback) { - var _this3 = this; - - if (this._sessionId !== null) { - for (var c = 0; c < this._client.length; c++) { - this._client[c].disconnect(this._sessionId[c], function (error) { - // Success will return NULL - - if (error) { - return callback(error, _this3); - } - _this3._sessionId = null; - _this3._client = null; - _this3._numConnections = 0; - _this3.serverPingTimes = null; - return callback(null, _this3); - }); - } - } - return this; + if (!_sessionId) { + return callback(new Error("You are not connected to a server. Try running the connect method first.")); } - /** - * Get the recent dashboards as a list of TFrontendView objects. - * These objects contain a value for the view_name property, - * but not for the view_state property. - * @return {Promise} An array which has all saved dashboards. - * - * @example Get the list of dashboards from the server: - * - * con.getFrontendViewsAsync().then((results) => console.log(results)) - * // [TFrontendView, TFrontendView] - */ + var thriftCopyParams = helpers.convertObjectToThriftCopyParams(copyParams); + var thriftRowDesc = helpers.mutateThriftRowDesc(rowDescObj, null); + if (isShapeFile) { + return _client.import_geo_table(_sessionId, tableName, fileName, thriftCopyParams, thriftRowDesc, callback); + } else { + return _client.import_table(_sessionId, tableName, fileName, thriftCopyParams, callback); + } + } + /** + * Import a geo table from a file. + * @param {String} tableName - desired name of the new table + * @param {String} fileName - name of imported file + * @param {TCopyParams} copyParams - see {@link TCopyParams} + * @param {TColumnType[]} rowDescObj -- a colleciton of metadata related to the table headers + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + */ + function importShapeTable(tableName, fileName, copyParams, rowDescObj, callback) { + return importTable(tableName, fileName, copyParams, rowDescObj, callback, true); + } + /** + * Use for backend rendering. This method will fetch a PNG image + * that is a render of the vega json object. + * + * @param {Number} widgetid the widget id of the calling widget + * @param {String} vega the vega json + * @param {Object} options the options for the render query + * @param {Number} options.compressionLevel the png compression level. + * range 1 (low compression, faster) to 10 (high compression, slower). + * Default 3. + * @param {Function} callback (error, Base64Image) => {…} + * @returns {undefined} + */ + function renderVega(widgetid, vega, options, callback) { + var queryId = null; + var compressionLevel = COMPRESSION_LEVEL_DEFAULT; + if (options) { + queryId = options.hasOwnProperty("queryId") ? options.queryId : queryId; + compressionLevel = options.hasOwnProperty("compressionLevel") ? options.compressionLevel : compressionLevel; + } - /** - * Get a dashboard object containing a value for the view_state property. - * This object contains a value for the view_state property, - * but not for the view_name property. - * @param {String} viewName the name of the dashboard - * @return {Promise.} An object that contains all data and metadata related to the dashboard - * - * @example Get a specific dashboard from the server: - * - * con.getFrontendViewAsync('dashboard_name').then((result) => console.log(result)) - * // {TFrontendView} - */ + var lastQueryTime = queryId in _queryTimes ? _queryTimes[queryId] : DEFAULT_QUERY_TIME; + var curNonce = (_nonce++).toString(); - /** - * Get the status of the server as a TServerStatus object. - * This includes whether the server is read-only, - * has backend rendering enabled, and the version number. - * @return {Promise.} - * - * @example Get the server status: - * - * con.getServerStatusAsync().then((result) => console.log(result)) - * // { - * // "read_only": false, - * // "version": "3.0.0dev-20170503-40e2de3", - * // "rendering_enabled": true, - * // "start_time": 1493840131 - * // } - */ + var processResultsOptions = { + isImage: true, + query: "render: " + vega, + queryId: queryId, + conId: 0, + estimatedQueryTime: lastQueryTime + }; - }, { - key: "createFrontendViewAsync", + _client.render_vega(_sessionId, widgetid, vega, compressionLevel, curNonce, function (error, result) { + if (error) { + return callback(normalizeError(error)); + } + return processResults(result, callback, _logging, _datumEnum, processResultsOptions); + }); + } + /** + * Used primarily for backend rendered maps, this method will fetch the row + * for a specific table that was last rendered at a pixel. + * @param {Number} widgetId - the widget id of the caller + * @param {TPixel} pixel - the pixel (lower left-hand corner is pixel (0,0)) + * @param {Object} tableColNamesMap - object of tableName -> array of col names + * @param {Function} callback (error, results) => {…} + * @param {Number} [pixelRadius=2] - the radius around the primary pixel to search + * @returns {undefined} + */ + function getPixel(widgetId, pixel, tableColNamesMap, callback) { + var pixelRadius = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 2; - - /** - * Add a new dashboard to the server. - * @param {String} viewName - the name of the new dashboard - * @param {String} viewState - the base64-encoded state string of the new dashboard - * @param {String} imageHash - the numeric hash of the dashboard thumbnail - * @param {String} metaData - Stringified metaData related to the view - * @return {Promise} Returns empty if success - * - * @example Add a new dashboard to the server: - * - * con.createFrontendViewAsync('newSave', 'viewstateBase64', null, 'metaData').then(res => console.log(res)) - */ - value: function createFrontendViewAsync(viewName, viewState, imageHash, metaData) { - var _this4 = this; - - if (!this._sessionId) { - return new Promise(function (resolve, reject) { - reject(new Error("You are not connected to a server. Try running the connect method first.")); - }); - } - - return Promise.all(this._client.map(function (client, i) { - return new Promise(function (resolve, reject) { - client.create_frontend_view(_this4._sessionId[i], viewName, viewState, imageHash, metaData, function (error, data) { - if (error) { - reject(error); - } else { - resolve(data); - } - }); - }); - })); + if (Array.isArray(callback)) { + console.warn("getPixel callbacks array deprecated; pass single callback instead."); // eslint-disable-line no-console + callback = callback[0]; } - }, { - key: "deleteFrontendView", - value: function deleteFrontendView(viewName, callback) { - var _this5 = this; - - if (!this._sessionId) { - throw new Error("You are not connected to a server. Try running the connect method first."); - } - try { - this._client.forEach(function (client, i) { - // do we want to try each one individually so if we fail we keep going? - client.delete_frontend_view(_this5._sessionId[i], viewName, callback); - }); - } catch (err) { - console.log("ERROR: Could not delete the frontend view. Check your session id.", err); - } + if (!(pixel instanceof TPixel)) { + pixel = new TPixel(pixel); } - - /** - * Delete a dashboard object containing a value for the view_state property. - * @param {String} viewName - the name of the dashboard - * @return {Promise.} Name of dashboard successfully deleted - * - * @example Delete a specific dashboard from the server: - * - * con.deleteFrontendViewAsync('dashboard_name').then(res => console.log(res)) - */ - - }, { - key: "createLinkAsync", - - - /** - * Create a short hash to make it easy to share a link to a specific dashboard. - * @param {String} viewState - the base64-encoded state string of the new dashboard - * @param {String} metaData - Stringified metaData related to the link - * @return {Promise.} link - A short hash of the dashboard used for URLs - * - * @example Create a link to the current state of a dashboard: - * - * con.createLinkAsync("eyJuYW1lIjoibXlkYXNoYm9hcmQifQ==", 'metaData').then(res => console.log(res)); - * // ["28127951"] - */ - value: function createLinkAsync(viewState, metaData) { - var _this6 = this; - - return Promise.all(this._client.map(function (client, i) { - return new Promise(function (resolve, reject) { - client.create_link(_this6._sessionId[i], viewState, metaData, function (error, data) { - if (error) { - reject(error); - } else { - var result = data.split(",").reduce(function (links, link) { - if (links.indexOf(link) === -1) { - links.push(link); - } - return links; - }, []); - if (!result || result.length !== 1) { - reject(new Error("Different links were created on connection")); - } else { - resolve(result.join()); - } - } - }); - }); - })); + var columnFormat = true; + var curNonce = String(_nonce++); + _client.get_result_row_for_pixel(_sessionId, widgetId, pixel, tableColNamesMap, columnFormat, pixelRadius, curNonce, processPixelResults.bind(this, callback, _logging, _datumEnum)); + } + /** + * Get or set the session ID used by the server to serve the correct data. + * This is typically set by {@link connect} and should not be set manually. + * @param {Number} newSessionId - The session ID of the current connection + * @returns {Number|Connector} - The session ID or the Connector itself + * + * @example Get the session id: + * + * con.sessionId(); + * // sessionID === 3145846410 + * + * @example Set the session id: + * var con = new Connector().connect().sessionId(3415846410); + * // NOTE: It is generally unsafe to set the session id manually. + */ + function sessionId(newSessionId) { + if (!arguments.length) { + return _sessionId; } - - /** - * Get a fully-formed dashboard object from a generated share link. - * This object contains the given link for the view_name property, - * @param {String} link - the short hash of the dashboard, see {@link createLink} - * @return {Promise.} Object of the dashboard and metadata - * - * @example Get a dashboard from a link: - * - * con.getLinkViewAsync('28127951').then(res => console.log(res)) - * // { - * // "view_name": "28127951", - * // "view_state": "eyJuYW1lIjoibXlkYXNoYm9hcmQifQ==", - * // "image_hash": "", - * // "update_time": "2017-04-28T21:34:01Z", - * // "view_metadata": "metaData" - * // } - */ - - }, { - key: "detectColumnTypes", - value: function detectColumnTypes(fileName, copyParams, callback) { - var thriftCopyParams = helpers.convertObjectToThriftCopyParams(copyParams); - this._client[0].detect_column_types(this._sessionId[0], fileName, thriftCopyParams, callback); + _sessionId = newSessionId; + return this; + } + /** + * Get or set the connection server hostname. + * This is is typically the first method called after instantiating a new Connector. + * @param {String} hostname - The hostname address + * @returns {String|Connector} - The hostname or the Connector itself + * + * @example Set the hostname: + * var con = new Connector().host('localhost'); + * + * @example Get the hostname: + * var host = con.host(); + * // host === 'localhost' + */ + function host(hostname) { + if (!arguments.length) { + return _host; } - - /** - * Asynchronously get the data from an importable file, - * such as a .csv or plaintext file with a header. - * @param {String} fileName - the name of the importable file - * @param {TCopyParams} copyParams - see {@link TCopyParams} - * @returns {Promise.} An object which has copy_params and row_set - * - * @example Get data from table_data.csv: - * - * var copyParams = new TCopyParams(); - * con.detectColumnTypesAsync('table_data.csv', copyParams).then(res => console.log(res)) - * // TDetectResult {row_set: TRowSet, copy_params: TCopyParams} - * - */ - - }, { - key: "detectColumnTypesAsync", - value: function detectColumnTypesAsync(fileName, copyParams) { - var _this7 = this; - - return new Promise(function (resolve, reject) { - _this7.detectColumnTypes.bind(_this7, fileName, copyParams)(function (err, res) { - if (err) { - reject(err); - } else { - _this7.importerRowDesc = res.row_set.row_desc; - resolve(res); - } - }); - }); + _host = hostname; + return this; + } + /** + * Get or set the connection port. + * @param {String} thePort - The port to connect on + * @returns {String|Connector} - The port or the Connector itself + * + * @example Set the port: + * var con = new Connector().port('8080'); + * + * @example Get the port: + * var port = con.port(); + * // port === '8080' + */ + function port(thePort) { + if (!arguments.length) { + return _port; } - - /** - * Submit a query to the database and process the results. - * @param {String} query The query to perform - * @param {Object} options the options for the query - * @param {Function} callback that takes `(err, result) => result` - * @returns {Object} The result of the query - * - * @example create a query - * - * var query = "SELECT count(*) AS n FROM tweets_nov_feb WHERE country='CO'"; - * var options = {}; - * - * con.query(query, options, function(err, result) { - * console.log(result) - * }); - * - */ - - }, { - key: "query", - value: function query(_query, options, callback) { - var _this8 = this; - - var columnarResults = true; - var eliminateNullRows = false; - var queryId = null; - var returnTiming = false; - var limit = -1; - if (options) { - columnarResults = options.hasOwnProperty("columnarResults") ? options.columnarResults : columnarResults; - eliminateNullRows = options.hasOwnProperty("eliminateNullRows") ? options.eliminateNullRows : eliminateNullRows; - queryId = options.hasOwnProperty("queryId") ? options.queryId : queryId; - returnTiming = options.hasOwnProperty("returnTiming") ? options.returnTiming : returnTiming; - limit = options.hasOwnProperty("limit") ? options.limit : limit; - } - - var lastQueryTime = queryId in this.queryTimes ? this.queryTimes[queryId] : this.DEFAULT_QUERY_TIME; - - var curNonce = (this._nonce++).toString(); - - var conId = 0; - - var processResultsOptions = { - returnTiming: returnTiming, - eliminateNullRows: eliminateNullRows, - query: _query, - queryId: queryId, - conId: conId, - estimatedQueryTime: lastQueryTime - }; - - try { - if (callback) { - this._client[conId].sql_execute(this._sessionId[conId], _query, columnarResults, curNonce, limit, function (error, result) { - if (error) { - callback(error); - } else { - _this8.processResults(processResultsOptions, result, callback); - } - }); - return curNonce; - } else if (!callback) { - var SQLExecuteResult = this._client[conId].sql_execute(this._sessionId[conId], _query, columnarResults, curNonce, limit); - return this.processResults(processResultsOptions, SQLExecuteResult); - } - } catch (err) { - if (err.name === "NetworkError") { - this.removeConnection(conId); - if (this._numConnections === 0) { - err.msg = "No remaining database connections"; - throw err; - } - this.query(_query, options, callback); - } else if (callback) { - callback(err); - } else { - throw err; - } - } + _port = thePort; + return this; + } + /** + * Get or set the username to authenticate with. + * @param {String} username - The username to authenticate with + * @returns {String|Connector} - The username or the Connector itself + * + * @example Set the username: + * var con = new Connector().user('foo'); + * + * @example Get the username: + * var username = con.user(); + * // user === 'foo' + */ + function user(username) { + if (!arguments.length) { + return _user; } - - /** @deprecated will default to query */ - - }, { - key: "validateQuery", - - - /** - * Submit a query to validate whether the backend can create a result set based on the SQL statement. - * @param {String} query The query to perform - * @returns {Promise.} The result of whether the query is valid - * - * @example create a query - * - * var query = "SELECT count(*) AS n FROM tweets_nov_feb WHERE country='CO'"; - * - * con.validateQuery(query).then(res => console.log(res)) - * - * // [{ - * // "name": "n", - * // "type": "INT", - * // "is_array": false, - * // "is_dict": false - * // }] - * - */ - value: function validateQuery(query) { - var _this9 = this; - - return new Promise(function (resolve, reject) { - _this9._client[0].sql_validate(_this9._sessionId[0], query, function (error, res) { - if (error) { - reject(error); - } else { - resolve(_this9.convertFromThriftTypes(res)); - } - }); - }); + _user = username; + return this; + } + /** + * Get or set the user's password to authenticate with. + * @param {String} pass - The password to authenticate with + * @returns {String|Connector} - The password or the Connector itself + * + * @example Set the password: + * var con = new Connector().password('bar'); + * + * @example Get the username: + * var password = con.password(); + * // password === 'bar' + */ + function password(pass) { + if (!arguments.length) { + return _password; } - }, { - key: "removeConnection", - value: function removeConnection(conId) { - if (conId < 0 || conId >= this.numConnections) { - var err = { - msg: "Remove connection id invalid" - }; - throw err; - } - this._client.splice(conId, 1); - this._sessionId.splice(conId, 1); - this._numConnections--; + _password = pass; + return this; + } + /** + * Get or set the name of the database to connect to. + * @param {String} db - The database to connect to + * @returns {String|Connector} - The name of the database or the Connector itself + * + * @example Set the database name: + * var con = new Connector().dbName('myDatabase'); + * + * @example Get the database name: + * var dbName = con.dbName(); + * // dbName === 'myDatabase' + */ + function dbName(db) { + if (!arguments.length) { + return _dbName; } - }, { - key: "getTables", - value: function getTables(callback) { - this._client[0].get_tables(this._sessionId[0], function (error, tables) { - if (error) { - callback(error); - } else { - callback(null, tables.map(function (table) { - return { - name: table, - label: "obs" - }; - })); - } - }); + _dbName = db; + return this; + } + /** + * Whether the raw queries strings will be logged to the console. + * Used primarily for debugging and defaults to false. + * @param {Boolean} loggingEnabled - Set to true to enable logging + * @returns {Boolean|Connector} - The current logging flag or Connector itself + * + * @example Set logging to true: + * var con = new Connector().logging(true); + * + * @example Get the logging flag: + * var isLogging = con.logging(); + * // isLogging === true + */ + function logging(loggingEnabled) { + if (typeof loggingEnabled === "undefined") { + return _logging; + } else if (typeof loggingEnabled !== "boolean") { + return "logging can only be set with boolean values"; } - - /** - * Get the names of the databases that exist on the current session's connectdion. - * @return {Promise.} list of table objects containing the label and table names. - * - * @example Get the list of tables from a connection: - * - * con.getTablesAsync().then(res => console.log(res)) - * - * // [{ - * // label: 'obs', // deprecated property - * // name: 'myDatabaseName' - * // }, - * // ...] - */ - - }, { - key: "getTablesAsync", - value: function getTablesAsync() { - var _this10 = this; - - return new Promise(function (resolve, reject) { - _this10.getTables.bind(_this10)(function (error, tables) { - if (error) { - reject(error); - } else { - resolve(tables); - } - }); - }); + _logging = loggingEnabled; + var isEnabledTxt = loggingEnabled ? "enabled" : "disabled"; + return "SQL logging is now " + isEnabledTxt; + } + /** + * The protocol to use for requests. + * @param {String} theProtocol - http or https + * @returns {String|Connector} - protocol or Connector itself + * + * @example Set the protocol: + * var con = new Connector().protocol('http'); + * + * @example Get the protocol: + * var protocol = con.protocol(); + * // protocol === 'http' + */ + function protocol(theProtocol) { + if (!arguments.length) { + return _protocol; } + _protocol = theProtocol; + return this; + } + } - /** - * Create an array-like object from {@link TDatumType} by - * flipping the string key and numerical value around. - * - * @returns {Undefined} This function does not return anything - */ + // helper functions - }, { - key: "invertDatumTypes", - value: function invertDatumTypes() { - var datumType = TDatumType; // eslint-disable-line no-undef - for (var key in datumType) { - if (datumType.hasOwnProperty(key)) { - this._datumEnum[datumType[key]] = key; - } - } - } + function noop() {/* noop */} - /** - * Get a list of field objects for a given table. - * @param {String} tableName - name of table containing field names - * @param {Function} callback - (err, results) - * @return {Array} fields - the formmatted list of field objects - * - * @example Get the list of fields from a specific table: - * - * con.getFields('flights', (err, res) => console.log(res)) - * // [{ - * name: 'fieldName', - * type: 'BIGINT', - * is_array: false, - * is_dict: false - * }, ...] - */ + function isNodeRuntime() { + return typeof window === "undefined"; + } - }, { - key: "getFields", - value: function getFields(tableName, callback) { - var _this11 = this; + function publicizeMethods(theClass, methods) { + methods.forEach(function (method) { + theClass[method.name] = method; + }); + } - this._client[0].get_table_details(this._sessionId[0], tableName, function (error, fields) { - if (fields) { - var rowDict = fields.row_desc.reduce(function (accum, value) { - accum[value.col_name] = value; - return accum; - }, {}); - callback(null, _this11.convertFromThriftTypes(rowDict)); - } else { - callback(new Error("Table (" + tableName + ") not found" + error)); - } + function convertFromThriftTypes(fields, _datumEnum) { + var fieldsArray = []; + for (var key in fields) { + if (fields.hasOwnProperty(key)) { + fieldsArray.push({ + name: key, + type: _datumEnum[fields[key].col_type.type], + is_array: fields[key].col_type.is_array, + is_dict: fields[key].col_type.encoding === TEncodingType.DICT }); } - }, { - key: "createTable", - value: function createTable(tableName, rowDescObj, tableType, callback) { - if (!this._sessionId) { - throw new Error("You are not connected to a server. Try running the connect method first."); - } - - var thriftRowDesc = helpers.mutateThriftRowDesc(rowDescObj, this.importerRowDesc); - - for (var c = 0; c < this._numConnections; c++) { - this._client[c].create_table(this._sessionId[c], tableName, thriftRowDesc, tableType, function (err) { - if (err) { - callback(err); - } else { - callback(); - } - }); - } - } - - /** - * Create a table and persist it to the backend. - * @param {String} tableName - desired name of the new table - * @param {Array} rowDescObj - fields of the new table - * @param {Number} tableType - the types of tables a user can import into the db - * @return {Promise.} it will either catch an error or return undefined on success - * - * @example Create a new table: - * - * con.createTable('mynewtable', [TColumnType, TColumnType, ...], 0).then(res => console.log(res)); - * // undefined - */ - - }, { - key: "importTable", - value: function importTable(tableName, fileName, copyParams, rowDescObj, isShapeFile, callback) { - if (!this._sessionId) { - throw new Error("You are not connected to a server. Try running the connect method first."); - } - - var thriftCopyParams = helpers.convertObjectToThriftCopyParams(copyParams); - var thriftRowDesc = helpers.mutateThriftRowDesc(rowDescObj, this.importerRowDesc); - - var thriftCallBack = function thriftCallBack(err, res) { - if (err) { - callback(err); - } else { - callback(null, res); - } - }; - - for (var c = 0; c < this._numConnections; c++) { - if (isShapeFile) { - this._client[c].import_geo_table(this._sessionId[c], tableName, fileName, thriftCopyParams, thriftRowDesc, thriftCallBack); - } else { - this._client[c].import_table(this._sessionId[c], tableName, fileName, thriftCopyParams, thriftCallBack); - } - } - } - }, { - key: "importTableAsyncWrapper", - value: function importTableAsyncWrapper(isShapeFile) { - var _this12 = this; - - return function (tableName, fileName, copyParams, headers) { - return new Promise(function (resolve, reject) { - _this12.importTable(tableName, fileName, copyParams, headers, isShapeFile, function (err, link) { - if (err) { - reject(err); - } else { - resolve(link); - } - }); - }); - }; - } - - /** - * Import a delimited table from a file. - * @param {String} tableName - desired name of the new table - * @param {String} fileName - * @param {TCopyParams} copyParams - see {@link TCopyParams} - * @param {TColumnType[]} headers -- a colleciton of metadata related to the table headers - */ - - - /** - * Import a geo table from a file. - * @param {String} tableName - desired name of the new table - * @param {String} fileName - * @param {TCopyParams} copyParams - see {@link TCopyParams} - * @param {TColumnType[]} headers -- a colleciton of metadata related to the table headers - */ - - }, { - key: "renderVega", - - - /** - * Use for backend rendering. This method will fetch a PNG image - * that is a render of the vega json object. - * - * @param {Number} widgetid the widget id of the calling widget - * @param {String} vega the vega json - * @param {Object} options the options for the render query - * @param {Number} options.compressionLevel the png compression level. - * range 1 (low compression, faster) to 10 (high compression, slower). - * Default 3. - * @param {Function} callback takes `(err, success)` as its signature. Returns con singleton on success. - * - * @returns {Image} Base 64 Image - */ - value: function renderVega(widgetid, vega, options, callback) /* istanbul ignore next */{ - var _this13 = this; - - var queryId = null; - var compressionLevel = COMPRESSION_LEVEL_DEFAULT; - if (options) { - queryId = options.hasOwnProperty("queryId") ? options.queryId : queryId; - compressionLevel = options.hasOwnProperty("compressionLevel") ? options.compressionLevel : compressionLevel; - } - - var lastQueryTime = queryId in this.queryTimes ? this.queryTimes[queryId] : this.DEFAULT_QUERY_TIME; - - var curNonce = (this._nonce++).toString(); - - var conId = 0; - this._lastRenderCon = conId; - - var processResultsOptions = { - isImage: true, - query: "render: " + vega, - queryId: queryId, - conId: conId, - estimatedQueryTime: lastQueryTime - }; - - try { - if (!callback) { - var renderResult = this._client[conId].render_vega(this._sessionId[conId], widgetid, vega, compressionLevel, curNonce); - return this.processResults(processResultsOptions, renderResult); - } - - this._client[conId].render_vega(this._sessionId[conId], widgetid, vega, compressionLevel, curNonce, function (error, result) { - if (error) { - callback(error); - } else { - _this13.processResults(processResultsOptions, result, callback); - } - }); - } catch (err) { - throw err; - } - - return curNonce; - } - - /** - * Used primarily for backend rendered maps, this method will fetch the row - * for a specific table that was last rendered at a pixel. - * - * @param {widgetId} Number - the widget id of the caller - * @param {TPixel} pixel - the pixel (lower left-hand corner is pixel (0,0)) - * @param {String} tableName - the table containing the geo data - * @param {Object} tableColNamesMap - object of tableName -> array of col names - * @param {Array} callbacks - * @param {Number} [pixelRadius=2] - the radius around the primary pixel to search - */ - - }, { - key: "getResultRowForPixel", - value: function getResultRowForPixel(widgetId, pixel, tableColNamesMap, callbacks) /* istanbul ignore next */{ - var pixelRadius = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 2; - - if (!(pixel instanceof TPixel)) { - pixel = new TPixel(pixel); - } - var columnFormat = true; // BOOL - var curNonce = (this._nonce++).toString(); - try { - if (!callbacks) { - return this.processPixelResults(undefined, // eslint-disable-line no-undefined - this._client[this._lastRenderCon].get_result_row_for_pixel(this._sessionId[this._lastRenderCon], widgetId, pixel, tableColNamesMap, columnFormat, pixelRadius, curNonce)); - } - this._client[this._lastRenderCon].get_result_row_for_pixel(this._sessionId[this._lastRenderCon], widgetId, pixel, tableColNamesMap, columnFormat, pixelRadius, curNonce, this.processPixelResults.bind(this, callbacks)); - } catch (err) { - throw err; - } - return curNonce; - } - - /** - * Formats the pixel results into the same pattern as textual results. - * - * @param {Array} callbacks a collection of callbacks - * @param {Object} error an error if one was thrown, otherwise null - * @param {Array|Object} results unformatted results of pixel rowId information - * - * @returns {Object} An object with the pixel results formatted for display - */ - - }, { - key: "processPixelResults", - value: function processPixelResults(callbacks, error, results) { - callbacks = Array.isArray(callbacks) ? callbacks : [callbacks]; - results = Array.isArray(results) ? results.pixel_rows : [results]; - var numPixels = results.length; - var processResultsOptions = { - isImage: false, - eliminateNullRows: false, - query: "pixel request", - queryId: -2 - }; - for (var p = 0; p < numPixels; p++) { - results[p].row_set = this.processResults(processResultsOptions, results[p]); - } - if (!callbacks) { - return results; - } - callbacks.pop()(error, results); - } - - /** - * Get or set the session ID used by the server to serve the correct data. - * This is typically set by {@link connect} and should not be set manually. - * @param {Number} sessionId - The session ID of the current connection - * @return {Number|MapdCon} - The session ID or the MapdCon itself - * - * @example Get the session id: - * - * con.sessionId(); - * // sessionID === 3145846410 - * - * @example Set the session id: - * var con = new MapdCon().connect().sessionId(3415846410); - * // NOTE: It is generally unsafe to set the session id manually. - */ - - }, { - key: "sessionId", - value: function sessionId(_sessionId) { - if (!arguments.length) { - return this._sessionId; - } - this._sessionId = _sessionId; - return this; - } - - /** - * Get or set the connection server hostname. - * This is is typically the first method called after instantiating a new MapdCon. - * @param {String} host - The hostname address - * @return {String|MapdCon} - The hostname or the MapdCon itself - * - * @example Set the hostname: - * var con = new MapdCon().host('localhost'); - * - * @example Get the hostname: - * var host = con.host(); - * // host === 'localhost' - */ - - }, { - key: "host", - value: function host(_host) { - if (!arguments.length) { - return this._host; - } - this._host = arrayify(_host); - return this; - } - - /** - * Get or set the connection port. - * @param {String} port - The port to connect on - * @return {String|MapdCon} - The port or the MapdCon itself - * - * @example Set the port: - * var con = new MapdCon().port('8080'); - * - * @example Get the port: - * var port = con.port(); - * // port === '8080' - */ - - }, { - key: "port", - value: function port(_port) { - if (!arguments.length) { - return this._port; - } - this._port = arrayify(_port); - return this; - } - - /** - * Get or set the username to authenticate with. - * @param {String} user - The username to authenticate with - * @return {String|MapdCon} - The username or the MapdCon itself - * - * @example Set the username: - * var con = new MapdCon().user('foo'); - * - * @example Get the username: - * var username = con.user(); - * // user === 'foo' - */ - - }, { - key: "user", - value: function user(_user) { - if (!arguments.length) { - return this._user; - } - this._user = arrayify(_user); - return this; - } - - /** - * Get or set the user's password to authenticate with. - * @param {String} password - The password to authenticate with - * @return {String|MapdCon} - The password or the MapdCon itself - * - * @example Set the password: - * var con = new MapdCon().password('bar'); - * - * @example Get the username: - * var password = con.password(); - * // password === 'bar' - */ - - }, { - key: "password", - value: function password(_password) { - if (!arguments.length) { - return this._password; - } - this._password = arrayify(_password); - return this; - } - - /** - * Get or set the name of the database to connect to. - * @param {String} dbName - The database to connect to - * @return {String|MapdCon} - The name of the database or the MapdCon itself - * - * @example Set the database name: - * var con = new MapdCon().dbName('myDatabase'); - * - * @example Get the database name: - * var dbName = con.dbName(); - * // dbName === 'myDatabase' - */ - - }, { - key: "dbName", - value: function dbName(_dbName) { - if (!arguments.length) { - return this._dbName; - } - this._dbName = arrayify(_dbName); - return this; - } - - /** - * Whether the raw queries strings will be logged to the console. - * Used primarily for debugging and defaults to false. - * @param {Boolean} logging - Set to true to enable logging - * @return {Boolean|MapdCon} - The current logging flag or MapdCon itself - * - * @example Set logging to true: - * var con = new MapdCon().logging(true); - * - * @example Get the logging flag: - * var isLogging = con.logging(); - * // isLogging === true - */ - - }, { - key: "logging", - value: function logging(_logging) { - if (typeof _logging === "undefined") { - return this._logging; - } else if (typeof _logging !== "boolean") { - return "logging can only be set with boolean values"; - } - this._logging = _logging; - var isEnabledTxt = _logging ? "enabled" : "disabled"; - return "SQL logging is now " + isEnabledTxt; - } - - /** - * The name of the platform. - * @param {String} platform - The platform, default is "mapd" - * @return {String|MapdCon} - The platform or the MapdCon itself - * - * @example Set the platform name: - * var con = new MapdCon().platform('myPlatform'); - * - * @example Get the platform name: - * var platform = con.platform(); - * // platform === 'myPlatform' - */ - - }, { - key: "platform", - value: function platform(_platform) { - if (!arguments.length) { - return this._platform; - } - this._platform = _platform; - return this; - } - - /** - * Get the number of connections that are currently open. - * @return {Number} - number of open connections - * - * @example Get the number of connections: - * - * var numConnections = con.numConnections(); - * // numConnections === 1 - */ - - }, { - key: "numConnections", - value: function numConnections() { - return this._numConnections; - } + } + return fieldsArray; + } - /** - * The protocol to use for requests. - * @param {String} protocol - http or https - * @return {String|MapdCon} - protocol or MapdCon itself - * - * @example Set the protocol: - * var con = new MapdCon().protocol('http'); - * - * @example Get the protocol: - * var protocol = con.protocol(); - * // protocol === 'http' - */ + function updateQueryTimes(queryTimes) { + return function (conId, queryId, estimatedQueryTime, execution_time_ms) { + queryTimes[queryId] = execution_time_ms; + }; + } - }, { - key: "protocol", - value: function protocol(_protocol) { - if (!arguments.length) { - return this._protocol; + function processPixelResults(callback, _logging, _datumEnum, error, results) { + // eslint-disable-line consistent-return + if (error) { + return callback(normalizeError(error)); + } + results = Array.isArray(results) ? results.pixel_rows : [results]; + var numPixels = results.length; + var processResultsOptions = { + isImage: false, + eliminateNullRows: false, + query: "pixel request", + queryId: -2 + }; + var numResultsProcessed = 0; + for (var p = 0; p < numPixels; p++) { + processResults(results[p], aggregatingCallback(p), _logging, _datumEnum, processResultsOptions); + } + function aggregatingCallback(index) { + return function (processResultsError, row_set) { + results[index].row_set = row_set; + if (processResultsError) { + numResultsProcessed = -Infinity; // avoid invoking callback again + return callback(processResultsError); + } else if (numResultsProcessed === numPixels - 1) { + return callback(null, results); + } else { + return numResultsProcessed++; } - this._protocol = arrayify(_protocol); - return this; - } + }; + } + } - /** - * Generates a list of endpoints from the connection params. - * @return {Array} - list of endpoints - * - * @example Get the endpoints: - * var con = new MapdCon().protocol('http').host('localhost').port('8000'); - * var endpoints = con.getEndpoints(); - * // endpoints === [ 'http://localhost:8000' ] - */ + function processResults(result, callback, _logging, _datumEnum) { + var options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {}; - }, { - key: "getEndpoints", - value: function getEndpoints() { - var _this14 = this; + var processor = (0, _processQueryResults2.default)(_logging, updateQueryTimes); + var processResultsObject = processor(options, _datumEnum, result, callback); + return processResultsObject; + } - return this._host.map(function (host, i) { - return _this14._protocol[i] + "://" + host + ":" + _this14._port[i]; - }); + function invertDatumTypes(datumEnum) { + var datumType = TDatumType; + for (var key in datumType) { + if (datumType.hasOwnProperty(key)) { + datumEnum[datumType[key]] = key; } - }]); - - return MapdCon; - }(); + } + } function resetThriftClientOnArgumentErrorForMethods(connector, client, methodNames) { methodNames.forEach(function (methodName) { @@ -26708,14 +26296,13 @@ module.exports = }); } - // Set a global mapdcon function when mapdcon is brought in via script tag. - if (( false ? "undefined" : _typeof(module)) === "object" && module.exports) { - if (!isNodeRuntime()) { - window.MapdCon = MapdCon; + function normalizeError(error) { + if (isNodeRuntime()) { + return new Error(error.name + " " + error.error_msg); + } else { + return new Error("TMapDException " + error.message); } } - module.exports = MapdCon; - exports.default = MapdCon; /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(55)(module))) /***/ }), @@ -26804,8 +26391,8 @@ module.exports = }; MapDClientV2.prototype.get_result_row_for_pixel = function () { - var getResultRowForPixelWithErrorHandling = (0, _wrapWithErrorHandling.wrapWithErrorHandling)(this, "get_result_row_for_pixel"); - return getResultRowForPixelWithErrorHandling.apply(undefined, arguments); + var getPixelWithErrorHandling = (0, _wrapWithErrorHandling.wrapWithErrorHandling)(this, "get_result_row_for_pixel"); + return getPixelWithErrorHandling.apply(undefined, arguments); }; MapDClientV2.prototype.delete_frontend_view = function () { diff --git a/examples/browser.html b/examples/browser.html index c9dfe4d6..0e5cc12c 100644 --- a/examples/browser.html +++ b/examples/browser.html @@ -38,7 +38,7 @@ // The total number of tweets from Columbia const query = "SELECT count(*) AS n FROM tweets_nov_feb WHERE country='CO'" const defaultQueryOptions = {} - var connector = new MapdCon() + var connector = new Connector() connector .protocol("https") @@ -50,9 +50,10 @@ .connect((connectError, session) => { // eslint-disable-line consistent-return if (connectError) { return console.error("Error connecting", connectError) } - session.getTablesAsync() - .then(data => console.log("All tables available at metis.mapd.com:", data.map(x => x.name))) - .catch(error => console.error("getTablesAsync error:", error)) + session.getTables((error, data) => { + if (error) { return console.error("getTablesAsync error:", error) } + return console.log("All tables available at metis.mapd.com:", data.map(x => x.name)) + }) session.query(query, defaultQueryOptions, (error, data) => { if (error) { return console.error("Query 1 error:", error) } diff --git a/examples/node.js b/examples/node.js index ab0f85ac..748a7382 100644 --- a/examples/node.js +++ b/examples/node.js @@ -17,9 +17,10 @@ connector .connect((connectError, session) => { // eslint-disable-line consistent-return if (connectError) { return console.error("Error connecting", connectError) } - session.getTablesAsync() - .then(data => console.log("All tables available at metis.mapd.com:", data.map(x => x.name))) - .catch(error => console.error("getTablesAsync error:", error)) + session.getTables((error, data) => { + if (error) { return console.error("getTablesAsync error:", error) } + return console.log("All tables available at metis.mapd.com:", data.map(x => x.name)) + }) session.query(query, defaultQueryOptions, (error, data) => { if (error) { return console.error("Query 1 error:", error) } diff --git a/src/mapd-client-v2.js b/src/mapd-client-v2.js index 7a788956..1bd0b2e4 100644 --- a/src/mapd-client-v2.js +++ b/src/mapd-client-v2.js @@ -37,8 +37,8 @@ MapDClientV2.prototype.render_vega = function (...args) { } MapDClientV2.prototype.get_result_row_for_pixel = function (...args) { - const getResultRowForPixelWithErrorHandling = wrapWithErrorHandling(this, "get_result_row_for_pixel") - return getResultRowForPixelWithErrorHandling(...args) + const getPixelWithErrorHandling = wrapWithErrorHandling(this, "get_result_row_for_pixel") + return getPixelWithErrorHandling(...args) } MapDClientV2.prototype.delete_frontend_view = function (...args) { diff --git a/src/mapd-con-es6.js b/src/mapd-con-es6.js index 8d542557..c54ef3b8 100644 --- a/src/mapd-con-es6.js +++ b/src/mapd-con-es6.js @@ -12,301 +12,221 @@ import * as helpers from "./helpers" import MapDClientV2 from "./mapd-client-v2" import processQueryResults from "./process-query-results" -const COMPRESSION_LEVEL_DEFAULT = 3 - -function arrayify (maybeArray) { return Array.isArray(maybeArray) ? maybeArray : [maybeArray] } - -function isNodeRuntime () { return typeof window === "undefined" } - -class MapdCon { - - constructor () { - this._host = null - this._user = null - this._password = null - this._port = null - this._dbName = null - this._client = null - this._sessionId = null - this._protocol = null - this._datumEnum = {} - this._logging = false - this._platform = "mapd" - this._nonce = 0 - this._balanceStrategy = "adaptive" - this._numConnections = 0 - this._lastRenderCon = 0 - this.queryTimes = { } - this.serverQueueTimes = null - this.serverPingTimes = null - this.pingCount = null - this.DEFAULT_QUERY_TIME = 50 - this.NUM_PINGS_PER_SERVER = 4 - this.importerRowDesc = null - - // invoke initialization methods - this.invertDatumTypes() - - this.processResults = (options = {}, result, callback) => { - const processor = processQueryResults(this._logging, this.updateQueryTimes) - const processResultsObject = processor(options, this._datumEnum, result, callback) - return processResultsObject - } - - // return this to allow chaining off of instantiation - return this +// Set a global Connector function when Connector is brought in via script tag. +if (typeof module === "object" && module.exports) { + if (!isNodeRuntime()) { + window.Connector = Connector } +} +module.exports = Connector +export default Connector +const COMPRESSION_LEVEL_DEFAULT = 3 +const DEFAULT_QUERY_TIME = 50 + +function Connector () { + // initialize variables + const _datumEnum = {} + const _queryTimes = {} + let _client = null + let _dbName = null + let _host = null + let _logging = false + let _nonce = 0 + let _password = null + let _port = null + let _protocol = null + let _sessionId = null + let _user = null + + // invoke initialization methods + publicizeMethods(this, [ + connect, + createFrontendView, + createLink, + createTable, + dbName, + deleteFrontendView, + detectColumnTypes, + disconnect, + getFields, + getFrontendView, + getFrontendViews, + getLinkView, + getPixel, + getServerStatus, + getTables, + host, + importShapeTable, + importTable, + logging, + password, + port, + protocol, + query, + renderVega, + sessionId, + user, + validateQuery + ]) + invertDatumTypes(_datumEnum) + + // return this to allow chaining off of instantiation + return this + + // public methods /** * Create a connection to the server, generating a client and session id. - * @param {Function} callback A callback that takes `(err, success)` as its signature. Returns con singleton on success. - * @return {MapdCon} Object + * @param {Function} callback (error, session) => {…} + * @returns {undefined} * * @example Connect to a MapD server: - * var con = new MapdCon() + * var con = new Connector() * .host('localhost') * .port('8080') * .dbName('myDatabase') * .user('foo') * .password('bar') - * .connect((err, con) => console.log(con.sessionId())); + * .connect((error, con) => console.log(con.sessionId())); * * // ["om9E9Ujgbhl6wIzWgLENncjWsaXRDYLy"] */ - connect (callback) { - if (this._sessionId) { - this.disconnect() + function connect (callback) { // eslint-disable-line consistent-return + if (_sessionId) { + disconnect() } - // TODO: should be its own function - const allAreArrays = Array.isArray(this._host) && Array.isArray(this._port) && Array.isArray(this._user) && Array.isArray(this._password) && Array.isArray(this._dbName) - if (!allAreArrays) { - return callback("All connection parameters must be arrays.") + if ([_host, _port, _user, _password, _dbName].some(Array.isArray)) { + console.warn("Connection parameters as arrays is deprecated; use single values.") // eslint-disable-line no-console + _host = Array.isArray(_host) ? _host[0] : _host + _port = Array.isArray(_port) ? _port[0] : _port + _user = Array.isArray(_user) ? _user[0] : _user + _password = Array.isArray(_password) ? _password[0] : _password + _dbName = Array.isArray(_dbName) ? _dbName[0] : _dbName } - this._client = [] - this._sessionId = [] - - if (!this._user[0]) { + if (!_user) { return callback("Please enter a username.") - } else if (!this._password[0]) { + } else if (!_password) { return callback("Please enter a password.") - } else if (!this._dbName[0]) { + } else if (!_dbName) { return callback("Please enter a database.") - } else if (!this._host[0]) { + } else if (!_host) { return callback("Please enter a host name.") - } else if (!this._port[0]) { + } else if (!_port) { return callback("Please enter a port.") } - // now check to see if length of all arrays are the same and > 0 - const hostLength = this._host.length - if (hostLength < 1) { - return callback("Must have at least one server to connect to.") - } - if (hostLength !== this._port.length || hostLength !== this._user.length || hostLength !== this._password.length || hostLength !== this._dbName.length) { - return callback("Array connection parameters must be of equal length.") - } - - if (!this._protocol) { - this._protocol = this._host.map(() => window.location.protocol.replace(":", "")) - } - - const transportUrls = this.getEndpoints() - for (let h = 0; h < hostLength; h++) { - let client = null - - if (isNodeRuntime()) { - const {protocol, hostname, port} = parseUrl(transportUrls[h]) - const connection = thriftWrapper.createHttpConnection( - hostname, - port, - { - transport: thriftWrapper.TBufferedTransport, - protocol: thriftWrapper.TJSONProtocol, - path: "/", - headers: {Connection: "close"}, - https: protocol === "https:" - } - ) - connection.on("error", console.error) // eslint-disable-line no-console - client = thriftWrapper.createClient(MapDThrift, connection) - resetThriftClientOnArgumentErrorForMethods(this, client, [ - "connect", - "createFrontendViewAsync", - "createLinkAsync", - "createTableAsync", - "dbName", - "deleteFrontendViewAsync", - "detectColumnTypesAsync", - "disconnect", - "getFields", - "getFrontendViewAsync", - "getFrontendViewsAsync", - "getLinkViewAsync", - "getResultRowForPixel", - "getServerStatusAsync", - "getTablesAsync", - "host", - "importTableAsync", - "importTableGeoAsync", - "logging", - "password", - "port", - "protocol", - "query", - "renderVega", - "sessionId", - "user", - "validateQuery" - ]) - } else { - const thriftTransport = new Thrift.Transport(transportUrls[h]) - const thriftProtocol = new Thrift.Protocol(thriftTransport) - client = new MapDClientV2(thriftProtocol) - } - - client.connect(this._user[h], this._password[h], this._dbName[h], (error, sessionId) => { - if (error) { - callback(error) - return + _client = null + _sessionId = null + _protocol = _protocol || window.location.protocol.replace(":", "") + + const transportUrl = `${_protocol}://${_host}:${_port}` + let client = null + + if (isNodeRuntime()) { + const {protocol: parsedProtocol, hostname: parsedHost, port: parsedPort} = parseUrl(transportUrl) + const connection = thriftWrapper.createHttpConnection( + parsedHost, + parsedPort, + { + transport: thriftWrapper.TBufferedTransport, + protocol: thriftWrapper.TJSONProtocol, + path: "/", + headers: {Connection: "close"}, + https: parsedProtocol === "https:" } - this._client.push(client) - this._sessionId.push(sessionId) - this._numConnections = this._client.length - callback(null, this) - }) - } - - return this - } - - convertFromThriftTypes (fields) { - const fieldsArray = [] - // silly to change this from map to array - // - then later it turns back to map - for (const key in fields) { - if (fields.hasOwnProperty(key)) { - fieldsArray.push({ - name: key, - type: this._datumEnum[fields[key].col_type.type], - is_array: fields[key].col_type.is_array, - is_dict: fields[key].col_type.encoding === TEncodingType.DICT // eslint-disable-line no-undef - }) - } - } - return fieldsArray + ) + connection.on("error", console.error) // eslint-disable-line no-console + client = thriftWrapper.createClient(MapDThrift, connection) + resetThriftClientOnArgumentErrorForMethods(this, client, Object.keys(this)) + } else { + const thriftTransport = new Thrift.Transport(transportUrl) + const thriftProtocol = new Thrift.Protocol(thriftTransport) + client = new MapDClientV2(thriftProtocol) + } + client.connect(_user, _password, _dbName, (error, newSessionId) => { // eslint-disable-line no-loop-func + if (error) { return callback(normalizeError(error)) } + _client = client + _sessionId = newSessionId + return callback(null, this) + }) } - /** * Disconnect from the server then clears the client and session values. - * @return {MapdCon} Object - * @param {Function} callback A callback that takes `(err, success)` as its signature. Returns con singleton on success. + * @param {Function} callback (error) => {…} + * @returns {undefined} * * @example Disconnect from the server: * * con.sessionId() // ["om9E9Ujgbhl6wIzWgLENncjWsaXRDYLy"] - * con.disconnect((err, con) => console.log(err, con)) - * con.sessionId() === null; - */ - disconnect (callback) { - if (this._sessionId !== null) { - for (let c = 0; c < this._client.length; c++) { - this._client[c].disconnect(this._sessionId[c], error => { - // Success will return NULL - - if (error) { - return callback(error, this) - } - this._sessionId = null - this._client = null - this._numConnections = 0 - this.serverPingTimes = null - return callback(null, this) - }) - } + * con.disconnect((err) => { + * console.error(err); + * con.sessionId() === null; + * }) + */ + function disconnect (callback = noop) { + if (_sessionId !== null) { + _client.disconnect(_sessionId, error => { // eslint-disable-line no-loop-func + if (error) { return callback(error, this) } + _sessionId = null + _client = null + return callback(null, this) + }) } - return this - } - - updateQueryTimes = (conId, queryId, estimatedQueryTime, execution_time_ms) => { - this.queryTimes[queryId] = execution_time_ms } - - getFrontendViews = (callback) => { - if (this._sessionId) { - this._client[0].get_frontend_views(this._sessionId[0], callback) + /** + * Get a dashboard object containing a value for the view_state property. + * This object contains a value for the view_state property, + * but not for the view_name property. + * @param {String} viewName the name of the dashboard + * @param {Function} callback (error, data) => {…} + * @returns {undefined} + * + * @example Get a specific dashboard from the server: + * + * con.getFrontendView('dashboard_name').then((result) => console.log(result)) + * // {TFrontendView} + */ + function getFrontendView (viewName, callback) { + if (_sessionId && viewName) { + _client.get_frontend_view(_sessionId, viewName, callback) + return } else { callback(new Error("No Session ID")) + return } } - /** * Get the recent dashboards as a list of TFrontendView objects. * These objects contain a value for the view_name property, * but not for the view_state property. - * @return {Promise} An array which has all saved dashboards. + * @param {Function} callback (error, data) => {…} + * @returns {undefined} * * @example Get the list of dashboards from the server: * - * con.getFrontendViewsAsync().then((results) => console.log(results)) + * con.getFrontendViews().then((results) => console.log(results)) * // [TFrontendView, TFrontendView] */ - getFrontendViewsAsync = () => ( - new Promise((resolve, reject) => { - this.getFrontendViews((error, views) => { - if (error) { - reject(error) - } else { - resolve(views) - } - }) - }) - ) - - getFrontendView = (viewName, callback) => { - if (this._sessionId && viewName) { - this._client[0].get_frontend_view(this._sessionId[0], viewName, callback) + function getFrontendViews (callback) { + if (_sessionId) { + _client.get_frontend_views(_sessionId, callback) } else { callback(new Error("No Session ID")) + return } } - - /** - * Get a dashboard object containing a value for the view_state property. - * This object contains a value for the view_state property, - * but not for the view_name property. - * @param {String} viewName the name of the dashboard - * @return {Promise.} An object that contains all data and metadata related to the dashboard - * - * @example Get a specific dashboard from the server: - * - * con.getFrontendViewAsync('dashboard_name').then((result) => console.log(result)) - * // {TFrontendView} - */ - getFrontendViewAsync = (viewName) => new Promise((resolve, reject) => { - this.getFrontendView(viewName, (err, view) => { - if (err) { - reject(err) - } else { - resolve(view) - } - }) - }) - - getServerStatus = (callback) => { - this._client[0].get_server_status(this._sessionId[0], callback) - } - /** * Get the status of the server as a TServerStatus object. * This includes whether the server is read-only, * has backend rendering enabled, and the version number. - * @return {Promise.} + * @param {Function} callback (error, data) => {…} + * @returns {undefined} * * @example Get the server status: * - * con.getServerStatusAsync().then((result) => console.log(result)) + * con.getServerStatus().then((result) => console.log(result)) * // { * // "read_only": false, * // "version": "3.0.0dev-20170503-40e2de3", @@ -314,126 +234,82 @@ class MapdCon { * // "start_time": 1493840131 * // } */ - - getServerStatusAsync = () => ( - new Promise((resolve, reject) => { - this.getServerStatus((err, result) => { - if (err) { - reject(err) - } else { - resolve(result) - } - }) - }) - ) - + function getServerStatus (callback) { + _client.get_server_status(_sessionId, callback) + } /** * Add a new dashboard to the server. * @param {String} viewName - the name of the new dashboard * @param {String} viewState - the base64-encoded state string of the new dashboard * @param {String} imageHash - the numeric hash of the dashboard thumbnail * @param {String} metaData - Stringified metaData related to the view - * @return {Promise} Returns empty if success + * @param {Function} callback (error) => {…} success returns null + * @returns {undefined} * * @example Add a new dashboard to the server: * - * con.createFrontendViewAsync('newSave', 'viewstateBase64', null, 'metaData').then(res => console.log(res)) + * con.createFrontendView('newSave', 'viewstateBase64', null, 'metaData').then(res => console.log(res)) */ - createFrontendViewAsync (viewName, viewState, imageHash, metaData) { - if (!this._sessionId) { - return new Promise((resolve, reject) => { - reject(new Error("You are not connected to a server. Try running the connect method first.")) - }) - } - - return Promise.all(this._client.map((client, i) => new Promise((resolve, reject) => { - client.create_frontend_view(this._sessionId[i], viewName, viewState, imageHash, metaData, (error, data) => { - if (error) { - reject(error) - } else { - resolve(data) - } - }) - }))) + function createFrontendView (viewName, viewState, imageHash, metaData, callback) { + if (!_sessionId) { return callback(new Error("You are not connected to a server. Try running the connect method first.")) } + return _client.create_frontend_view(_sessionId, viewName, viewState, imageHash, metaData, callback) } - - deleteFrontendView (viewName, callback) { - if (!this._sessionId) { - throw new Error("You are not connected to a server. Try running the connect method first.") - } - try { - this._client.forEach((client, i) => { - // do we want to try each one individually so if we fail we keep going? - client.delete_frontend_view(this._sessionId[i], viewName, callback) - }) - } catch (err) { - console.log("ERROR: Could not delete the frontend view. Check your session id.", err) - } - } - /** * Delete a dashboard object containing a value for the view_state property. * @param {String} viewName - the name of the dashboard - * @return {Promise.} Name of dashboard successfully deleted + * @param {Function} callback (error, data) => {…} + * @returns {undefined} * * @example Delete a specific dashboard from the server: * - * con.deleteFrontendViewAsync('dashboard_name').then(res => console.log(res)) + * con.deleteFrontendView('dashboard_name').then(res => console.log(res)) */ - deleteFrontendViewAsync = (viewName) => new Promise((resolve, reject) => { - this.deleteFrontendView(viewName, (err) => { - if (err) { - reject(err) - } else { - resolve(viewName) - } - }) - }) - + function deleteFrontendView (viewName, callback) { + if (!_sessionId) { + return callback(new Error("You are not connected to a server. Try running the connect method first.")) + } + try { // eslint-disable-line no-restricted-syntax + return _client.delete_frontend_view(_sessionId, viewName, callback) + } catch (err) { + return callback(new Error("Could not delete the frontend view; check your session id.", err)) + } + } /** * Create a short hash to make it easy to share a link to a specific dashboard. * @param {String} viewState - the base64-encoded state string of the new dashboard * @param {String} metaData - Stringified metaData related to the link - * @return {Promise.} link - A short hash of the dashboard used for URLs + * @param {Function} callback (error, id) => {…} + * @returns {undefined} * * @example Create a link to the current state of a dashboard: * - * con.createLinkAsync("eyJuYW1lIjoibXlkYXNoYm9hcmQifQ==", 'metaData').then(res => console.log(res)); + * con.createLink("eyJuYW1lIjoibXlkYXNoYm9hcmQifQ==", 'metaData').then(res => console.log(res)); * // ["28127951"] */ - createLinkAsync (viewState, metaData) { - return Promise.all(this._client.map((client, i) => new Promise((resolve, reject) => { - client.create_link(this._sessionId[i], viewState, metaData, (error, data) => { - if (error) { - reject(error) - } else { - const result = data.split(",").reduce((links, link) => { - if (links.indexOf(link) === -1) { links.push(link) } - return links - }, []) - if (!result || result.length !== 1) { - reject(new Error("Different links were created on connection")) - } else { - resolve(result.join()) - } - } - }) - }))) - } - - getLinkView = (link, callback) => { - this._client[0].get_link_view(this._sessionId[0], link, callback) + function createLink (viewState, metaData, callback) { + return _client.create_link(_sessionId, viewState, metaData, (error, data) => { + if (error) { return callback(normalizeError(error)) } + const result = data.split(",").reduce((links, link) => { // eslint-disable-line max-nested-callbacks + if (!links.includes(link)) { links.push(link) } + return links + }, []) + if (!result || result.length !== 1) { + return callback(new Error("Different links were created on connection")) + } else { + return callback(null, result.join()) + } + }) } - /** * Get a fully-formed dashboard object from a generated share link. * This object contains the given link for the view_name property, * @param {String} link - the short hash of the dashboard, see {@link createLink} - * @return {Promise.} Object of the dashboard and metadata + * @param {Function} callback (error, data) => {…} + * @returns {undefined} * * @example Get a dashboard from a link: * - * con.getLinkViewAsync('28127951').then(res => console.log(res)) + * con.getLinkView('28127951').then(res => console.log(res)) * // { * // "view_name": "28127951", * // "view_state": "eyJuYW1lIjoibXlkYXNoYm9hcmQifQ==", @@ -442,66 +318,52 @@ class MapdCon { * // "view_metadata": "metaData" * // } */ - getLinkViewAsync = (link) => new Promise((resolve, reject) => { - this.getLinkView(link, (err, theLink) => { - if (err) { - reject(err) + function getLinkView (link, callback) { + _client.get_link_view(_sessionId, link, (error, data) => { + if (error) { + return callback(normalizeError(error)) } else { - resolve(theLink) + return callback(null, data) } }) - }) - - detectColumnTypes (fileName, copyParams, callback) { - const thriftCopyParams = helpers.convertObjectToThriftCopyParams(copyParams) - this._client[0].detect_column_types(this._sessionId[0], fileName, thriftCopyParams, callback) } - /** * Asynchronously get the data from an importable file, * such as a .csv or plaintext file with a header. * @param {String} fileName - the name of the importable file * @param {TCopyParams} copyParams - see {@link TCopyParams} - * @returns {Promise.} An object which has copy_params and row_set + * @param {Function} callback (error, data) => {…} + * @returns {undefined} * * @example Get data from table_data.csv: * * var copyParams = new TCopyParams(); - * con.detectColumnTypesAsync('table_data.csv', copyParams).then(res => console.log(res)) + * con.detectColumnTypes('table_data.csv', copyParams).then(res => console.log(res)) * // TDetectResult {row_set: TRowSet, copy_params: TCopyParams} * */ - detectColumnTypesAsync (fileName, copyParams) { - return new Promise((resolve, reject) => { - this.detectColumnTypes.bind(this, fileName, copyParams)((err, res) => { - if (err) { - reject(err) - } else { - this.importerRowDesc = res.row_set.row_desc - resolve(res) - } - }) - }) + function detectColumnTypes (fileName, copyParams, callback) { + const thriftCopyParams = helpers.convertObjectToThriftCopyParams(copyParams) + _client.detect_column_types(_sessionId, fileName, thriftCopyParams, callback) } - /** * Submit a query to the database and process the results. - * @param {String} query The query to perform + * @param {String} sql The query to perform * @param {Object} options the options for the query - * @param {Function} callback that takes `(err, result) => result` - * @returns {Object} The result of the query + * @param {Function} callback (error, data) => {…} + * @returns {undefined} * * @example create a query * * var query = "SELECT count(*) AS n FROM tweets_nov_feb WHERE country='CO'"; * var options = {}; * - * con.query(query, options, function(err, result) { - * console.log(result) + * con.query(query, options, function(error, data) { + * console.log(data) * }); * */ - query (query, options, callback) { + function query (sql, options, callback) { let columnarResults = true let eliminateNullRows = false let queryId = null @@ -515,64 +377,32 @@ class MapdCon { limit = options.hasOwnProperty("limit") ? options.limit : limit } - const lastQueryTime = queryId in this.queryTimes ? this.queryTimes[queryId] : this.DEFAULT_QUERY_TIME + const lastQueryTime = queryId in _queryTimes ? _queryTimes[queryId] : DEFAULT_QUERY_TIME - const curNonce = (this._nonce++).toString() - - const conId = 0 + const curNonce = (_nonce++).toString() const processResultsOptions = { returnTiming, eliminateNullRows, - query, + sql, queryId, - conId, + conId: 0, estimatedQueryTime: lastQueryTime } - try { - if (callback) { - this._client[conId].sql_execute(this._sessionId[conId], query, columnarResults, curNonce, limit, (error, result) => { - if (error) { - callback(error) - } else { - this.processResults(processResultsOptions, result, callback) - } - }) - return curNonce - } else if (!callback) { - const SQLExecuteResult = this._client[conId].sql_execute( - this._sessionId[conId], - query, - columnarResults, - curNonce, - limit - ) - return this.processResults(processResultsOptions, SQLExecuteResult) - } - } catch (err) { - if (err.name === "NetworkError") { - this.removeConnection(conId) - if (this._numConnections === 0) { - err.msg = "No remaining database connections" - throw err - } - this.query(query, options, callback) - } else if (callback) { - callback(err) + _client.sql_execute(_sessionId, sql, columnarResults, curNonce, limit, (error, result) => { + if (error) { + return callback(normalizeError(error)) } else { - throw err + return processResults(result, callback, _logging, _datumEnum, processResultsOptions) } - } + }) } - - /** @deprecated will default to query */ - queryAsync = this.query - /** * Submit a query to validate whether the backend can create a result set based on the SQL statement. - * @param {String} query The query to perform - * @returns {Promise.} The result of whether the query is valid + * @param {String} sql The query to perform + * @param {Function} callback (error, data) => {…} + * @returns {undefined} * * @example create a query * @@ -588,50 +418,23 @@ class MapdCon { * // }] * */ - validateQuery (query) { - return new Promise((resolve, reject) => { - this._client[0].sql_validate(this._sessionId[0], query, (error, res) => { - if (error) { - reject(error) - } else { - resolve(this.convertFromThriftTypes(res)) - } - }) - }) - } - - removeConnection (conId) { - if (conId < 0 || conId >= this.numConnections) { - const err = { - msg: "Remove connection id invalid" - } - throw err - } - this._client.splice(conId, 1) - this._sessionId.splice(conId, 1) - this._numConnections-- - } - - getTables (callback) { - this._client[0].get_tables(this._sessionId[0], (error, tables) => { + function validateQuery (sql, callback) { + _client.sql_validate(_sessionId, sql, (error, data) => { if (error) { - callback(error) + return callback(normalizeError(error)) } else { - callback(null, tables.map((table) => ({ - name: table, - label: "obs" - }))) + return callback(null, convertFromThriftTypes(data, _datumEnum)) } }) } - /** * Get the names of the databases that exist on the current session's connectdion. - * @return {Promise.} list of table objects containing the label and table names. + * @param {Function} callback (error, data) => {…} + * @returns {undefined} * * @example Get the list of tables from a connection: * - * con.getTablesAsync().then(res => console.log(res)) + * con.getTables().then(res => console.log(res)) * * // [{ * // label: 'obs', // deprecated property @@ -639,42 +442,24 @@ class MapdCon { * // }, * // ...] */ - getTablesAsync () { - return new Promise((resolve, reject) => { - this.getTables.bind(this)((error, tables) => { - if (error) { - reject(error) - } else { - resolve(tables) - } - }) - }) - } - - /** - * Create an array-like object from {@link TDatumType} by - * flipping the string key and numerical value around. - * - * @returns {Undefined} This function does not return anything - */ - invertDatumTypes () { - const datumType = TDatumType // eslint-disable-line no-undef - for (const key in datumType) { - if (datumType.hasOwnProperty(key)) { - this._datumEnum[datumType[key]] = key + function getTables (callback) { + _client.get_tables(_sessionId, (error, tables) => { + if (error) { + return callback(normalizeError(error)) + } else { + return callback(null, tables.map((table) => ({name: table, label: "obs"}))) } - } + }) } - /** * Get a list of field objects for a given table. * @param {String} tableName - name of table containing field names - * @param {Function} callback - (err, results) - * @return {Array} fields - the formmatted list of field objects + * @param {Function} callback - (error, fields) => {…} + * @returns {undefined} * * @example Get the list of fields from a specific table: * - * con.getFields('flights', (err, res) => console.log(res)) + * con.getFields('flights', (error, res) => console.log(res)) * // [{ * name: 'fieldName', * type: 'BIGINT', @@ -682,136 +467,94 @@ class MapdCon { * is_dict: false * }, ...] */ - getFields (tableName, callback) { - this._client[0].get_table_details(this._sessionId[0], tableName, (error, fields) => { + function getFields (tableName, callback) { + _client.get_table_details(_sessionId, tableName, (error, fields) => { if (fields) { const rowDict = fields.row_desc.reduce((accum, value) => { accum[value.col_name] = value return accum }, {}) - callback(null, this.convertFromThriftTypes(rowDict)) + return callback(null, convertFromThriftTypes(rowDict, _datumEnum)) } else { - callback(new Error("Table (" + tableName + ") not found" + error)) + return callback(normalizeError(error)) } }) } - - - createTable (tableName, rowDescObj, tableType, callback) { - if (!this._sessionId) { - throw new Error("You are not connected to a server. Try running the connect method first.") - } - - const thriftRowDesc = helpers.mutateThriftRowDesc(rowDescObj, this.importerRowDesc) - - for (let c = 0; c < this._numConnections; c++) { - this._client[c].create_table( - this._sessionId[c], - tableName, - thriftRowDesc, - tableType, - (err) => { - if (err) { - callback(err) - } else { - callback() - } - } - ) - } - - } - /** * Create a table and persist it to the backend. * @param {String} tableName - desired name of the new table * @param {Array} rowDescObj - fields of the new table * @param {Number} tableType - the types of tables a user can import into the db - * @return {Promise.} it will either catch an error or return undefined on success + * @param {Function} callback (error, data) => {…} + * @returns {undefined} * * @example Create a new table: * * con.createTable('mynewtable', [TColumnType, TColumnType, ...], 0).then(res => console.log(res)); * // undefined */ - createTableAsync = (tableName, rowDescObj, tableType) => new Promise((resolve, reject) => { - this.createTable(tableName, rowDescObj, tableType, (err) => { - if (err) { - reject(err) - } else { - resolve() - } - }) - }) - - importTable (tableName, fileName, copyParams, rowDescObj, isShapeFile, callback) { - if (!this._sessionId) { - throw new Error("You are not connected to a server. Try running the connect method first.") - } - - const thriftCopyParams = helpers.convertObjectToThriftCopyParams(copyParams) - const thriftRowDesc = helpers.mutateThriftRowDesc(rowDescObj, this.importerRowDesc) - - const thriftCallBack = (err, res) => { - if (err) { - callback(err) - } else { - callback(null, res) - } + function createTable (tableName, rowDescObj, tableType, callback) { + if (!_sessionId) { + return callback(new Error("You are not connected to a server. Try running the connect method first.")) } - - for (let c = 0; c < this._numConnections; c++) { - if (isShapeFile) { - this._client[c].import_geo_table( - this._sessionId[c], - tableName, - fileName, - thriftCopyParams, - thriftRowDesc, - thriftCallBack - ) - } else { - this._client[c].import_table( - this._sessionId[c], - tableName, - fileName, - thriftCopyParams, - thriftCallBack - ) - } - } - } - - importTableAsyncWrapper (isShapeFile) { - return (tableName, fileName, copyParams, headers) => new Promise((resolve, reject) => { - this.importTable(tableName, fileName, copyParams, headers, isShapeFile, (err, link) => { - if (err) { - reject(err) - } else { - resolve(link) - } - }) - }) + const thriftRowDesc = helpers.mutateThriftRowDesc(rowDescObj, null) + return _client.create_table( + _sessionId, + tableName, + thriftRowDesc, + tableType, + callback + ) } - /** * Import a delimited table from a file. * @param {String} tableName - desired name of the new table - * @param {String} fileName + * @param {String} fileName - name of imported file * @param {TCopyParams} copyParams - see {@link TCopyParams} - * @param {TColumnType[]} headers -- a colleciton of metadata related to the table headers + * @param {TColumnType[]} rowDescObj -- a colleciton of metadata related to the table headers + * @param {Function} callback (error, data) => {…} + * @param {Boolean} isShapeFile false by default, enabled to import shape data. + * @returns {undefined} */ - importTableAsync = this.importTableAsyncWrapper(false) + function importTable (tableName, fileName, copyParams, rowDescObj, callback, isShapeFile = false) { + if (!_sessionId) { + return callback(new Error("You are not connected to a server. Try running the connect method first.")) + } + const thriftCopyParams = helpers.convertObjectToThriftCopyParams(copyParams) + const thriftRowDesc = helpers.mutateThriftRowDesc(rowDescObj, null) + + if (isShapeFile) { + return _client.import_geo_table( + _sessionId, + tableName, + fileName, + thriftCopyParams, + thriftRowDesc, + callback + ) + } else { + return _client.import_table( + _sessionId, + tableName, + fileName, + thriftCopyParams, + callback + ) + } + } /** * Import a geo table from a file. * @param {String} tableName - desired name of the new table - * @param {String} fileName + * @param {String} fileName - name of imported file * @param {TCopyParams} copyParams - see {@link TCopyParams} - * @param {TColumnType[]} headers -- a colleciton of metadata related to the table headers + * @param {TColumnType[]} rowDescObj -- a colleciton of metadata related to the table headers + * @param {Function} callback (error, data) => {…} + * @returns {undefined} */ - importTableGeoAsync = this.importTableAsyncWrapper(true) - + function importShapeTable (tableName, fileName, copyParams, rowDescObj, callback) { + return importTable(tableName, fileName, copyParams, rowDescObj, callback, true) + } /** * Use for backend rendering. This method will fetch a PNG image * that is a render of the vega json object. @@ -822,11 +565,10 @@ class MapdCon { * @param {Number} options.compressionLevel the png compression level. * range 1 (low compression, faster) to 10 (high compression, slower). * Default 3. - * @param {Function} callback takes `(err, success)` as its signature. Returns con singleton on success. - * - * @returns {Image} Base 64 Image + * @param {Function} callback (error, Base64Image) => {…} + * @returns {undefined} */ - renderVega (widgetid, vega, options, callback) /* istanbul ignore next */ { + function renderVega (widgetid, vega, options, callback) { let queryId = null let compressionLevel = COMPRESSION_LEVEL_DEFAULT if (options) { @@ -834,127 +576,57 @@ class MapdCon { compressionLevel = options.hasOwnProperty("compressionLevel") ? options.compressionLevel : compressionLevel } - const lastQueryTime = queryId in this.queryTimes ? this.queryTimes[queryId] : this.DEFAULT_QUERY_TIME - - const curNonce = (this._nonce++).toString() + const lastQueryTime = queryId in _queryTimes ? _queryTimes[queryId] : DEFAULT_QUERY_TIME - const conId = 0 - this._lastRenderCon = conId + const curNonce = (_nonce++).toString() const processResultsOptions = { isImage: true, query: "render: " + vega, queryId, - conId, + conId: 0, estimatedQueryTime: lastQueryTime } - try { - if (!callback) { - const renderResult = this._client[conId].render_vega( - this._sessionId[conId], - widgetid, - vega, - compressionLevel, - curNonce - ) - return this.processResults(processResultsOptions, renderResult) - } - - this._client[conId].render_vega(this._sessionId[conId], widgetid, vega, compressionLevel, curNonce, (error, result) => { - if (error) { - callback(error) - } else { - this.processResults(processResultsOptions, result, callback) - } - }) - } catch (err) { - throw err - } - - return curNonce + _client.render_vega(_sessionId, widgetid, vega, compressionLevel, curNonce, (error, result) => { + if (error) { return callback(normalizeError(error)) } + return processResults(result, callback, _logging, _datumEnum, processResultsOptions) + }) } - /** * Used primarily for backend rendered maps, this method will fetch the row * for a specific table that was last rendered at a pixel. - * - * @param {widgetId} Number - the widget id of the caller + * @param {Number} widgetId - the widget id of the caller * @param {TPixel} pixel - the pixel (lower left-hand corner is pixel (0,0)) - * @param {String} tableName - the table containing the geo data * @param {Object} tableColNamesMap - object of tableName -> array of col names - * @param {Array} callbacks + * @param {Function} callback (error, results) => {…} * @param {Number} [pixelRadius=2] - the radius around the primary pixel to search + * @returns {undefined} */ - - getResultRowForPixel (widgetId, pixel, tableColNamesMap, callbacks, pixelRadius = 2) /* istanbul ignore next */ { - if (!(pixel instanceof TPixel)) { pixel = new TPixel(pixel) } - const columnFormat = true // BOOL - const curNonce = (this._nonce++).toString() - try { - if (!callbacks) { - return this.processPixelResults( - undefined, // eslint-disable-line no-undefined - this._client[this._lastRenderCon].get_result_row_for_pixel( - this._sessionId[this._lastRenderCon], - widgetId, - pixel, - tableColNamesMap, - columnFormat, - pixelRadius, - curNonce - )) - } - this._client[this._lastRenderCon].get_result_row_for_pixel( - this._sessionId[this._lastRenderCon], - widgetId, - pixel, - tableColNamesMap, - columnFormat, - pixelRadius, - curNonce, - this.processPixelResults.bind(this, callbacks) - ) - } catch (err) { - throw err - } - return curNonce - } - - - /** - * Formats the pixel results into the same pattern as textual results. - * - * @param {Array} callbacks a collection of callbacks - * @param {Object} error an error if one was thrown, otherwise null - * @param {Array|Object} results unformatted results of pixel rowId information - * - * @returns {Object} An object with the pixel results formatted for display - */ - processPixelResults (callbacks, error, results) { - callbacks = Array.isArray(callbacks) ? callbacks : [callbacks] - results = Array.isArray(results) ? results.pixel_rows : [results] - const numPixels = results.length - const processResultsOptions = { - isImage: false, - eliminateNullRows: false, - query: "pixel request", - queryId: -2 - } - for (let p = 0; p < numPixels; p++) { - results[p].row_set = this.processResults(processResultsOptions, results[p]) - } - if (!callbacks) { - return results + function getPixel (widgetId, pixel, tableColNamesMap, callback, pixelRadius = 2) { + if (Array.isArray(callback)) { + console.warn("getPixel callbacks array deprecated; pass single callback instead.") // eslint-disable-line no-console + callback = callback[0] } - callbacks.pop()(error, results) + if (!(pixel instanceof TPixel)) { pixel = new TPixel(pixel) } + const columnFormat = true + const curNonce = String(_nonce++) + _client.get_result_row_for_pixel( + _sessionId, + widgetId, + pixel, + tableColNamesMap, + columnFormat, + pixelRadius, + curNonce, + processPixelResults.bind(this, callback, _logging, _datumEnum) + ) } - /** * Get or set the session ID used by the server to serve the correct data. * This is typically set by {@link connect} and should not be set manually. - * @param {Number} sessionId - The session ID of the current connection - * @return {Number|MapdCon} - The session ID or the MapdCon itself + * @param {Number} newSessionId - The session ID of the current connection + * @returns {Number|Connector} - The session ID or the Connector itself * * @example Get the session id: * @@ -962,206 +634,226 @@ class MapdCon { * // sessionID === 3145846410 * * @example Set the session id: - * var con = new MapdCon().connect().sessionId(3415846410); + * var con = new Connector().connect().sessionId(3415846410); * // NOTE: It is generally unsafe to set the session id manually. */ - sessionId (sessionId) { + function sessionId (newSessionId) { if (!arguments.length) { - return this._sessionId + return _sessionId } - this._sessionId = sessionId + _sessionId = newSessionId return this } - /** * Get or set the connection server hostname. - * This is is typically the first method called after instantiating a new MapdCon. - * @param {String} host - The hostname address - * @return {String|MapdCon} - The hostname or the MapdCon itself + * This is is typically the first method called after instantiating a new Connector. + * @param {String} hostname - The hostname address + * @returns {String|Connector} - The hostname or the Connector itself * * @example Set the hostname: - * var con = new MapdCon().host('localhost'); + * var con = new Connector().host('localhost'); * * @example Get the hostname: * var host = con.host(); * // host === 'localhost' */ - host (host) { + function host (hostname) { if (!arguments.length) { - return this._host + return _host } - this._host = arrayify(host) + _host = hostname return this } - /** * Get or set the connection port. - * @param {String} port - The port to connect on - * @return {String|MapdCon} - The port or the MapdCon itself + * @param {String} thePort - The port to connect on + * @returns {String|Connector} - The port or the Connector itself * * @example Set the port: - * var con = new MapdCon().port('8080'); + * var con = new Connector().port('8080'); * * @example Get the port: * var port = con.port(); * // port === '8080' */ - port (port) { + function port (thePort) { if (!arguments.length) { - return this._port + return _port } - this._port = arrayify(port) + _port = thePort return this } - /** * Get or set the username to authenticate with. - * @param {String} user - The username to authenticate with - * @return {String|MapdCon} - The username or the MapdCon itself + * @param {String} username - The username to authenticate with + * @returns {String|Connector} - The username or the Connector itself * * @example Set the username: - * var con = new MapdCon().user('foo'); + * var con = new Connector().user('foo'); * * @example Get the username: * var username = con.user(); * // user === 'foo' */ - user (user) { + function user (username) { if (!arguments.length) { - return this._user + return _user } - this._user = arrayify(user) + _user = username return this } - /** * Get or set the user's password to authenticate with. - * @param {String} password - The password to authenticate with - * @return {String|MapdCon} - The password or the MapdCon itself + * @param {String} pass - The password to authenticate with + * @returns {String|Connector} - The password or the Connector itself * * @example Set the password: - * var con = new MapdCon().password('bar'); + * var con = new Connector().password('bar'); * * @example Get the username: * var password = con.password(); * // password === 'bar' */ - password (password) { + function password (pass) { if (!arguments.length) { - return this._password + return _password } - this._password = arrayify(password) + _password = pass return this } - /** * Get or set the name of the database to connect to. - * @param {String} dbName - The database to connect to - * @return {String|MapdCon} - The name of the database or the MapdCon itself + * @param {String} db - The database to connect to + * @returns {String|Connector} - The name of the database or the Connector itself * * @example Set the database name: - * var con = new MapdCon().dbName('myDatabase'); + * var con = new Connector().dbName('myDatabase'); * * @example Get the database name: * var dbName = con.dbName(); * // dbName === 'myDatabase' */ - dbName (dbName) { + function dbName (db) { if (!arguments.length) { - return this._dbName + return _dbName } - this._dbName = arrayify(dbName) + _dbName = db return this } - /** * Whether the raw queries strings will be logged to the console. * Used primarily for debugging and defaults to false. - * @param {Boolean} logging - Set to true to enable logging - * @return {Boolean|MapdCon} - The current logging flag or MapdCon itself + * @param {Boolean} loggingEnabled - Set to true to enable logging + * @returns {Boolean|Connector} - The current logging flag or Connector itself * * @example Set logging to true: - * var con = new MapdCon().logging(true); + * var con = new Connector().logging(true); * * @example Get the logging flag: * var isLogging = con.logging(); * // isLogging === true */ - logging (logging) { - if (typeof logging === "undefined") { - return this._logging - } else if (typeof (logging) !== "boolean") { + function logging (loggingEnabled) { + if (typeof loggingEnabled === "undefined") { + return _logging + } else if (typeof loggingEnabled !== "boolean") { return "logging can only be set with boolean values" } - this._logging = logging - const isEnabledTxt = logging ? "enabled" : "disabled" + _logging = loggingEnabled + const isEnabledTxt = loggingEnabled ? "enabled" : "disabled" return `SQL logging is now ${isEnabledTxt}` } - - /** - * The name of the platform. - * @param {String} platform - The platform, default is "mapd" - * @return {String|MapdCon} - The platform or the MapdCon itself - * - * @example Set the platform name: - * var con = new MapdCon().platform('myPlatform'); - * - * @example Get the platform name: - * var platform = con.platform(); - * // platform === 'myPlatform' - */ - platform (platform) { - if (!arguments.length) { - return this._platform - } - this._platform = platform - return this - } - - /** - * Get the number of connections that are currently open. - * @return {Number} - number of open connections - * - * @example Get the number of connections: - * - * var numConnections = con.numConnections(); - * // numConnections === 1 - */ - numConnections () { - return this._numConnections - } - /** * The protocol to use for requests. - * @param {String} protocol - http or https - * @return {String|MapdCon} - protocol or MapdCon itself + * @param {String} theProtocol - http or https + * @returns {String|Connector} - protocol or Connector itself * * @example Set the protocol: - * var con = new MapdCon().protocol('http'); + * var con = new Connector().protocol('http'); * * @example Get the protocol: * var protocol = con.protocol(); * // protocol === 'http' */ - protocol (protocol) { + function protocol (theProtocol) { if (!arguments.length) { - return this._protocol + return _protocol } - this._protocol = arrayify(protocol) + _protocol = theProtocol return this } +} - /** - * Generates a list of endpoints from the connection params. - * @return {Array} - list of endpoints - * - * @example Get the endpoints: - * var con = new MapdCon().protocol('http').host('localhost').port('8000'); - * var endpoints = con.getEndpoints(); - * // endpoints === [ 'http://localhost:8000' ] - */ - getEndpoints () { - return this._host.map((host, i) => this._protocol[i] + "://" + host + ":" + this._port[i]) +// helper functions + +function noop () { /* noop */ } + +function isNodeRuntime () { return typeof window === "undefined" } + +function publicizeMethods (theClass, methods) { methods.forEach(method => { theClass[method.name] = method }) } + +function convertFromThriftTypes (fields, _datumEnum) { + const fieldsArray = [] + for (const key in fields) { + if (fields.hasOwnProperty(key)) { + fieldsArray.push({ + name: key, + type: _datumEnum[fields[key].col_type.type], + is_array: fields[key].col_type.is_array, + is_dict: fields[key].col_type.encoding === TEncodingType.DICT + }) + } + } + return fieldsArray +} + +function updateQueryTimes (queryTimes) { + return (conId, queryId, estimatedQueryTime, execution_time_ms) => { + queryTimes[queryId] = execution_time_ms + } +} + +function processPixelResults (callback, _logging, _datumEnum, error, results) { // eslint-disable-line consistent-return + if (error) { return callback(normalizeError(error)) } + results = Array.isArray(results) ? results.pixel_rows : [results] + const numPixels = results.length + const processResultsOptions = { + isImage: false, + eliminateNullRows: false, + query: "pixel request", + queryId: -2 + } + let numResultsProcessed = 0 + for (let p = 0; p < numPixels; p++) { + processResults(results[p], aggregatingCallback(p), _logging, _datumEnum, processResultsOptions) + } + function aggregatingCallback (index) { + return (processResultsError, row_set) => { + results[index].row_set = row_set + if (processResultsError) { + numResultsProcessed = -Infinity // avoid invoking callback again + return callback(processResultsError) + } else if (numResultsProcessed === numPixels - 1) { + return callback(null, results) + } else { + return numResultsProcessed++ + } + } + } +} + +function processResults (result, callback, _logging, _datumEnum, options = {}) { + const processor = processQueryResults(_logging, updateQueryTimes) + const processResultsObject = processor(options, _datumEnum, result, callback) + return processResultsObject +} + +function invertDatumTypes (datumEnum) { + const datumType = TDatumType + for (const key in datumType) { + if (datumType.hasOwnProperty(key)) { + datumEnum[datumType[key]] = key + } } } @@ -1184,11 +876,10 @@ function resetThriftClientOnArgumentErrorForMethods (connector, client, methodNa }) } -// Set a global mapdcon function when mapdcon is brought in via script tag. -if (typeof module === "object" && module.exports) { - if (!isNodeRuntime()) { - window.MapdCon = MapdCon +function normalizeError (error) { + if (isNodeRuntime()) { + return new Error(`${error.name} ${error.error_msg}`) + } else { + return new Error(`TMapDException ${error.message}`) } } -module.exports = MapdCon -export default MapdCon diff --git a/test/integration.spec.js b/test/integration.spec.js index 03e9a6cf..13ff07f9 100644 --- a/test/integration.spec.js +++ b/test/integration.spec.js @@ -1,8 +1,11 @@ "use strict" +/* eslint-disable no-unused-expressions */ +/* eslint-disable max-nested-callbacks */ +/* eslint-disable no-magic-numbers */ const isNodeRuntime = typeof window === "undefined" const expect = isNodeRuntime ? require("chai").expect : window.expect const convertToDataUrl = isNodeRuntime ? require("base64-arraybuffer").encode : x => x -const Connector = isNodeRuntime ? require("../dist/node-connector.js") : window.MapdCon +const Connector = isNodeRuntime ? require("../dist/node-connector.js") : window.Connector const imageRegex = /^iVBOR/ // An empty image data url will have about 80 header chars, then repeat 12 chars till it ends with a roughly 35 char footer. @@ -10,14 +13,29 @@ const imageRegex = /^iVBOR/ // Note that \1 substitutes in the value of the first capture group (the 12 chars). const emptyImageRegex = /^.{70,90}(.{12})\1+.{30,50}$/ -describe(isNodeRuntime ? "node" : "browser", () => { - let connector - beforeEach(() => { - connector = new Connector().protocol("https").host("metis.mapd.com").port("443").dbName("mapd").user("mapd").password("HyperInteractive") +describe(`${isNodeRuntime ? "node" : "browser"} Connector`, () => { + it(".connect success", done => { + new Connector().protocol("https").host("metis.mapd.com").port("443").dbName("mapd").user("mapd").password("HyperInteractive") + .connect((error, session) => { + expect(error).not.be.an.error + expect(session).to.be.an.instanceOf(Connector) + expect(session).to.respondTo("query") + done() + }) }) - const widgetId = 0 - const options = {} + it(".connect error", done => { + new Connector().password("invalid password").protocol("https").host("metis.mapd.com").port("443").dbName("mapd").user("mapd") + .connect((error, session) => { + expect(error).to.be.an.error + expect(error.message).to.equal("TMapDException Password for User mapd is incorrect.") + expect(session).to.be.undefined + done() + }) + }) +}) + +describe(`${isNodeRuntime ? "node" : "browser"} Connector session`, () => { const vega = JSON.stringify({ // vega must be a JSON-parsable string width: 384, height: 541, @@ -48,165 +66,357 @@ describe(isNodeRuntime ? "node" : "browser", () => { }] }) - it(".connect", done => { - connector.connect((connectError, session) => { - expect(connectError).not.be.an("error") - expect(session).to.respondTo("query") + let session = null + beforeEach(done => { + new Connector().protocol("https").host("metis.mapd.com").port("443").dbName("mapd").user("mapd").password("HyperInteractive") + .connect((error, newSession) => { + expect(error).not.be.an.error + session = newSession done() }) }) - it(".disconnect", done => { - connector.connect((connectError, session) => { - expect(connectError).to.not.be.an("error") - session.disconnect(disconnectError => { - expect(disconnectError).not.be.an("error") - expect(session.getServerStatus).to.throw() // example use of disconnected client should fail - done() - }) + it(".disconnect success", done => { + expect(session.sessionId()).not.to.be.null + session.disconnect((error, newSession) => { + expect(error).not.be.an.error + expect(newSession).to.be.an.instanceOf(Connector) + expect(newSession.sessionId()).to.be.null + done() }) }) - it(".getTablesAsync", done => { - connector.connect((connectError, session) => { - expect(connectError).to.not.be.an("error") - session.getTablesAsync() - .then(data => { - expect(data).to.deep.equal([ - {name: "flights_donotmodify", label: "obs"}, - {name: "contributions_donotmodify", label: "obs"}, - {name: "tweets_nov_feb", label: "obs"}, - {name: "zipcodes", label: "obs"} - ]) - done() + it(".getFrontendView success", done => { + session.getFrontendView("__E2E_NUMBER_CHART__DO_NOT_MODIFY__", (error, data) => { + expect(error).to.be.null + expect(JSON.parse(JSON.stringify(data))).to.deep.equal({ // parse stringify because some hidden prop fails deep equals. + view_name: "__E2E_NUMBER_CHART__DO_NOT_MODIFY__", + view_state: "eyJjaGFydHMiOnsiMCI6eyJkY0ZsYWciOjF9LCIxIjp7ImFyZUZpbHRlcnNJbnZlcnNlIjpmYWxzZSwiYmluUGFyYW1zIjpudWxsLCJjYXAiOjEyLCJjb2xvciI6eyJ0eXBlIjoic29saWQiLCJrZXkiOiJibHVlIiwidmFsIjpbIiMyN2FlZWYiXX0sImNvbG9yRG9tYWluIjpudWxsLCJkY0ZsYWciOjQsImRpbWVuc2lvbnMiOltdLCJlbGFzdGljWCI6dHJ1ZSwiZmlsdGVycyI6W10sImdlb0pzb24iOm51bGwsImhlaWdodCI6bnVsbCwibG9hZGluZyI6ZmFsc2UsIm1lYXN1cmVzIjpbeyJpc0Vycm9yIjpmYWxzZSwiaXNSZXF1aXJlZCI6ZmFsc2UsInRhYmxlIjoiZmxpZ2h0c19kb25vdG1vZGlmeSIsInR5cGUiOiJTTUFMTElOVCIsImlzX2FycmF5IjpmYWxzZSwiaXNfZGljdCI6ZmFsc2UsIm5hbWVfaXNfYW1iaWd1b3VzIjpmYWxzZSwibGFiZWwiOiJ0YXhpb3V0IiwidmFsdWUiOiJ0YXhpb3V0IiwiY29sb3JUeXBlIjoicXVhbnRpdGF0aXZlIiwiYWdnVHlwZSI6IkF2ZyIsImN1c3RvbSI6ZmFsc2UsIm9yaWdpbkluZGV4IjowLCJuYW1lIjoidmFsIiwiaW5hY3RpdmUiOmZhbHNlfV0sIm9yZGVyaW5nIjoiZGVzYyIsIm90aGVyc0dyb3VwZXIiOmZhbHNlLCJzYXZlZENvbG9ycyI6e30sInNvcnRDb2x1bW4iOnsiY29sIjp7Im5hbWUiOiJjb3VudHZhbCJ9LCJpbmRleCI6MSwib3JkZXIiOiJkZXNjIn0sInRpY2tzIjozLCJ0aW1lQmluSW5wdXRWYWwiOiIiLCJ0aXRsZSI6IiIsInR5cGUiOiJudW1iZXIiLCJ3aWR0aCI6bnVsbCwiaGFzRXJyb3IiOmZhbHNlfX0sInVpIjp7InNob3dGaWx0ZXJQYW5lbCI6ZmFsc2UsInNob3dDbGVhckZpbHRlcnNEcm9wZG93biI6ZmFsc2UsIm1vZGFsIjp7Im9wZW4iOmZhbHNlLCJjb250ZW50IjoiIiwiaGVhZGVyIjoiIiwiY2FuY2VsQnV0dG9uIjpmYWxzZX0sInNlbGVjdG9yUGlsbEhvdmVyIjp7InNob3VsZFNob3dQcm9tcHQiOmZhbHNlLCJtZXNzYWdlIjoiIiwidG9wIjowfSwic2VsZWN0b3JQb3NpdGlvbnMiOnsiZGltZW5zaW9ucyI6WzExMCwxMDYsMTUwLDExMCwxMTBdLCJtZWFzdXJlcyI6WzE5OSwxOTksMjM5LDI3OSwxOTUsMjM5LDE5OV19fSwiZmlsdGVycyI6W10sImRhc2hib2FyZCI6eyJ0aXRsZSI6Il9fRTJFX05VTUJFUl9DSEFSVF9fRE9fTk9UX01PRElGWV9fIiwiY2hhcnRDb250YWluZXJzIjpbeyJpZCI6IjEifV0sInRhYmxlIjoiZmxpZ2h0c19kb25vdG1vZGlmeSIsImZpbHRlcnNJZCI6W10sImxheW91dCI6W3sidyI6MTAsImgiOjEwLCJ4IjowLCJ5IjowLCJpIjoiMSIsIm1vdmVkIjpmYWxzZSwic3RhdGljIjpmYWxzZX1dLCJzYXZlTGlua1N0YXRlIjp7ImVycm9yIjpmYWxzZSwicmVxdWVzdCI6ZmFsc2UsInNhdmVMaW5rSWQiOm51bGx9LCJsb2FkU3RhdGUiOnsiZXJyb3IiOmZhbHNlLCJyZXF1ZXN0IjpmYWxzZSwibG9hZExpbmtJZCI6bnVsbH0sInNhdmVTdGF0ZSI6eyJlcnJvciI6ZmFsc2UsInJlcXVlc3QiOmZhbHNlLCJsYXN0U3RhdGUiOm51bGwsImlzU2F2ZWQiOmZhbHNlfX19", + image_hash: "", + update_time: "2016-10-15T05:32:44Z", + view_metadata: "{\"table\":\"flights_donotmodify\",\"version\":\"v2\"}" }) - .catch(getTablesAsyncError => expect(getTablesAsyncError).to.not.be.an("error")) - }) - }) - - it(".getFields", done => { - connector.connect((connectError, session) => { - expect(connectError).to.not.be.an("error") - session.getFields("flights_donotmodify", (getFieldsError, data) => { - expect(getFieldsError).to.not.be.an("error") - expect(data).to.deep.equal([ - {is_array: false, is_dict: false, name: "flight_year", type: "SMALLINT"}, - {is_array: false, is_dict: false, name: "flight_month", type: "SMALLINT"}, - {is_array: false, is_dict: false, name: "flight_dayofmonth", type: "SMALLINT"}, - {is_array: false, is_dict: false, name: "flight_dayofweek", type: "SMALLINT"}, - {is_array: false, is_dict: false, name: "deptime", type: "SMALLINT"}, - {is_array: false, is_dict: false, name: "crsdeptime", type: "SMALLINT"}, - {is_array: false, is_dict: false, name: "arrtime", type: "SMALLINT"}, - {is_array: false, is_dict: false, name: "crsarrtime", type: "SMALLINT"}, - {is_array: false, is_dict: true, name: "uniquecarrier", type: "STR"}, - {is_array: false, is_dict: false, name: "flightnum", type: "SMALLINT"}, - {is_array: false, is_dict: true, name: "tailnum", type: "STR"}, - {is_array: false, is_dict: false, name: "actualelapsedtime", type: "SMALLINT"}, - {is_array: false, is_dict: false, name: "crselapsedtime", type: "SMALLINT"}, - {is_array: false, is_dict: false, name: "airtime", type: "SMALLINT"}, - {is_array: false, is_dict: false, name: "arrdelay", type: "SMALLINT"}, - {is_array: false, is_dict: false, name: "depdelay", type: "SMALLINT"}, - {is_array: false, is_dict: true, name: "origin", type: "STR"}, - {is_array: false, is_dict: true, name: "dest", type: "STR"}, - {is_array: false, is_dict: false, name: "distance", type: "SMALLINT"}, - {is_array: false, is_dict: false, name: "taxiin", type: "SMALLINT"}, - {is_array: false, is_dict: false, name: "taxiout", type: "SMALLINT"}, - {is_array: false, is_dict: false, name: "cancelled", type: "SMALLINT"}, - {is_array: false, is_dict: true, name: "cancellationcode", type: "STR"}, - {is_array: false, is_dict: false, name: "diverted", type: "SMALLINT"}, - {is_array: false, is_dict: false, name: "carrierdelay", type: "SMALLINT"}, - {is_array: false, is_dict: false, name: "weatherdelay", type: "SMALLINT"}, - {is_array: false, is_dict: false, name: "nasdelay", type: "SMALLINT"}, - {is_array: false, is_dict: false, name: "securitydelay", type: "SMALLINT"}, - {is_array: false, is_dict: false, name: "lateaircraftdelay", type: "SMALLINT"}, - {is_array: false, is_dict: false, name: "dep_timestamp", type: "TIMESTAMP"}, - {is_array: false, is_dict: false, name: "arr_timestamp", type: "TIMESTAMP"}, - {is_array: false, is_dict: true, name: "carrier_name", type: "STR"}, - {is_array: false, is_dict: true, name: "plane_type", type: "STR"}, - {is_array: false, is_dict: true, name: "plane_manufacturer", type: "STR"}, - {is_array: false, is_dict: false, name: "plane_issue_date", type: "DATE"}, - {is_array: false, is_dict: true, name: "plane_model", type: "STR"}, - {is_array: false, is_dict: true, name: "plane_status", type: "STR"}, - {is_array: false, is_dict: true, name: "plane_aircraft_type", type: "STR"}, - {is_array: false, is_dict: true, name: "plane_engine_type", type: "STR"}, - {is_array: false, is_dict: false, name: "plane_year", type: "SMALLINT"}, - {is_array: false, is_dict: true, name: "origin_name", type: "STR"}, - {is_array: false, is_dict: true, name: "origin_city", type: "STR"}, - {is_array: false, is_dict: true, name: "origin_state", type: "STR"}, - {is_array: false, is_dict: true, name: "origin_country", type: "STR"}, - {is_array: false, is_dict: false, name: "origin_lat", type: "FLOAT"}, - {is_array: false, is_dict: false, name: "origin_lon", type: "FLOAT"}, - {is_array: false, is_dict: true, name: "dest_name", type: "STR"}, - {is_array: false, is_dict: true, name: "dest_city", type: "STR"}, - {is_array: false, is_dict: true, name: "dest_state", type: "STR"}, - {is_array: false, is_dict: true, name: "dest_country", type: "STR"}, - {is_array: false, is_dict: false, name: "dest_lat", type: "FLOAT"}, - {is_array: false, is_dict: false, name: "dest_lon", type: "FLOAT"}, - {is_array: false, is_dict: false, name: "origin_merc_x", type: "FLOAT"}, - {is_array: false, is_dict: false, name: "origin_merc_y", type: "FLOAT"}, - {is_array: false, is_dict: false, name: "dest_merc_x", type: "FLOAT"}, - {is_array: false, is_dict: false, name: "dest_merc_y", type: "FLOAT"} - ]) - done() + done() + }) + }) + + it(".getFrontendViews success", done => { + session.getFrontendViews((error, data) => { + expect(error).to.be.null + expect(JSON.parse(JSON.stringify(data))).to.deep.equal([ // parse stringify because some hidden prop fails deep equals. + { + image_hash: "", + update_time: "2016-10-15T05:44:20Z", + view_metadata: "{\"table\":\"flights_donotmodify\",\"version\":\"v2\"}", + view_name: "__E2E_LINE_CHART_BRUSH__DO_NOT_MODIFY__", + view_state: "" + }, { + image_hash: "", + update_time: "2016-10-15T05:40:39Z", + view_metadata: "{\"table\":\"flights_donotmodify\",\"version\":\"v2\"}", + view_name: "__E2E_MULTI_BIN_DIM_HEATMAP_FILTER__DO_NOT_MODIFY__", + view_state: "" + }, { + image_hash: "", + update_time: "2016-10-15T05:32:44Z", + view_metadata: "{\"table\":\"flights_donotmodify\",\"version\":\"v2\"}", + view_name: "__E2E_NUMBER_CHART__DO_NOT_MODIFY__", + view_state: "" + }, { + image_hash: "", + update_time: "2016-10-15T05:55:33Z", + view_metadata: "{\"table\":\"contributions_donotmodify\",\"version\":\"v2\"}", + view_name: "__E2E__ALL_CHARTS__DO_NOT_MODIFY__", + view_state: "" + } + ]) + done() + }) + }) + + it(".getServerStatus success", done => { + session.getServerStatus((error, data) => { + expect(error).to.be.null + expect(Object.keys(data).sort()).to.deep.equal(["edition", "read_only", "rendering_enabled", "start_time", "version"]) + expect(data.edition).to.be.null + expect(data.read_only).to.equal(true) + expect(data.rendering_enabled).to.equal(true) + expect(Number(data.start_time)).to.be.a.number // timestamp likely to change + expect(isNaN(data.start_time)).to.equal(false) // timestamp likely to change + expect(data.version).to.be.a.string // version likely to change + done() + }) + }) + + xit(".createFrontendView success") + + xit(".deleteFrontendView success") + + xit(".createLink success") + + it(".getLinkView success", done => { + const link = "1563954d" + session.getLinkView(link, (error, data) => { + expect(error).to.be.null + expect(JSON.parse(JSON.stringify(data))).to.deep.equal({ // parse stringify because some hidden prop fails deep equals. + view_name: "1563954d", + view_state: "eyJjaGFydHMiOnsiMCI6eyJkY0ZsYWciOjF9LCIxIjp7ImFyZUZpbHRlcnNJbnZlcnNlIjpmYWxzZSwiYmluUGFyYW1zIjpudWxsLCJjYXAiOjEyLCJjb2xvciI6eyJ0eXBlIjoic29saWQiLCJrZXkiOiJibHVlIiwidmFsIjpbIiMyN2FlZWYiXX0sImNvbG9yRG9tYWluIjpudWxsLCJkaW1lbnNpb25zIjpbXSwiZWxhc3RpY1giOnRydWUsImZpbHRlcnMiOltdLCJnZW9Kc29uIjpudWxsLCJoZWlnaHQiOm51bGwsImxvYWRpbmciOmZhbHNlLCJtZWFzdXJlcyI6W3siaXNFcnJvciI6ZmFsc2UsImlzUmVxdWlyZWQiOmZhbHNlLCJ0YWJsZSI6ImZsaWdodHNfZG9ub3Rtb2RpZnkiLCJ0eXBlIjoiU01BTExJTlQiLCJpc19hcnJheSI6ZmFsc2UsImlzX2RpY3QiOmZhbHNlLCJuYW1lX2lzX2FtYmlndW91cyI6ZmFsc2UsImxhYmVsIjoidGF4aW91dCIsInZhbHVlIjoidGF4aW91dCIsImNvbG9yVHlwZSI6InF1YW50aXRhdGl2ZSIsImFnZ1R5cGUiOiJBdmciLCJjdXN0b20iOmZhbHNlLCJvcmlnaW5JbmRleCI6MCwibmFtZSI6InZhbCIsImluYWN0aXZlIjpmYWxzZX1dLCJvdGhlcnNHcm91cGVyIjpmYWxzZSwic2F2ZWRDb2xvcnMiOnt9LCJzb3J0Q29sdW1uIjp7ImNvbCI6eyJuYW1lIjoiY291bnR2YWwifSwiaW5kZXgiOjEsIm9yZGVyIjoiZGVzYyJ9LCJ0aWNrcyI6MywidGltZUJpbklucHV0VmFsIjoiIiwidGl0bGUiOiIiLCJ0eXBlIjoibnVtYmVyIiwid2lkdGgiOm51bGwsImhhc0Vycm9yIjpmYWxzZSwic2hvd051bGxEaW1lbnNpb25zIjpmYWxzZSwicmFuZ2VGaWx0ZXIiOltdLCJkY0ZsYWciOjJ9fSwidWkiOnsic2VsZWN0b3JQb3NpdGlvbnMiOnsiZGltZW5zaW9ucyI6WzExMCwxMDYsMTUwLDExMCwxMTBdLCJtZWFzdXJlcyI6WzE5OSwxOTksMjM5LDI3OSwxOTUsMjM5LDE5OV19LCJzaG93RmlsdGVyUGFuZWwiOmZhbHNlLCJzaG93Q2xlYXJGaWx0ZXJzRHJvcGRvd24iOmZhbHNlLCJtb2RhbCI6eyJvcGVuIjpmYWxzZSwiY29udGVudCI6IiIsImhlYWRlciI6IiIsImNhbmNlbEJ1dHRvbiI6ZmFsc2V9LCJzZWxlY3RvclBpbGxIb3ZlciI6eyJzaG91bGRTaG93UHJvbXB0IjpmYWxzZSwibWVzc2FnZSI6IiIsInRvcCI6MH19LCJmaWx0ZXJzIjpbXSwiZGFzaGJvYXJkIjp7InRpdGxlIjoiX19FMkVfTlVNQkVSX0NIQVJUX19ET19OT1RfTU9ESUZZX18iLCJjaGFydENvbnRhaW5lcnMiOlt7ImlkIjoiMSJ9XSwidGFibGUiOiJmbGlnaHRzX2Rvbm90bW9kaWZ5IiwiZmlsdGVyc0lkIjpbXSwibGF5b3V0IjpbeyJ3IjoxMCwiaCI6MTAsIngiOjAsInkiOjAsImkiOiIxIiwibW92ZWQiOmZhbHNlLCJzdGF0aWMiOmZhbHNlfV0sInNhdmVMaW5rU3RhdGUiOnsiZXJyb3IiOmZhbHNlLCJyZXF1ZXN0IjpmYWxzZSwic2F2ZUxpbmtJZCI6bnVsbH0sImxvYWRTdGF0ZSI6eyJyZXF1ZXN0IjpmYWxzZSwiZXJyb3IiOmZhbHNlfSwic2F2ZVN0YXRlIjp7ImlzU2F2ZWQiOmZhbHNlLCJyZXF1ZXN0IjpmYWxzZSwiZXJyb3IiOmZhbHNlfSwiaW5pdGlhbGl6YXRpb24iOnsiZG9uZSI6ZmFsc2UsInBlbmRpbmciOmZhbHNlLCJlcnJvciI6ZmFsc2UsImNvdW50ZXIiOjB9LCJ2ZXJzaW9uIjoiMy4wLjAtMjAxNzA1MDItOWU1YmE5NSJ9fQ==", + image_hash: "", + update_time: "2017-06-25T05:29:11Z", + view_metadata: "" }) + done() + }) + }) + + it(".getLinkView error", done => { + const link = "BAD" + session.getLinkView(link, (error, data) => { + expect(error).to.be.an.error + expect(error.message).to.equal("TMapDException Link BAD is not valid.") + expect(data).to.be.undefined + done() + }) + }) + + xit(".detectColumnTypes success", done => { // TODO Can this be done without the file locally? What are valid copyParams? + const fileName = "test.csv" + const copyParams = {} + session.detectColumnTypes(fileName, copyParams, (error, data) => { + expect(error).to.be.null + expect(data).to.deep.equal([]) + done() + }) + }) + + it(".query success", done => { + const sql = "SELECT count(*) AS n FROM tweets_nov_feb WHERE country='CO'" + const options = {} + session.query(sql, options, (error, data) => { + expect(error).not.be.an.error + expect(Number(data[0].n)).to.equal(6400) + done() + }) + }) + + it(".query error", done => { + const sql = "invalid sql" + const options = {} + session.query(sql, options, (error, data) => { + expect(error).to.be.an.error + expect(error.message).to.equal("TMapDException Exception: ERROR-- Parse failed: line 1, column 1, Non-query expression encountered in illegal context") + expect(data).to.be.undefined + done() }) }) - it(".query", done => { + it(".validateQuery success", done => { const sql = "SELECT count(*) AS n FROM tweets_nov_feb WHERE country='CO'" - connector.connect((connectError, session) => { - expect(connectError).to.not.be.an("error") - session.query(sql, options, (error, data) => { - expect(error).not.be.an("error") - expect(Number(data[0].n)).to.equal(6400) + session.validateQuery(sql, (error, data) => { + expect(error).not.be.an.error + expect(data).to.deep.equal([{name: "n", type: "INT", is_array: false, is_dict: false}]) + done() + }) + }) + + it(".validateQuery error", done => { + const sql = "invalid sql" + session.validateQuery(sql, (error, data) => { + expect(error).to.be.an.error + expect(error.message).to.equal("TMapDException Exception: ERROR-- Parse failed: line 1, column 1, Non-query expression encountered in illegal context") + expect(data).to.be.undefined + done() + }) + }) + + it(".getTables success", done => { + session.getTables((getTablesError, data) => { + expect(getTablesError).to.not.be.an.error + expect(data).to.deep.equal([ + {name: "flights_donotmodify", label: "obs"}, + {name: "contributions_donotmodify", label: "obs"}, + {name: "tweets_nov_feb", label: "obs"}, + {name: "zipcodes", label: "obs"} + ]) + done() + }) + }) + + xit(".getTables error") + + it(".getFields success", done => { + session.getFields("flights_donotmodify", (getFieldsError, data) => { + expect(getFieldsError).to.not.be.an.error + expect(data).to.deep.equal([ + {is_array: false, is_dict: false, name: "flight_year", type: "SMALLINT"}, + {is_array: false, is_dict: false, name: "flight_month", type: "SMALLINT"}, + {is_array: false, is_dict: false, name: "flight_dayofmonth", type: "SMALLINT"}, + {is_array: false, is_dict: false, name: "flight_dayofweek", type: "SMALLINT"}, + {is_array: false, is_dict: false, name: "deptime", type: "SMALLINT"}, + {is_array: false, is_dict: false, name: "crsdeptime", type: "SMALLINT"}, + {is_array: false, is_dict: false, name: "arrtime", type: "SMALLINT"}, + {is_array: false, is_dict: false, name: "crsarrtime", type: "SMALLINT"}, + {is_array: false, is_dict: true, name: "uniquecarrier", type: "STR"}, + {is_array: false, is_dict: false, name: "flightnum", type: "SMALLINT"}, + {is_array: false, is_dict: true, name: "tailnum", type: "STR"}, + {is_array: false, is_dict: false, name: "actualelapsedtime", type: "SMALLINT"}, + {is_array: false, is_dict: false, name: "crselapsedtime", type: "SMALLINT"}, + {is_array: false, is_dict: false, name: "airtime", type: "SMALLINT"}, + {is_array: false, is_dict: false, name: "arrdelay", type: "SMALLINT"}, + {is_array: false, is_dict: false, name: "depdelay", type: "SMALLINT"}, + {is_array: false, is_dict: true, name: "origin", type: "STR"}, + {is_array: false, is_dict: true, name: "dest", type: "STR"}, + {is_array: false, is_dict: false, name: "distance", type: "SMALLINT"}, + {is_array: false, is_dict: false, name: "taxiin", type: "SMALLINT"}, + {is_array: false, is_dict: false, name: "taxiout", type: "SMALLINT"}, + {is_array: false, is_dict: false, name: "cancelled", type: "SMALLINT"}, + {is_array: false, is_dict: true, name: "cancellationcode", type: "STR"}, + {is_array: false, is_dict: false, name: "diverted", type: "SMALLINT"}, + {is_array: false, is_dict: false, name: "carrierdelay", type: "SMALLINT"}, + {is_array: false, is_dict: false, name: "weatherdelay", type: "SMALLINT"}, + {is_array: false, is_dict: false, name: "nasdelay", type: "SMALLINT"}, + {is_array: false, is_dict: false, name: "securitydelay", type: "SMALLINT"}, + {is_array: false, is_dict: false, name: "lateaircraftdelay", type: "SMALLINT"}, + {is_array: false, is_dict: false, name: "dep_timestamp", type: "TIMESTAMP"}, + {is_array: false, is_dict: false, name: "arr_timestamp", type: "TIMESTAMP"}, + {is_array: false, is_dict: true, name: "carrier_name", type: "STR"}, + {is_array: false, is_dict: true, name: "plane_type", type: "STR"}, + {is_array: false, is_dict: true, name: "plane_manufacturer", type: "STR"}, + {is_array: false, is_dict: false, name: "plane_issue_date", type: "DATE"}, + {is_array: false, is_dict: true, name: "plane_model", type: "STR"}, + {is_array: false, is_dict: true, name: "plane_status", type: "STR"}, + {is_array: false, is_dict: true, name: "plane_aircraft_type", type: "STR"}, + {is_array: false, is_dict: true, name: "plane_engine_type", type: "STR"}, + {is_array: false, is_dict: false, name: "plane_year", type: "SMALLINT"}, + {is_array: false, is_dict: true, name: "origin_name", type: "STR"}, + {is_array: false, is_dict: true, name: "origin_city", type: "STR"}, + {is_array: false, is_dict: true, name: "origin_state", type: "STR"}, + {is_array: false, is_dict: true, name: "origin_country", type: "STR"}, + {is_array: false, is_dict: false, name: "origin_lat", type: "FLOAT"}, + {is_array: false, is_dict: false, name: "origin_lon", type: "FLOAT"}, + {is_array: false, is_dict: true, name: "dest_name", type: "STR"}, + {is_array: false, is_dict: true, name: "dest_city", type: "STR"}, + {is_array: false, is_dict: true, name: "dest_state", type: "STR"}, + {is_array: false, is_dict: true, name: "dest_country", type: "STR"}, + {is_array: false, is_dict: false, name: "dest_lat", type: "FLOAT"}, + {is_array: false, is_dict: false, name: "dest_lon", type: "FLOAT"}, + {is_array: false, is_dict: false, name: "origin_merc_x", type: "FLOAT"}, + {is_array: false, is_dict: false, name: "origin_merc_y", type: "FLOAT"}, + {is_array: false, is_dict: false, name: "dest_merc_x", type: "FLOAT"}, + {is_array: false, is_dict: false, name: "dest_merc_y", type: "FLOAT"} + ]) + done() + }) + }) + + it(".getFields error", done => { + session.getFields("NOT A TABLE", (error, data) => { + expect(error).to.be.an.error + expect(error.message).to.equal("TMapDException Table doesn't exist") + expect(data).to.be.undefined + done() + }) + }) + + xit(".createTable success") + + xit(".importTable success") + + xit(".importShapeTable success") + + it(".renderVega success", done => { + const widgetId = 0 + const options = {} + session.renderVega(widgetId, vega, options, (renderVegaError, data) => { + expect(renderVegaError).to.not.be.an.error + const imageData = convertToDataUrl(data.image) + expect(imageData, "should be a image data URL").to.match(imageRegex) + expect(imageData, "shouldn't be an empty image").to.not.match(emptyImageRegex) + done() + }) + }) + + it(".renderVega error", done => { + const widgetId = 0 + const options = {} + const badVega = "BAD" + session.renderVega(widgetId, badVega, options, (error, data) => { + expect(error).to.be.an.error + expect(error.message).to.equal("TMapDException Exception: /home/jenkins-slave/workspace/mapd2-multi/compiler/gcc/gpu/cuda/host/centos/render/render/QueryRenderer/QueryRenderer.cpp:164 JSON parse error: json offset: 0, error: Invalid value.") + expect(data).to.be.undefined + done() + }) + }) + + it(".getPixel success", done => { + const widgetId = 0 + const options = {} + const pixel = {x: 70, y: 275} + const tableColNamesMap = {points: ["dest_lon"]} + session.renderVega(widgetId, vega, options, renderVegaError => { + expect(renderVegaError).to.not.be.an.error + session.getPixel(widgetId, pixel, tableColNamesMap, (pixelError, data) => { + expect(pixelError).to.not.be.an.error + expect(data[0].row_set).to.deep.equal([{dest_lon: -119.056770324707}]) // Ran 100 times; seems deterministic. done() }) }) }) - it(".render", done => { - connector.connect((connectError, session) => { - expect(connectError).to.not.be.an("error") - session.renderVega(widgetId, vega, options, (renderVegaError, data) => { - expect(renderVegaError).to.not.be.an("error") - const imageData = convertToDataUrl(data.image) - expect(imageData, "should be a image data URL").to.match(imageRegex) - expect(imageData, "shouldn't be an empty image").to.not.match(emptyImageRegex) + it(".getPixel success empty", done => { + const widgetId = 0 + const options = {} + const pixelWithNoResults = {x: 0, y: 0} + const tableColNamesMap = {points: ["dest_lon"]} + session.renderVega(widgetId, vega, options, renderVegaError => { + expect(renderVegaError).to.not.be.an.error + session.getPixel(widgetId, pixelWithNoResults, tableColNamesMap, (pixelError, data) => { + expect(pixelError).to.not.be.an.error + expect(data[0].row_set).to.deep.equal([]) done() }) }) }) - it(".getResultRowForPixel", done => { + xit(".getPixel error unrendered", done => { // TODO should throw rather than succeed empty + const widgetId = -947647 // not rendered const pixel = {x: 70, y: 275} - const tableColNamesMap = {points: ["dest_lon"]} // {vegaDataLayerName: [columnFromDataLayerTable]} - connector.connect((connectError, session) => { - expect(connectError).to.not.be.an("error") - session.renderVega(widgetId, vega, options, renderVegaError => { - expect(renderVegaError).to.not.be.an("error") - session.getResultRowForPixel(widgetId, pixel, tableColNamesMap, (pixelError, data) => { - expect(pixelError).to.not.be.an("error") - expect(data[0].row_set).to.deep.equal([{dest_lon: -119.056770324707}]) // Ran 100 times; seems deterministic. - done() - }) + const tableColNamesMap = {points: ["dest_lon"]} + session.getPixel(widgetId, pixel, tableColNamesMap, (error, data) => { + expect(error).to.be.an.error + expect(error.message).to.equal("TMapDException no vega rendered for id -947647.") + expect(data).to.be.undefined + done() + }) + }) + + it(".getPixel error", done => { + const widgetId = 0 + const options = {} + const pixel = {x: 70, y: 275} + const badTableColNamesMap = {points: ["NOT_A_COLUMN"]} + session.renderVega(widgetId, vega, options, renderVegaError => { + expect(renderVegaError).to.not.be.an.error + session.getPixel(widgetId, pixel, badTableColNamesMap, (pixelError, data) => { + expect(pixelError).to.be.an.error + expect(pixelError.message).to.equal("TMapDException Exception: TException - service has thrown: TMapDException(error_msg=Exception: ERROR-- Validate failed: From line 1, column 8 to line 1, column 21: Column 'NOT_A_COLUMN' not found in any table)") + expect(data).to.be.undefined + done() }) }) }) if (isNodeRuntime) { // bug only applies to node; in browser thriftTransportInstance is undefined. - it("on bad arguments: passes error, flushes internal buffer so next RPC doesn't fail, dereferences callback to avoid memory leak", done => { + it("on bad arguments: passes error, flushes internal buffer so next RPC doesn't fail, dereferences callback to avoid memory leak", () => { const BAD_ARG = {} - const callback = () => { /* noop */ } - connector.connect((_, session) => { - expect(() => session.getFields(BAD_ARG, callback)).to.throw("writeString called without a string/Buffer argument: [object Object]") - const thriftClient = connector._client[0] + expect(() => session.getFields(BAD_ARG, () => { + const thriftClient = session._client[0] const thriftTransportInstance = thriftClient.output expect(thriftTransportInstance.outCount).to.equal(0) expect(thriftTransportInstance.outBuffers).to.deep.equal([]) expect(thriftTransportInstance._seqid).to.equal(null) expect(thriftClient._reqs[thriftClient._seqid]).to.equal(null) - done() - }) + })).to.throw("writeString called without a string/Buffer argument: [object Object]") }) } })