#include #include #include #include "../shared/GlobalConfigFile.h" #include "../shared/SimpleLogFile.h" #include "../shared/MiscSQL.h" #include "FormatTime.h" #include "TopListConfig.h" ///////////////////////////////////////////////////////////////////// // FilterListConfig ///////////////////////////////////////////////////////////////////// void FilterListConfig::clear() { _min.clear(); _max.clear(); } // Includes an ampersand before each name/value pair. std::string FilterListConfig::asSaveConfig() const { std::string result; for (std::map< std::string, double >::const_iterator it = _min.begin(); it != _min.end(); it++) { result += "&Min"; result += it->first; result += '='; result += ntoa(it->second); } for (std::map< std::string, double >::const_iterator it = _max.begin(); it != _max.end(); it++) { result += "&Max"; result += it->first; result += '='; result += ntoa(it->second); } return result; } void FilterListConfig::getFromConfigWindow(PropertyList const &config, PairedFilterList const &filters) { clear(); for (PairedFilterList::Iterator it = filters.begin(); it != filters.end(); it++) { const std::string baseName = (*it)->baseName; getFromConfigWindow(config, baseName, "Min", _min); getFromConfigWindow(config, baseName, "Max", _max); } //_value = strtodDefault(removeCommas(encodedConfig), NAN); // if (!finite(_value)) } void FilterListConfig::getFromConfigWindow(PropertyList const &config, std::string baseName, std::string prefix, std::map< std::string, double > &m) { const std::string input = getPropertyDefault(config,prefix + baseName); const double value = strtodDefault(AllConfigInfo::removeCommas(input), NAN); if (finite(value)) m[baseName] = value; } std::string FilterListConfig::allFiltersExpression(PairedFilterList const & filters) const { std::vector< std::string > expressions; for (PairedFilterList::Iterator it = filters.begin(); it != filters.end(); it++) { AllConfigInfo::PairedFilterInfo const *const info = *it; const double *minValue = getProperty(_min, info->baseName); const double *maxValue = getProperty(_max, info->baseName); if (minValue) { if (maxValue) { if (*minValue <= *maxValue) expressions.push_back('(' + info->sql + ") BETWEEN " + ntoa(*minValue) + " AND " + ntoa(*maxValue)); else expressions.push_back("((" + info->sql + ") >= " + ntoa(*minValue) + " OR (" + info->sql + ") <= " + ntoa(*maxValue) + ')'); } else expressions.push_back('(' + info->sql + ") >= " + ntoa(*minValue)); } else if (maxValue) expressions.push_back('(' + info->sql + ") <= " + ntoa(*maxValue)); } return sqlAnd(expressions); } void FilterListConfig::getStructureForEditor(XmlNode &parent, PairedFilterList const &filters, std::string const &language) { for (PairedFilterList::Iterator it = filters.begin(); it != filters.end(); it++) { AllConfigInfo::PairedFilterInfo const &info = **it; XmlNode &filterNode = parent["WINDOW_SPECIFIC_FILTERS"][-1]; filterNode.properties["BASE"] = info.baseName; copyLanguageValue(filterNode, "DESCRIPTION", info.description, language); copyLanguageValue(filterNode, "UNITS", info.units, language); if (!info.flip.empty()) filterNode.properties["FLIP"] = info.flip; copyLanguageValue(filterNode, "KEYWORDS", info.keywords, language); // Do we need this next bit? //if (info.keywords.empty()) //filterNode.properties["KEYWORDS"] = ".filter."; //else //filterNode.properties["KEYWORDS"] = info.keywords + " .filter."; } } void FilterListConfig::getValuesForEditor(XmlNode &parent) const { XmlNode &node = parent["WINDOW_SPECIFIC_FILTERS"]; getValuesForEditor(node, "Min", _min); getValuesForEditor(node, "Max", _max); } void FilterListConfig::getValuesForEditor(XmlNode &parent, std::string prefix, std::map< std::string, double > const &m ) const { for (std::map< std::string, double >::const_iterator it = m.begin(); it != m.end(); it++) { XmlNode &filterNode = parent[-1]; filterNode.name = prefix + it->first; filterNode.properties["VALUE"] = ntoa(it->second); } } ///////////////////////////////////////////////////////////////////// // SortByConfig ///////////////////////////////////////////////////////////////////// void SortByConfig::getInitialDescription(XmlNode &parent) const { parent["SORT_BY"].properties["FIELD"] = _field; } void SortByConfig::clear() { // Set some defaults. This should never really be empty. This is the same // default as on the web. _field = "RV"; _highestOnTop = true; } std::string SortByConfig::asString() const { return (_highestOnTop?"Max":"Min") + _field; } // Includes an ampersand before each name/value pair. std::string SortByConfig::asSaveConfig() const { return "&sort=" + asString(); } // If it fails, it returns false and the two outputs are undefined. // If it succeeds, it returns true and sets the two outputs accordingly. static bool findFilter(std::string name, PairedFilterList const &filters, bool &highestOnTop, AllConfigInfo::PairedFilterInfo const *&info) { if (name.size() < 4) return false; if (strncmp("Max", name.data(), 3) == 0) highestOnTop = true; else if (strncmp("Min", name.data(), 3) == 0) highestOnTop = false; else return false; info = filters.get(name.substr(3)); return info; } // Note that this does not ensure that we have a reasonable value. It tries, // but for a number of reasons, you will need to check again in orderBySql(). // If nothing else, the list of filters could change before then. void SortByConfig::getFromConfigWindow(PropertyList const &config, PairedFilterList const &filters) { AllConfigInfo::PairedFilterInfo const *info; if (!findFilter(getPropertyDefault(config, "sort"), filters, _highestOnTop, info)) // Reasonable defaults. clear(); else _field = info->baseName; } std::string SortByConfig::orderBySql(PairedFilterList const &filters) const { AllConfigInfo::PairedFilterInfo const *const info = filters.get(_field); if (!info) return "1"; return info->sql + (_highestOnTop?" DESC":" ASC"); } void SortByConfig::getValuesForEditor(XmlNode &parent) const { parent.properties["SORT"] = asString(); } ///////////////////////////////////////////////////////////////////// // SimpleExchangesConfig ///////////////////////////////////////////////////////////////////// void SimpleExchangesConfig::clear() { _activeExchanges.clear(); } // Includes an ampersand before each name/value pair. std::string SimpleExchangesConfig::asSaveConfig() const { AllConfigInfo const &allConfigInfo = AllConfigInfo::instance(); std::string result; for (AllConfigInfo::ExchangeIterator it = allConfigInfo.firstExchange(); it != allConfigInfo.endExchange(); it++) { AllConfigInfo::ExchangeInfo const &info = **it; if (_activeExchanges.get(info.asBit)) { result += '&'; result += info.formName; result += "=on"; } } return result; } void SimpleExchangesConfig::getFromConfigWindow(PropertyList const &config) { AllConfigInfo const &allConfigInfo = AllConfigInfo::instance(); for (AllConfigInfo::ExchangeIterator it = allConfigInfo.firstExchange(); it != allConfigInfo.endExchange(); it++) { AllConfigInfo::ExchangeInfo const &info = **it; if (config.count(info.formName)) { _activeExchanges.set(info.asBit); } } if (_activeExchanges.empty()) { //getDefaultExchanges() contains way too much. //_activeExchanges = allConfigInfo.getDefaultExchanges(); if (AllConfigInfo::ExchangeInfo const *NYSE = allConfigInfo.getExchange("X_NYSE")) _activeExchanges.set(NYSE->asBit); if (AllConfigInfo::ExchangeInfo const *AMEX = allConfigInfo.getExchange("X_AMEX")) _activeExchanges.set(AMEX->asBit); if (AllConfigInfo::ExchangeInfo const *NASDAQ = allConfigInfo.getExchange("XN")) _activeExchanges.set(NASDAQ->asBit); if (AllConfigInfo::ExchangeInfo const *ARCA = allConfigInfo.getExchange("X_ARCA")) _activeExchanges.set(ARCA->asBit); // Xetra if (AllConfigInfo::ExchangeInfo const *XTRA = allConfigInfo.getExchange("X_XETRA")) _activeExchanges.set(XTRA->asBit); } } std::string SimpleExchangesConfig::getSql() const { return AllConfigInfo::instance().exchangeExpression(_activeExchanges); } void SimpleExchangesConfig::getValuesForEditor(XmlNode &parent) const { AllConfigInfo const &allConfigInfo = AllConfigInfo::instance(); for (AllConfigInfo::ExchangeIterator it = allConfigInfo.firstExchange(); it != allConfigInfo.endExchange(); it++) { AllConfigInfo::ExchangeInfo const &info = **it; if (info.userVisible() && _activeExchanges.get(info.asBit)) { XmlNode &exchangeNode = parent["EXCHANGES"][-1]; exchangeNode.name = info.formName; } } } BitSet SimpleExchangesConfig::getValidExchanges(UserId userId, DatabaseWithRetry &database) { return (BitSet)database.tryQueryUntilSuccess("SELECT HEX(valid_exchanges) FROM users WHERE id=" + ntoa(userId))->getStringField(0, "0"); } void SimpleExchangesConfig::removeIllegalData(UserId userId, DatabaseWithRetry &database) { if (userId) _activeExchanges &= getValidExchanges(userId, database); } void SimpleExchangesConfig::getStructureForEditor(XmlNode &parent, UserId userId, DatabaseWithRetry &database) { AllConfigInfo const &allConfigInfo = AllConfigInfo::instance(); BitSet validExchanges = getValidExchanges(userId, database); for (AllConfigInfo::ExchangeIterator it = allConfigInfo.firstExchange(); it != allConfigInfo.endExchange(); it++) { AllConfigInfo::ExchangeInfo const &info = **it; if (info.userVisible()) { // If a person is not logged in, or is logged in a DEMO, show // them all of the exchanges. Let them play with it. They can't // get real data, so it doesn't hurt us. bool entitled = (!userId) || validExchanges.get(info.asBit); if (entitled) { XmlNode &exchangeNode = parent["EXCHANGES"][-1]; exchangeNode.name = info.formName; exchangeNode.properties["DESCRIPTION"] = info.description; if (!entitled) { exchangeNode.properties["DISABLED"] = "1"; } } } } } ///////////////////////////////////////////////////////////////////// // TopListTimeConfig ///////////////////////////////////////////////////////////////////// std::string TopListTimeConfig::asSaveConfig() const { std::string result; if (_history) { result += "&hist=1"; // This is a little strange. The date is always midnight server time. // But the hours should be New York time. Some times near midnight will // not encode and decode correctly. The website will throw out history // values more than 24 or less than 0. result += "&h_d="; result += ntoa(midnight(_time)); const int seconds = localToNy(secondOfTheDay(_time)); const int hours = seconds / 3600; const int minutes = (seconds / 60) % 60; result += "&h_h="; result += ntoa(hours); result += "&h_m="; result += ntoa(minutes); } else { if (_outsideMarketHours) result += "&omh=1"; } return result; } void TopListTimeConfig::getValuesForEditor(XmlNode &parent, bool easternTime) const { if (_history) parent.properties["HISTORY"] = exportTime(_time, easternTime); else if (_outsideMarketHours) parent.properties["OUTSIDE_MARKET_HOURS"] = "1"; } void TopListTimeConfig::getStructureForEditor(XmlNode &parent, DatabaseWithRetry &database) { XmlNode &group = parent["HISTORY"]; static const std::string sql[2] = { "SELECT @max_time := MAX(timestamp) FROM alerts", "SELECT date, UNIX_TIMESTAMP(date) FROM alerts_daily " "WHERE date <= DATE(@max_time) GROUP BY date " "ORDER BY date DESC" }; for (MysqlResultRef result = database.tryAllUntilSuccess(&sql[0], &sql[2])[1]; result->rowIsValid(); result->nextRow()) { XmlNode &node = group[-1]; node.properties["SQL"] = result->getStringField(0); // time_t is midnight server time. node.properties["TIME_T"] = ntoa(result->getIntegerField(1, 0)); } } // This is "quick" because it bypasses the database. The new top list code // needs to quickly decide which requests go to the historical database, // and which are streaming. void TopListTimeConfig::quickLoad(std::string const &config) { PropertyList propertyList; parseUrlRequest(propertyList, config); getFromConfigWindow(propertyList); } void TopListTimeConfig::getFromConfigWindow(PropertyList const &config) { clear(); _history = getPropertyDefault(config, "hist") == "1"; if (_history) { _time = importTime(getPropertyDefault(config, "exact_time")); if (!_time) { _time = strtollDefault(getPropertyDefault(config, "h_d"), 0) + (strtollDefault(getPropertyDefault(config, "h_h"), 0) * MARKET_HOURS_HOUR) + (strtollDefault(getPropertyDefault(config, "h_m"), 0) * MARKET_HOURS_MINUTE); _time = nyToLocal(_time); } // Mostly to be consistent with the web we round off to the nearst // 5 minute boundary. const int roundTo = 5 * MARKET_HOURS_MINUTE; _time = (_time + roundTo/2) / roundTo * roundTo; } else { // Live _outsideMarketHours = getPropertyDefault(config, "omh") == "1"; } } ///////////////////////////////////////////////////////////////////// // TopListConfig::Location ///////////////////////////////////////////////////////////////////// inline bool TopListConfig::Location::valid() const { return (mode == tlmLive) || ((minId != invalidMin()) && maxId != invalidMax()); } inline TclList TopListConfig::Location::debugDump() const { TclList result; result<<"mode"; switch (mode) { case tlmDelayed: result<<"tlmDelayed"; break; case tlmLive: result<<"tlmLive"; break; case tlmHistory: result<<"tlmHistory"; break; default: result<<(int)mode; } result<<"endOfDay"<<(endOfDay?"yes":"no") <<"mostRecentClose"<<(mostRecentClose?"yes":"no") <<"endTime"< 1000) _count = 1000; _clientCookie.getFromConfigWindow(rawConfig); } void TopListConfig::load(std::string rawConfig, UserId userId, DatabaseWithRetry &database, bool allowNonFilterColumns) { PropertyList propertyList; parseUrlRequest(propertyList, rawConfig); load(propertyList, userId, database, allowNonFilterColumns); } std::string TopListConfig::save() const { std::string result = "form=1"; result += _columnList.asSaveConfig(); result += _filterList.asSaveConfig(); result += _symbolLists.asSaveConfigUrl(); result += _sortBy.asSaveConfig(); result += _exchanges.asSaveConfig(); result += _time.asSaveConfig(); if (!_windowName.empty()) { result += "&WN="; result += urlEncode(_windowName, false); } if (_count != DEFAULT_COUNT) { result += "&count="; result += ntoa(_count); } result += _clientCookie.asSaveConfig(); return result; } void TopListConfig::removeIllegalData(UserId userId, DatabaseWithRetry &database) { _columnList.removeIllegalData(userId, database); _symbolLists.removeIllegalLists(userId, database); // In AlertConfig::removeIllegalData() we call // AlertConfig::useHiddenSettings() here. I don't think we'll need anything // like that for the top lists. _exchanges.removeIllegalData(userId, database); } void TopListConfig::saveToMru(UserId userId, DatabaseWithRetry &database) { // This was mostly copied from AlertConfig.C. I changed the name of the // table from view_mru to view_mru_tl. if (!userId) { return; } const std::string userIdStr = ntoa(userId); const std::string options = save(); std::string sql = "REPLACE INTO view_mru_tl VALUES (" + userIdStr + ", now(), '" + mysqlEscapeString(options) + "', md5('" + mysqlEscapeString(options) + "'))"; database.tryQueryUntilSuccess(sql); sql = "SELECT start_time FROM view_mru_tl WHERE user_id=" + userIdStr + " ORDER BY start_time DESC LIMIT 24,1"; MysqlResultRef result = database.tryQueryUntilSuccess(sql); const std::string date = result->getStringField(0); if (!date.empty()) { sql = "DELETE FROM view_mru_tl WHERE user_id=" + userIdStr + " AND start_time < '" + date + "'"; database.tryQueryUntilSuccess(sql); } } void TopListConfig::getInitialDescription(XmlNode &node, UserId userId, DatabaseWithRetry &database) const { //PairedFilterList filters(userId, database, true, true); PairedFilterList columns(userId, database, true, false); _columnList.getInitialDescription(node, columns); _sortBy.getInitialDescription(node); node.properties["SHORT_FORM"] = save(); node.properties["WINDOW_NAME"] = getWindowName(); _clientCookie.getInitialDescription(node); } void TopListConfig::getStructureForEditor(XmlNode &node, UserId userId, bool allowSymbolListFolders, bool allowNegativeListIds, DatabaseWithRetry &database, std::string const &language) { PairedFilterList columns(userId, database, true, false); ColumnListConfig::getStructureForEditor(node, userId, database, columns, language); PairedFilterList filters(userId, database, true, true); FilterListConfig::getStructureForEditor(node, filters, language); SymbolLists().getStructureForEditor(node, userId, allowSymbolListFolders, allowNegativeListIds, database); // _sortBy -- Use data from filter list. SimpleExchangesConfig::getStructureForEditor(node, userId, database); TopListTimeConfig::getStructureForEditor(node, database); // _windowName -- fixed structure. // _count -- fixed structure. } void TopListConfig::getSettingsForEditor(XmlNode &node, bool easternTime) const { _columnList.getValuesForEditor(node); _filterList.getValuesForEditor(node); _symbolLists.getValuesForEditor(node); _sortBy.getValuesForEditor(node); _exchanges.getValuesForEditor(node); _time.getValuesForEditor(node, easternTime); if (!_windowName.empty()) node.properties["WINDOW_NAME"] = _windowName; if (_count != DEFAULT_COUNT) node.properties["COUNT"] = ntoa(_count); } void TopListConfig::findData(Location &location, UserId userId, bool useEasternTime, DatabaseWithRetry &database, bool forceDelayMode) const { std::string offset; // The web site starts by sending "BEGIN" to the database here. It was hard // to translate that into the C++ code, so I just skipped it. if (_time.history()) location.mode = tlmHistory; else if (forceDelayMode || !userId) location.mode = tlmDelayed; else location.mode = tlmLive; switch (location.mode) { case tlmLive: { location.tableName = "top_list"; static const std::string sql = "SELECT max(timestamp) as max_timestamp, " "((TIME(MAX(timestamp)) < '" + secondsToMysql(MarketHours::close()+300) + "') AND " " (TIME(MAX(timestamp)) >= '" + secondsToMysql(MarketHours::open()+30) + "')) AS market_hours " "FROM top_list"; MysqlResultRef result = database.tryQueryUntilSuccess(sql); location.endTime = result->getStringField(0); if ((!_time.outsideMarketHours()) && (!result->getBooleanField(1))) { findData(location, userId, useEasternTime, database, true); return; } offset = "29 SECOND"; break; } case tlmDelayed: { location.tableName = "alerts"; std::string sql[3] = { "SELECT @max_id := MAX(id) FROM alerts WHERE alert_type='HB'", "SELECT @max_time := timestamp FROM alerts WHERE id = @max_id", "SELECT timestamp AS max_time, " "DATE(timestamp) AS todays_date, " "TIME(timestamp) >= '" + secondsToMysql(MarketHours::close()+300) + "' AS end_of_day, " "TIME(timestamp) < '" + secondsToMysql(MarketHours::open()+300) + "' AS previous_day " "FROM alerts " // "+ INTERVAL 0 SECOND" is required!! Everything was fine until // we upgraded from 5.5.29-log to 5.5.33a-MariaDB-log. The newer // version used a full table scan for this. Using an @ variable // with a date causes the problem. I couldn't find a work around // where I set the variable. But adding this where we use the // variable fixed the problem. "WHERE timestamp < @max_time + INTERVAL 0 SECOND " "AND timestamp < NOW()" }; if (!userId) // Add delay sql[2] += " - INTERVAL 15 MINUTE"; sql[2] += " ORDER BY timestamp DESC LIMIT 1"; MysqlResultRef result = database.tryAllUntilSuccess(&sql[0], &sql[3])[2]; if (result->getBooleanField("end_of_day")) { // After the close. Look at the data from the close. location.endOfDay = true; const std::string sql1 = "SELECT MAX(timestamp) AS max_time " "FROM alerts " "WHERE timestamp <= '" + secondsToMysql(MarketHours::close()+299, result->getStringField("todays_date")) + "'"; location.endTime = database.tryQueryUntilSuccess(sql1)->getStringField(0); location.mostRecentClose = true; } else if (result->getBooleanField("previous_day")) { const std::string sqlYesterday = "SELECT DISTINCT date as yesterday " "FROM alerts_daily ORDER BY date DESC LIMIT 1 OFFSET 1"; const std::string yesterday = database.tryQueryUntilSuccess(sqlYesterday)->getStringField(0); const std::string sql1 = "SELECT MAX(timestamp) AS max_time " "FROM alerts " "WHERE timestamp <= '" + secondsToMysql(MarketHours::close()+299, yesterday) + "'"; location.endTime = database.tryQueryUntilSuccess(sql1)->getStringField(0); location.mostRecentClose = true; } else { location.endTime = result->getStringField("max_time"); location.mostRecentClose = false; } const std::string getIdsSql[3] = { "SELECT MAX(id) " "FROM alerts " "WHERE timestamp='" + location.endTime + "'", // The next step is required on the Xetra servers because we don't // have an alert in every time period. "SELECT @start_time := MIN(timestamp) " "FROM alerts " "WHERE timestamp >= '" + location.endTime + "' - INTERVAL 299 SECOND", "SELECT MIN(id) " "FROM alerts " "WHERE timestamp=@start_time" }; std::vector< MysqlResultRef > results = database.tryAllUntilSuccess(&getIdsSql[0], &getIdsSql[3]); location.maxId = results[0]->getIntegerField(0, Location::invalidMax()); location.minId = results[2]->getIntegerField(0, Location::invalidMin()); offset = "299 SECOND"; break; } case tlmHistory: { // We always look at 5 minutes worth of data because we are only that // precise. If someone asks for the open, 6:30am pacific, we show // everything between 6:30 and 6:35, i.e. the first 5 minutes of the // market, not the last 5 minutes before the market opens. // The close is similar. We have some special logic for picking the // "last" so the price doesn't change (much) after the close. So the // time they specify is the start time, not the end time. location.endTime = timeTToMysql(_time.getValue() + 299); const std::string sql1 = "SELECT table_name FROM alert_shards " "WHERE date = DATE(FROM_UNIXTIME(" + ntoa(_time.getValue()) + ")) " "AND live='Y'"; location.tableName = database.tryQueryUntilSuccess(sql1)->getStringField(0, "alerts"); const std::string getFirstSql[3] = { "SELECT @min_time := NULL", "SELECT @min_time := timestamp FROM " + location.tableName + " WHERE timestamp >='" + location.endTime + "' - INTERVAL 299 SECOND ORDER BY timestamp ASC limit 1", "SELECT MIN(id) FROM " + location.tableName + " WHERE timestamp = @min_time" }; location.minId = database.tryAllUntilSuccess(&getFirstSql[0], &getFirstSql[3])[2] ->getIntegerField(0, Location::invalidMin()); const std::string getLastSql[3] = { "SELECT @max_time := NULL", "SELECT @max_time := timestamp FROM " + location.tableName + " WHERE timestamp <= '" + location.endTime + "' ORDER BY timestamp DESC limit 1", "SELECT MAX(id) FROM " + location.tableName + " WHERE timestamp = @max_time" }; location.maxId = database.tryAllUntilSuccess(&getLastSql[0], &getLastSql[3])[2] ->getIntegerField(0, Location::invalidMax()); offset = "299 SECOND"; break; } } // Convert mysql to time_t. Seems like there should be an easier way to do // this! std::string sql; if (useEasternTime) sql = "SELECT \"" + location.endTime + "\" + INTERVAL 3 HOUR, \"" + location.endTime + "\" - INTERVAL " + offset + " + INTERVAL 3 HOUR"; else sql = "SELECT UNIX_TIMESTAMP(\"" + location.endTime + "\"), UNIX_TIMESTAMP(\"" + location.endTime + "\" - INTERVAL " + offset + ")"; MysqlResultRef result = database.tryQueryUntilSuccess(sql); location.userEndTime = result->getStringField(0); location.userStartTime = result->getStringField(1); } static void copyData(PropertyList &dest, MysqlResultRef &source, std::string const &fieldName) { const std::string value = source->getStringField(fieldName); if (!value.empty()) dest[fieldName] = value; } DayNumber TopListConfig::dayNumber(DatabaseWithRetry &database) const { if (!_time.history()) return DAY_NUMBER_UNKNOWN; const std::string sql = "SELECT day FROM alert_shards WHERE date = DATE(FROM_UNIXTIME(" + ntoa(_time.getValue()) + ")) AND live='Y'"; return database.tryQueryUntilSuccess(sql) ->getIntegerField(0, DAY_NUMBER_UNKNOWN); } void TopListConfig::getData(AllData &allData, UserId userId, bool useEasternTime, DatabaseWithRetry &database, bool showAllRows) const { allData.rows.clear(); Location location; findData(location, userId, useEasternTime, database, false); if (!location.valid()) { TclList msg; msg<sql; std::string sql = "SELECT "; sql += location.tableName; sql += ".symbol"; sql += _columnList.selectSql(columns); sql += ", list_exch AS EXCHANGE FROM "; sql += location.tableName; sql += ' '; if (location.mode != tlmLive) sql += "USE INDEX (by_type) "; sql += " LEFT JOIN alerts_daily ON "; sql += location.tableName; sql += ".symbol=d_symbol AND date='"; sql += dateOfScan; sql += "' WHERE "; std::vector< std::string > whereSql; if (location.mode != tlmLive) { whereSql.push_back("id BETWEEN " + ntoa(location.minId) + " AND " + ntoa(location.maxId)); whereSql.push_back("timestamp BETWEEN '" + location.endTime + "' - INTERVAL 299 SECOND AND '" + location.endTime + "'"); } else { whereSql.push_back("timestamp > date('" + location.endTime + "')"); } whereSql.push_back(_filterList.allFiltersExpression(realFilters)); whereSql.push_back(_exchanges.getSql()); if (location.mode != tlmLive) whereSql.push_back("alert_type = 'HB'"); if (!showAllRows) whereSql.push_back(sortField + " IS NOT NULL"); whereSql.push_back(_symbolLists.whereSql(userId, location.tableName)); sql += sqlAnd(whereSql); if (_symbolLists.getSymbolListType() == SymbolLists::sltOnly) sql += " GROUP BY " + location.tableName + ".symbol"; if (!showAllRows) { sql += " ORDER BY ("; sql += sortField; sql += ") "; if (_sortBy.highestOnTop()) sql += "DESC "; sql += "LIMIT "; sql += ntoa(_count); } const std::string priceField = location.endOfDay?"last":"price"; //TclList msg; //msg<<__FILE__<<__LINE__<<__FUNCTION__ // < 0) { TimeVal::Microseconds time = end.asMicroseconds() - start.asMicroseconds(); if (time > logTime) { TclList msg; msg<numRows()); Rows::size_type index = 0; for (; result->rowIsValid(); result->nextRow(), index++) { PropertyList &row = allData.rows[index]; copyData(row, result, "symbol"); copyData(row, result, "EXCHANGE"); _columnList.copyResult(row, result); } assert(index == allData.rows.size()); } TimeVal::Microseconds TopListConfig::logTime = 0; void TopListConfig::globalInit() { // Store this in microseconds, which is commonly used by the libraries. // Read this in decimal seconds. "1.2" mean 1.2 seconds. "0" or // unspecified means off. const std::string toplist_log_time = getConfigItem("toplist_log_time"); logTime = (TimeVal::Microseconds)(strtodDefault(toplist_log_time, 0.0) * 1000000); TclList msg; msg<