query_log.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945
  1. //
  2. // Copyright (c) 2017-2026, Manticore Software LTD (https://manticoresearch.com)
  3. // Copyright (c) 2001-2016, Andrew Aksyonoff
  4. // Copyright (c) 2008-2016, Sphinx Technologies Inc
  5. // All rights reserved
  6. //
  7. // This program is free software; you can redistribute it and/or modify
  8. // it under the terms of the GNU General Public License. You should have
  9. // received a copy of the GPL license along with this program; if you
  10. // did not, you can find it at http://www.gnu.org
  11. //
  12. #include "logger.h"
  13. #include "fileutils.h"
  14. #include "searchdaemon.h"
  15. #include "client_session.h"
  16. #include <sys/stat.h>
  17. // for USE_SYSLOG
  18. #include "config.h"
  19. #if USE_SYSLOG
  20. #include <syslog.h>
  21. #endif
  22. constexpr int LOG_COMPACT_IN = 128; // upto this many IN(..) values allowed in query_log
  23. static bool g_bQuerySyslog = false;
  24. static int g_iQueryLogFile = -1;
  25. static CSphString g_sQueryLogFile;
  26. static LOG_FORMAT g_eLogFormat = LOG_FORMAT::SPHINXQL;
  27. static bool g_bLogCompactIn = false; // whether to cut list in IN() clauses.
  28. extern int g_iQueryLogMinMs; // log 'slow' threshold for query, defined in searchd.cpp
  29. static bool g_bLogIOStats = false;
  30. static bool g_bLogCPUStats = false;
  31. static CSphBitvec g_tLogStatements;
  32. bool SlowTimeExceeded ( int iTimeMs ) noexcept
  33. {
  34. return !g_iQueryLogMinMs || iTimeMs > g_iQueryLogMinMs;
  35. }
  36. void SetLogFormat ( LOG_FORMAT eFormat ) noexcept
  37. {
  38. g_eLogFormat = eFormat;
  39. }
  40. LOG_FORMAT LogFormat () noexcept
  41. {
  42. return g_eLogFormat;
  43. }
  44. void SetLogCompact ( bool bCompact ) noexcept
  45. {
  46. g_bLogCompactIn = bCompact;
  47. }
  48. void SetLogStatsFlags ( bool bIOStats, bool bCPUStats ) noexcept
  49. {
  50. g_bLogIOStats = bIOStats;
  51. g_bLogCPUStats = bCPUStats;
  52. }
  53. void SetupQueryLogDrain ( CSphString sQueryLog )
  54. {
  55. if ( sQueryLog == "syslog" )
  56. g_bQuerySyslog = true;
  57. else
  58. {
  59. FixPathAbsolute ( sQueryLog );
  60. g_iQueryLogFile = open ( sQueryLog.cstr(), O_CREAT | O_RDWR | O_APPEND, S_IREAD | S_IWRITE );
  61. if ( g_iQueryLogFile < 0 )
  62. sphFatal ( "failed to open query log file '%s': %s", sQueryLog.cstr(), strerrorm ( errno ) );
  63. ChangeLogFileMode ( g_iQueryLogFile );
  64. }
  65. g_sQueryLogFile = std::move ( sQueryLog );
  66. }
  67. void ReopenQueryLog ()
  68. {
  69. if ( g_bQuerySyslog || g_iQueryLogFile == GetDaemonLogFD() || g_iQueryLogFile < 0 || isatty ( g_iQueryLogFile ) )
  70. return;
  71. int iFD = ::open ( g_sQueryLogFile.cstr(), O_CREAT | O_RDWR | O_APPEND, S_IREAD | S_IWRITE );
  72. if ( iFD < 0 )
  73. {
  74. sphWarning ( "failed to reopen query log file '%s': %s", g_sQueryLogFile.cstr(), strerrorm(errno) );
  75. } else
  76. {
  77. ::close ( g_iQueryLogFile );
  78. g_iQueryLogFile = iFD;
  79. ChangeLogFileMode ( g_iQueryLogFile );
  80. sphInfo ( "query log reopened" );
  81. }
  82. }
  83. void RedirectQueryLogToDaemonLog ()
  84. {
  85. g_bQuerySyslog = false;
  86. DisableLogSyslog();
  87. g_iQueryLogFile = GetDaemonLogFD();
  88. }
  89. void OpenSyslogIfNecessary ()
  90. {
  91. #if USE_SYSLOG
  92. if ( LogSyslogEnabled() || g_bQuerySyslog )
  93. {
  94. openlog ( "searchd", LOG_PID, LOG_DAEMON );
  95. }
  96. #else
  97. if ( g_bQuerySyslog )
  98. sphFatal ( "Wrong query_log file! You have to reconfigure --with-syslog and rebuild daemon if you want to use syslog there." );
  99. #endif
  100. }
  101. static void WriteQuery ( const StringBuilder_c & tBuf )
  102. {
  103. sphSeek ( g_iQueryLogFile, 0, SEEK_END );
  104. sphWrite ( g_iQueryLogFile, tBuf.cstr(), tBuf.GetLength() );
  105. }
  106. static void LogQueryPlain ( const CSphQuery & tQuery, const CSphQueryResultMeta & tMeta )
  107. {
  108. assert ( g_eLogFormat==LOG_FORMAT::_PLAIN );
  109. if ( (!g_bQuerySyslog && g_iQueryLogFile < 0) || !tMeta.m_sError.IsEmpty() )
  110. return;
  111. QuotationEscapedBuilder tBuf;
  112. // [time]
  113. #if USE_SYSLOG
  114. if ( !g_bQuerySyslog )
  115. {
  116. #endif
  117. tBuf << '[';
  118. sphFormatCurrentTime ( tBuf );
  119. tBuf << ']';
  120. #if USE_SYSLOG
  121. } else
  122. tBuf += "[query]";
  123. #endif
  124. // querytime sec
  125. int iQueryTime = Max ( tMeta.m_iQueryTime, 0 );
  126. int iRealTime = Max ( tMeta.m_iRealQueryTime, 0 );
  127. tBuf.Appendf ( " %d.%03d sec", iRealTime / 1000, iRealTime % 1000 );
  128. tBuf.Appendf ( " %d.%03d sec", iQueryTime / 1000, iQueryTime % 1000 );
  129. // optional multi-query multiplier
  130. if ( tMeta.m_iMultiplier > 1 )
  131. tBuf.Appendf ( " x%d", tMeta.m_iMultiplier );
  132. // [matchmode/numfilters/sortmode matches (offset,limit)
  133. constexpr const char * sModes[SPH_MATCH_TOTAL] = { "all", "any", "phr", "bool", "ext", "scan", "ext2" };
  134. constexpr const char * sSort[SPH_SORT_TOTAL] = { "rel", "attr-", "attr+", "tsegs", "ext", "expr" };
  135. tBuf.Appendf ( " [%s/%d/%s " INT64_FMT " (%d,%d)",
  136. sModes[tQuery.m_eMode], tQuery.m_dFilters.GetLength(), sSort[tQuery.m_eSort], tMeta.m_iTotalMatches,
  137. tQuery.m_iOffset, tQuery.m_iLimit );
  138. // optional groupby info
  139. if ( !tQuery.m_sGroupBy.IsEmpty() )
  140. tBuf.Appendf ( " @%s", tQuery.m_sGroupBy.cstr() );
  141. // ] [indexes]
  142. tBuf.Appendf ( "] [%s]", tQuery.m_sIndexes.cstr() );
  143. // optional performance counters
  144. if ( g_bLogIOStats || g_bLogCPUStats )
  145. {
  146. const CSphIOStats & IOStats = tMeta.m_tIOStats;
  147. tBuf += " [";
  148. if ( g_bLogIOStats )
  149. tBuf.Appendf ( "ios=%d kb=%d.%d ioms=%d.%d",
  150. IOStats.m_iReadOps, (int) (IOStats.m_iReadBytes / 1024), (int) (IOStats.m_iReadBytes % 1024) * 10 / 1024,
  151. (int) (IOStats.m_iReadTime / 1000), (int) (IOStats.m_iReadTime % 1000) / 100 );
  152. if ( g_bLogIOStats && g_bLogCPUStats )
  153. tBuf += " ";
  154. if ( g_bLogCPUStats )
  155. tBuf.Sprintf ( "cpums=%.1D", tMeta.m_iCpuTime / 100 );
  156. tBuf += "]";
  157. }
  158. // optional query comment
  159. if ( !tQuery.m_sComment.IsEmpty() )
  160. tBuf.Appendf ( " [%s]", tQuery.m_sComment.cstr() );
  161. // query
  162. // (m_sRawQuery is empty when using MySQL handler)
  163. const CSphString & sQuery = tQuery.m_sRawQuery.IsEmpty()
  164. ? tQuery.m_sQuery
  165. : tQuery.m_sRawQuery;
  166. if ( !sQuery.IsEmpty() )
  167. {
  168. tBuf += " ";
  169. tBuf.FixupSpacesAndAppend ( sQuery.cstr() );
  170. }
  171. #if USE_SYSLOG
  172. if ( !g_bQuerySyslog )
  173. {
  174. #endif
  175. // line feed
  176. tBuf += "\n";
  177. WriteQuery ( tBuf );
  178. #if USE_SYSLOG
  179. } else
  180. {
  181. syslog ( LOG_INFO, "%s", tBuf.cstr() );
  182. }
  183. #endif
  184. }
  185. namespace {
  186. CSphString RemoveBackQuotes ( const char * pSrc )
  187. {
  188. CSphString sResult;
  189. if ( !pSrc )
  190. return sResult;
  191. size_t iLen = strlen ( pSrc );
  192. if ( !iLen )
  193. return sResult;
  194. auto szResult = new char[iLen + 1];
  195. auto * sMax = pSrc + iLen;
  196. auto d = szResult;
  197. while ( pSrc < sMax )
  198. {
  199. auto sQuote = (const char *) memchr ( pSrc, '`', sMax - pSrc );
  200. if ( !sQuote )
  201. sQuote = sMax;
  202. auto iChunk = sQuote - pSrc;
  203. memmove ( d, pSrc, iChunk );
  204. d += iChunk;
  205. pSrc += iChunk + 1; // +1 to skip the quote
  206. }
  207. *d = '\0';
  208. if ( !*szResult ) // never return allocated, but empty str. Prefer to return nullptr instead.
  209. SafeDeleteArray ( szResult );
  210. sResult.Adopt ( &szResult );
  211. return sResult;
  212. }
  213. }
  214. static void FormatOrderBy ( StringBuilder_c * pBuf, const char * sPrefix, ESphSortOrder eSort, const CSphString & sSort )
  215. {
  216. assert ( pBuf );
  217. if ( eSort == SPH_SORT_EXTENDED && sSort == "@weight desc" )
  218. return;
  219. const char * sSubst = "@weight";
  220. if ( sSort != "@relevance" )
  221. sSubst = sSort.cstr();
  222. auto sUnquoted = RemoveBackQuotes ( sSubst );
  223. sSubst = sUnquoted.cstr();
  224. // for simplicity check that sPrefix is already prefixed/suffixed by spaces.
  225. assert ( sPrefix && sPrefix[0]==' ' && sPrefix[strlen ( sPrefix )-1]==' ' );
  226. *pBuf << sPrefix;
  227. switch ( eSort )
  228. {
  229. case SPH_SORT_TIME_SEGMENTS: *pBuf << "TIME_SEGMENT(" << sSubst << ")";
  230. break;
  231. case SPH_SORT_EXTENDED: *pBuf << sSubst;
  232. break;
  233. case SPH_SORT_EXPR: *pBuf << "BUILTIN_EXPR()";
  234. break;
  235. case SPH_SORT_RELEVANCE: *pBuf << "weight() desc";
  236. if ( sSubst ) *pBuf << ", " << sSubst;
  237. break;
  238. default: pBuf->Appendf ( "mode-%d", (int) eSort );
  239. break;
  240. }
  241. }
  242. static const CSphQuery g_tDefaultQuery {};
  243. static void FormatOption ( const CSphQuery & tQuery, StringBuilder_c & tBuf, const char * szOption )
  244. {
  245. ScopedComma_c tOptionComma ( tBuf, ", ", szOption );
  246. if ( tQuery.m_iMaxMatches != DEFAULT_MAX_MATCHES )
  247. tBuf.Appendf ( "max_matches=%d", tQuery.m_iMaxMatches );
  248. if ( !tQuery.m_sComment.IsEmpty() )
  249. tBuf.Appendf ( "comment='%s'", tQuery.m_sComment.cstr() ); // FIXME! escape, replace newlines..
  250. if ( tQuery.m_eRanker != SPH_RANK_DEFAULT )
  251. {
  252. const char * sRanker = sphGetRankerName ( tQuery.m_eRanker );
  253. if ( !sRanker )
  254. sRanker = sphGetRankerName ( SPH_RANK_DEFAULT );
  255. if ( tQuery.m_sRankerExpr.IsEmpty() )
  256. tBuf.Appendf ( "ranker=%s", sRanker );
  257. else
  258. tBuf.Appendf ( "ranker=%s(\'%s\')", sRanker, tQuery.m_sRankerExpr.scstr() );
  259. }
  260. if ( tQuery.m_iAgentQueryTimeoutMs != DEFAULT_QUERY_TIMEOUT )
  261. tBuf.Appendf ( "agent_query_timeout=%d", tQuery.m_iAgentQueryTimeoutMs );
  262. if ( tQuery.m_iCutoff != g_tDefaultQuery.m_iCutoff )
  263. tBuf.Appendf ( "cutoff=%d", tQuery.m_iCutoff );
  264. if ( !tQuery.m_dFieldWeights.IsEmpty() )
  265. {
  266. tBuf.StartBlock ( nullptr, "field_weights=(", ")" );
  267. tBuf << Vec2Str ( tQuery.m_dFieldWeights, ", " );
  268. tBuf.FinishBlock();
  269. }
  270. if ( tQuery.m_bGlobalIDF != g_tDefaultQuery.m_bGlobalIDF )
  271. tBuf << "global_idf=1";
  272. if ( tQuery.m_bPlainIDF || !tQuery.m_bNormalizedTFIDF )
  273. {
  274. tBuf.StartBlock ( ",", "idf='", "'" );
  275. tBuf << (tQuery.m_bPlainIDF ? "plain" : "normalized")
  276. << (tQuery.m_bNormalizedTFIDF ? "tfidf_normalized" : "tfidf_unnormalized");
  277. tBuf.FinishBlock();
  278. }
  279. if ( tQuery.m_bLocalDF.has_value() )
  280. tBuf.Appendf ( "local_df=%d", tQuery.m_bLocalDF.value() ? 1 : 0 );
  281. if ( !tQuery.m_dIndexWeights.IsEmpty() )
  282. {
  283. tBuf.StartBlock ( nullptr, "index_weights=(", ")" );
  284. tBuf << Vec2Str ( tQuery.m_dIndexWeights, ", " );
  285. tBuf.FinishBlock();
  286. }
  287. if ( tQuery.m_uMaxQueryMsec != g_tDefaultQuery.m_uMaxQueryMsec )
  288. tBuf.Appendf ( "max_query_time=%u", tQuery.m_uMaxQueryMsec );
  289. if ( tQuery.m_iMaxPredictedMsec != g_tDefaultQuery.m_iMaxPredictedMsec )
  290. tBuf.Appendf ( "max_predicted_time=%d", tQuery.m_iMaxPredictedMsec );
  291. if ( tQuery.m_iRetryCount != DEFAULT_QUERY_RETRY )
  292. tBuf.Appendf ( "retry_count=%d", tQuery.m_iRetryCount );
  293. if ( tQuery.m_iRetryDelay != DEFAULT_QUERY_RETRY )
  294. tBuf.Appendf ( "retry_delay=%d", tQuery.m_iRetryDelay );
  295. if ( tQuery.m_iRandSeed != g_tDefaultQuery.m_iRandSeed )
  296. tBuf.Appendf ( "rand_seed=" INT64_FMT, tQuery.m_iRandSeed );
  297. if ( !tQuery.m_sQueryTokenFilterLib.IsEmpty() )
  298. {
  299. if ( tQuery.m_sQueryTokenFilterOpts.IsEmpty() )
  300. tBuf.Appendf ( "token_filter = '%s:%s'", tQuery.m_sQueryTokenFilterLib.cstr(), tQuery.m_sQueryTokenFilterName.cstr() );
  301. else
  302. tBuf.Appendf ( "token_filter = '%s:%s:%s'", tQuery.m_sQueryTokenFilterLib.cstr(), tQuery.m_sQueryTokenFilterName.cstr(), tQuery.m_sQueryTokenFilterOpts.cstr() );
  303. }
  304. if ( tQuery.m_bIgnoreNonexistent )
  305. tBuf << "ignore_nonexistent_columns=1";
  306. if ( tQuery.m_bIgnoreNonexistentIndexes )
  307. tBuf << "ignore_nonexistent_indexes=1";
  308. if ( tQuery.m_bStrict )
  309. tBuf << "strict=1";
  310. if ( tQuery.m_eExpandKeywords != QUERY_OPT_DEFAULT && tQuery.m_eExpandKeywords != QUERY_OPT_MORPH_NONE )
  311. tBuf.Appendf ( "expand_keywords=%d", (tQuery.m_eExpandKeywords == QUERY_OPT_ENABLED ? 1 : 0) );
  312. if ( tQuery.m_eExpandKeywords == QUERY_OPT_MORPH_NONE )
  313. tBuf.Appendf ( "morphology=none" );
  314. if ( tQuery.m_iExpansionLimit != DEFAULT_QUERY_EXPANSION_LIMIT )
  315. tBuf.Appendf ( "expansion_limit=%d", tQuery.m_iExpansionLimit );
  316. }
  317. static const char* GetHintName ( const IndexHint_t & tHint )
  318. {
  319. switch ( tHint.m_eType )
  320. {
  321. case SecondaryIndexType_e::FILTER: return "Filter";
  322. case SecondaryIndexType_e::LOOKUP: return "DocidIndex";
  323. case SecondaryIndexType_e::INDEX: return "SecondaryIndex";
  324. case SecondaryIndexType_e::ANALYZER: return "ColumnarScan";
  325. default: return "None";
  326. }
  327. }
  328. static void AppendHint ( const IndexHint_t & tHint, const StrVec_t & dIndexes, StringBuilder_c & tBuf )
  329. {
  330. auto sName = SphSprintf ( " %s%s (", (tHint.m_bForce?"":"NO_"), GetHintName ( tHint ) );
  331. ScopedComma_c tComma ( tBuf, ",", sName.cstr(), ")" );
  332. for ( const auto & sIndex: dIndexes )
  333. tBuf << sIndex;
  334. }
  335. static void FormatIndexHints ( const CSphQuery & tQuery, StringBuilder_c & tBuf )
  336. {
  337. if ( !tQuery.m_dIndexHints.GetLength() )
  338. return;
  339. ScopedComma_c sMatch ( tBuf, nullptr );
  340. CSphVector<bool> dUsed { tQuery.m_dIndexHints.GetLength() };
  341. dUsed.ZeroVec();
  342. tBuf << " /*+ ";
  343. ARRAY_FOREACH ( i, tQuery.m_dIndexHints )
  344. {
  345. if ( dUsed[i] )
  346. continue;
  347. StrVec_t dIndexes;
  348. dIndexes.Add ( tQuery.m_dIndexHints[i].m_sIndex );
  349. for ( int j = i + 1; j < tQuery.m_dIndexHints.GetLength(); j++ )
  350. if ( !dUsed[j] && tQuery.m_dIndexHints[i].m_eType == tQuery.m_dIndexHints[j].m_eType && tQuery.m_dIndexHints[i].m_bForce == tQuery.m_dIndexHints[j].m_bForce )
  351. {
  352. dIndexes.Add ( tQuery.m_dIndexHints[j].m_sIndex );
  353. dUsed[j] = true;
  354. }
  355. AppendHint ( tQuery.m_dIndexHints[i], dIndexes, tBuf );
  356. }
  357. tBuf << " */";
  358. }
  359. void VacuumSpacesFromJson ( const char* szJson, StringBuilder_c & tBuf ) noexcept
  360. {
  361. if (!szJson)
  362. return;
  363. enum class eStates { initial, has_space, quoted, backslash };
  364. auto eState = eStates::initial;
  365. while (*szJson)
  366. {
  367. const char c = *szJson++;
  368. switch (eState)
  369. {
  370. case eStates::initial:
  371. switch (c)
  372. {
  373. case ' ':
  374. case '\t':
  375. case '\n':
  376. case '\r': eState = eStates::has_space;
  377. tBuf << " "; break;
  378. case '"': eState = eStates::quoted;
  379. tBuf << "\""; break;
  380. default:
  381. tBuf << c; break;
  382. }
  383. break;
  384. case eStates::has_space:
  385. switch (c)
  386. {
  387. case ' ':
  388. case '\t':
  389. case '\n':
  390. case '\r': break;
  391. case '"': eState = eStates::quoted;
  392. tBuf << "\""; break;
  393. default: eState = eStates::initial;
  394. tBuf << c; break;
  395. }
  396. break;
  397. case eStates::quoted:
  398. tBuf << c;
  399. switch (c)
  400. {
  401. case '"': eState = eStates::initial; break;
  402. case '\\': eState = eStates::backslash;
  403. default: break;
  404. }
  405. break;
  406. case eStates::backslash:
  407. tBuf << c;
  408. eState = eStates::quoted;
  409. }
  410. }
  411. }
  412. static void LogQueryJson ( const CSphQuery & q, StringBuilder_c & tBuf )
  413. {
  414. tBuf << " /*";
  415. if ( q.m_sRawQuery.IsEmpty() )
  416. {
  417. tBuf << "{\"index\":\"" << q.m_sIndexes << "\"}*/ /*";
  418. VacuumSpacesFromJson ( q.m_sQuery.cstr(), tBuf );
  419. }
  420. else
  421. VacuumSpacesFromJson ( q.m_sRawQuery.cstr(), tBuf );
  422. tBuf << " */";
  423. }
  424. static void FormatTimeConnClient ( StringBuilder_c & tBuf )
  425. {
  426. sphFormatCurrentTime ( tBuf );
  427. tBuf << " conn " << session::GetConnID() << " (" << session::szClientName() << ")";
  428. }
  429. static const char* TypeCastToStr ( ESphAttr eAttr )
  430. {
  431. switch ( eAttr )
  432. {
  433. case SPH_ATTR_INTEGER: return "INTEGER";
  434. case SPH_ATTR_BIGINT: return "BIGINT";
  435. case SPH_ATTR_STRING: return "STRING";
  436. default: return nullptr;
  437. }
  438. }
  439. void FormatSphinxql ( const CSphQuery & q, const CSphQuery & tJoinOptions, int iCompactIN, QuotationEscapedBuilder & tBuf )
  440. {
  441. if ( q.m_bHasOuter )
  442. tBuf << "SELECT * FROM (";
  443. if ( q.m_sSelect.IsEmpty() )
  444. tBuf << "SELECT * FROM " << q.m_sIndexes;
  445. else
  446. tBuf << "SELECT " << RemoveBackQuotes ( q.m_sSelect.cstr() ) << " FROM " << q.m_sIndexes;
  447. if ( q.m_eJoinType != JoinType_e::NONE )
  448. {
  449. tBuf << (q.m_eJoinType == JoinType_e::LEFT ? " LEFT JOIN " : " INNER JOIN ")
  450. << q.m_sJoinIdx << " ON ";
  451. ARRAY_FOREACH ( i, q.m_dOnFilters )
  452. {
  453. const auto & tOn = q.m_dOnFilters[i];
  454. const char* szCast1 = TypeCastToStr ( tOn.m_eTypeCast1 );
  455. if ( szCast1 )
  456. tBuf.Sprint ( szCast1, '(', tOn.m_sIdx1, '.', tOn.m_sAttr1, ')' );
  457. else
  458. tBuf.Sprint ( tOn.m_sIdx1, '.', tOn.m_sAttr1 );
  459. tBuf << '=';
  460. const char* szCast2 = TypeCastToStr ( tOn.m_eTypeCast2 );
  461. if ( szCast2 )
  462. tBuf.Sprint ( szCast2, '(', tOn.m_sIdx2, '.', tOn.m_sAttr2, ')' );
  463. else
  464. tBuf.Sprint ( tOn.m_sIdx2, '.', tOn.m_sAttr2 );
  465. if ( i < q.m_dOnFilters.GetLength() - 1 )
  466. tBuf << " AND ";
  467. }
  468. }
  469. // WHERE clause
  470. // (m_sRawQuery is empty when using MySQL handler)
  471. const CSphString & sQuery = q.m_sQuery;
  472. if ( !sQuery.IsEmpty() || !q.m_sJoinQuery.IsEmpty() || q.m_dFilters.GetLength() )
  473. {
  474. ScopedComma_c sWHERE ( tBuf, " AND ", " WHERE " );
  475. if ( !sQuery.IsEmpty() )
  476. {
  477. ScopedComma_c sMatch ( tBuf, nullptr, "MATCH(", ")" );
  478. tBuf.FixupSpacedAndAppendEscaped ( sQuery.cstr() );
  479. }
  480. if ( !q.m_sJoinQuery.IsEmpty() )
  481. {
  482. CSphString sEnd;
  483. sEnd.SetSprintf ( ",%s)", q.m_sJoinIdx.cstr() );
  484. ScopedComma_c sMatch ( tBuf, nullptr, "MATCH(", sEnd.cstr() );
  485. tBuf.FixupSpacedAndAppendEscaped ( q.m_sJoinQuery.cstr() );
  486. }
  487. FormatFiltersQL ( q.m_dFilters, q.m_dFilterTree, tBuf, iCompactIN );
  488. }
  489. // ORDER BY and/or GROUP BY clause
  490. if ( q.m_sGroupBy.IsEmpty() )
  491. {
  492. if ( !q.m_sSortBy.IsEmpty() ) // case API SPH_MATCH_EXTENDED2 - SPH_SORT_RELEVANCE
  493. FormatOrderBy ( &tBuf, " ORDER BY ", q.m_eSort, q.m_sSortBy );
  494. } else
  495. {
  496. tBuf << " GROUP BY " << q.m_sGroupBy;
  497. FormatOrderBy ( &tBuf, " WITHIN GROUP ORDER BY ", q.m_eSort, q.m_sSortBy );
  498. if ( !q.m_tHaving.m_sAttrName.IsEmpty() )
  499. {
  500. ScopedComma_c sHawing ( tBuf, nullptr, " HAVING " );
  501. FormatFilterQL ( q.m_tHaving, tBuf, iCompactIN );
  502. }
  503. if ( q.m_sGroupSortBy != "@group desc" )
  504. FormatOrderBy ( &tBuf, " ORDER BY ", SPH_SORT_EXTENDED, q.m_sGroupSortBy );
  505. }
  506. // LIMIT clause
  507. if ( q.m_iOffset != 0 || q.m_iLimit != 20 )
  508. tBuf << " LIMIT ";
  509. if ( q.m_iOffset )
  510. tBuf << q.m_iOffset << ',';
  511. if ( q.m_iLimit != 20 )
  512. tBuf << q.m_iLimit;
  513. // OPTION clause
  514. FormatOption ( q, tBuf, " OPTION " );
  515. if ( q.m_eJoinType != JoinType_e::NONE )
  516. {
  517. CSphString sJoinedOption;
  518. sJoinedOption.SetSprintf ( " OPTION(%s) ", q.m_sJoinIdx.cstr() );
  519. FormatOption ( tJoinOptions, tBuf, sJoinedOption.cstr() );
  520. }
  521. FormatIndexHints ( q, tBuf );
  522. // outer order by, limit
  523. if ( q.m_bHasOuter )
  524. {
  525. tBuf << ')';
  526. if ( !q.m_sOuterOrderBy.IsEmpty() )
  527. tBuf << " ORDER BY " << q.m_sOuterOrderBy;
  528. if ( q.m_iOuterOffset > 0 )
  529. tBuf << " LIMIT " << q.m_iOuterOffset << ", " << q.m_iOuterLimit;
  530. else if ( q.m_iOuterLimit > 0 )
  531. tBuf << " LIMIT " << q.m_iOuterLimit;
  532. }
  533. // finish SQL statement
  534. tBuf << ';';
  535. }
  536. static void LogQuerySphinxql ( const CSphQuery & q, const CSphQuery & tJoinOptions, const CSphQueryResultMeta & tMeta, const CSphVector<int64_t> & dAgentTimes )
  537. {
  538. assert ( g_eLogFormat==LOG_FORMAT::SPHINXQL );
  539. if ( g_iQueryLogFile < 0 )
  540. return;
  541. QuotationEscapedBuilder tBuf;
  542. int iCompactIN = (g_bLogCompactIn ? LOG_COMPACT_IN : 0);
  543. // real = elapsed wall-clock; wall = internal query wall-time metric used by query logging.
  544. // In distributed/multi-source queries, wall and real may differ.
  545. int iQueryTime = Max ( tMeta.m_iQueryTime, 0 );
  546. int iRealTime = Max ( tMeta.m_iRealQueryTime, 0 );
  547. tBuf << "/* ";
  548. FormatTimeConnClient ( tBuf );
  549. tBuf << " real " << FixedFrac ( iRealTime ) << " wall " << FixedFrac ( iQueryTime );
  550. if ( tMeta.m_iMultiplier > 1 )
  551. tBuf << " x" << tMeta.m_iMultiplier;
  552. tBuf << " found " << tMeta.m_iTotalMatches << " */ ";
  553. ///////////////////////////////////
  554. // format request as SELECT query
  555. ///////////////////////////////////
  556. if ( q.m_eQueryType == QUERY_JSON )
  557. LogQueryJson ( q, tBuf );
  558. else
  559. FormatSphinxql ( q, tJoinOptions, iCompactIN, tBuf );
  560. ///////////////
  561. // query stats
  562. ///////////////
  563. // next block ecnlosed in /* .. */, space-separated
  564. tBuf.StartBlock ( " ", " /*", " */" );
  565. if ( !tMeta.m_sError.IsEmpty() )
  566. {
  567. // all we have is an error
  568. tBuf.Appendf ( "error=%s", tMeta.m_sError.cstr() );
  569. } else
  570. {
  571. // performance counters
  572. if ( g_bLogIOStats || g_bLogCPUStats )
  573. {
  574. const CSphIOStats & IOStats = tMeta.m_tIOStats;
  575. if ( g_bLogIOStats )
  576. tBuf.Sprintf ( "ios=%d kb=%d.%d ioms=%.1D",
  577. IOStats.m_iReadOps, (int) (IOStats.m_iReadBytes / 1024), (int) (IOStats.m_iReadBytes % 1024) * 10 / 1024,
  578. IOStats.m_iReadTime / 100 );
  579. if ( g_bLogCPUStats )
  580. tBuf.Sprintf ( "cpums=%.1D", tMeta.m_iCpuTime / 100 );
  581. }
  582. // per-agent times
  583. if ( dAgentTimes.GetLength() )
  584. {
  585. ScopedComma_c dAgents ( tBuf, ", ", " agents=(", ")" );
  586. for ( auto iTime: dAgentTimes )
  587. tBuf.Appendf ( "%d.%03d",
  588. (int) (iTime / 1000),
  589. (int) (iTime % 1000) );
  590. }
  591. // merged stats
  592. if ( tMeta.m_hWordStats.GetLength() && (tMeta.m_tExpansionStats.m_iTerms || tMeta.m_tExpansionStats.m_iMerged) )
  593. tBuf.Appendf ( "terms expansion=(merged %d, not merged %d)", tMeta.m_tExpansionStats.m_iMerged, tMeta.m_tExpansionStats.m_iTerms );
  594. // warning
  595. if ( !tMeta.m_sWarning.IsEmpty() )
  596. tBuf.Appendf ( "warning=%s", tMeta.m_sWarning.cstr() );
  597. }
  598. tBuf.FinishBlock(); // close the comment
  599. // line feed
  600. tBuf += "\n";
  601. WriteQuery ( tBuf );
  602. }
  603. void LogQuery ( const CSphQuery & q, const CSphQuery & tJoinOptions, const CSphQueryResultMeta & tMeta, const CSphVector<int64_t> & dAgentTimes )
  604. {
  605. if ( g_iQueryLogMinMs > 0 && tMeta.m_iQueryTime < g_iQueryLogMinMs )
  606. return;
  607. // should not log query from buddy in the info but only in debug and more verbose
  608. bool bNoLogQuery = ((q.m_uDebugFlags & QUERY_DEBUG_NO_LOG) == QUERY_DEBUG_NO_LOG);
  609. if ( bNoLogQuery && g_eLogLevel == SPH_LOG_INFO )
  610. return;
  611. switch ( g_eLogFormat )
  612. {
  613. case LOG_FORMAT::_PLAIN: LogQueryPlain ( q, tMeta );
  614. break;
  615. case LOG_FORMAT::SPHINXQL: LogQuerySphinxql ( q, tJoinOptions, tMeta, dAgentTimes );
  616. break;
  617. }
  618. }
  619. void LogSphinxqlError ( const char * szStmt, const Str_t & sError )
  620. {
  621. LogSphinxqlError ( FromSz ( szStmt ), sError );
  622. }
  623. void LogSphinxqlError ( const Str_t & sStmt, const Str_t & sError )
  624. {
  625. if ( g_eLogFormat != LOG_FORMAT::SPHINXQL || g_iQueryLogFile < 0 || !IsFilled ( sStmt ) || IsEmpty ( sError ) )
  626. return;
  627. // some mysql cli, like mysql 9.0.1, 9.1.0, 9.3.0, may be others, fire 'select $$' query after connect
  628. // that produces some noise in query log, so let's just filter out these queries. #2772
  629. constexpr Str_t selectSS = FROMS("select $$");
  630. if ( sStmt.second==selectSS.second && !strncmp (sStmt.first, selectSS.first, selectSS.second) )
  631. return;
  632. QuotationEscapedBuilder tBuf;
  633. tBuf << "/* ";
  634. FormatTimeConnClient ( tBuf );
  635. tBuf << " */ ";
  636. tBuf.AppendEscaped ( sStmt.first, EscBld::eFixupSpace, sStmt.second );
  637. tBuf << " # error=" << sError << '\n';
  638. WriteQuery ( tBuf );
  639. }
  640. void LogBuddyQuery ( Str_t sQuery, BuddyQuery_e tType )
  641. {
  642. if ( g_eLogFormat != LOG_FORMAT::SPHINXQL || g_iQueryLogFile < 0 || IsEmpty ( sQuery ) )
  643. return;
  644. const auto & tMeta = session::GetClientSession()->m_tLastMeta;
  645. QuotationEscapedBuilder tBuf;
  646. // real = elapsed wall-clock; wall = internal query wall-time metric used by query logging.
  647. // In distributed/multi-source queries, wall and real may differ.
  648. int iQueryTime = Max ( tMeta.m_iQueryTime, 0 );
  649. int iRealTime = Max ( tMeta.m_iRealQueryTime, 0 );
  650. tBuf << "/* ";
  651. FormatTimeConnClient ( tBuf );
  652. tBuf << " real " << FixedFrac ( iRealTime ) << " wall " << FixedFrac ( iQueryTime );
  653. if ( tMeta.m_iMultiplier > 1 )
  654. tBuf << " x" << tMeta.m_iMultiplier;
  655. tBuf << " found " << tMeta.m_iTotalMatches << " */ ";
  656. if ( tType == BuddyQuery_e::HTTP )
  657. tBuf << "/* ";
  658. tBuf.AppendEscaped ( sQuery.first, EscBld::eFixupSpace, sQuery.second );
  659. if ( tType == BuddyQuery_e::HTTP )
  660. tBuf << " */";
  661. tBuf << ";\n";
  662. WriteQuery ( tBuf );
  663. }
  664. static int64_t LogFilterStatementSphinxql ( Str_t sQuery, SqlStmt_e eStmt )
  665. {
  666. if ( g_tLogStatements.IsEmpty() )
  667. return 0;
  668. if ( !g_tLogStatements.BitGet ( eStmt ) )
  669. return 0;
  670. int64_t tmStarted = sphMicroTimer();
  671. LogSphinxqlClause ( sQuery, 0 );
  672. return tmStarted;
  673. }
  674. void ConfigureQueryLogCommands ( const CSphString & sMode )
  675. {
  676. if ( sMode.IsEmpty() )
  677. return;
  678. StrVec_t dOpts = sphSplit ( sMode.cstr(), "," );
  679. SmallStringHash_T<int> hStmt;
  680. for ( int i = 0; i < STMT_TOTAL; ++i )
  681. hStmt.Add ( i, SqlStmt2Str ( (SqlStmt_e) i ) );
  682. CSphBitvec tLogStatements ( STMT_TOTAL );
  683. StringBuilder_c sWrongModes ( "," );
  684. for ( const CSphString & sOpt: dOpts )
  685. {
  686. if ( sOpt == "0" ) // implicitly disable all statements
  687. return;
  688. if ( sOpt == "1" || sOpt == "*" ) // enable all statements
  689. {
  690. tLogStatements.Set();
  691. g_tLogStatements = tLogStatements;
  692. return;
  693. }
  694. // check for whole statement enumerated
  695. int * pMode = hStmt ( sOpt );
  696. if ( pMode )
  697. {
  698. tLogStatements.BitSet ( *pMode );
  699. continue;
  700. }
  701. bool bHasWild = false;
  702. for ( const char * s = sOpt.cstr(); *s && !bHasWild; s++ )
  703. bHasWild = sphIsWild ( *s );
  704. if ( bHasWild )
  705. {
  706. bool bMatched = false;
  707. for ( int i = 0; i < STMT_TOTAL; ++i )
  708. {
  709. if ( sphWildcardMatch ( SqlStmt2Str ( (SqlStmt_e) i ), sOpt.cstr() ) )
  710. {
  711. tLogStatements.BitSet ( i );
  712. bMatched = true;
  713. break;
  714. }
  715. }
  716. if ( bMatched )
  717. continue;
  718. }
  719. sWrongModes += sOpt.cstr();
  720. }
  721. if ( tLogStatements.BitCount() )
  722. g_tLogStatements = tLogStatements;
  723. if ( !sWrongModes.IsEmpty() )
  724. sphWarning ( "query_log_statements invalid values: %s", sWrongModes.cstr() );
  725. }
  726. void LogSphinxqlClause ( Str_t sQuery, int iRealTime )
  727. {
  728. if ( g_iQueryLogFile < 0 || g_eLogFormat != LOG_FORMAT::SPHINXQL || !IsFilled ( sQuery ) )
  729. return;
  730. if ( session::IsQueryLogDisabled() && g_eLogLevel == SPH_LOG_INFO )
  731. return;
  732. StringBuilder_c tBuf;
  733. tBuf << "/* ";
  734. FormatTimeConnClient ( tBuf );
  735. tBuf << " real " << FixedFrac ( iRealTime ) << " */ " << sQuery << ";\n";
  736. WriteQuery ( tBuf );
  737. }
  738. void LogQueryToSphinxlLog (const CSphString& sQuery, const CSphString& sError )
  739. {
  740. if ( g_eLogFormat!=LOG_FORMAT::SPHINXQL || g_iQueryLogFile<0 )
  741. return;
  742. StringBuilder_c tBuf;
  743. tBuf << "/* ";
  744. sphFormatCurrentTime ( tBuf );
  745. tBuf << "*/ " << sQuery << " # error=" << sError << '\n';
  746. WriteQuery ( tBuf );
  747. }
  748. constexpr Str_t g_tLogDoneStmt = FROMS ("/* DONE */");
  749. LogStmtGuard_c::LogStmtGuard_c ( const Str_t & sQuery, SqlStmt_e eStmt, bool bMulti )
  750. : m_tmStarted(LogFilterStatementSphinxql ( sQuery, eStmt ))
  751. {
  752. m_bLogDone = ( m_tmStarted && eStmt!=STMT_UPDATE && eStmt!=STMT_SELECT && !bMulti ); // update and select will log differently
  753. }
  754. LogStmtGuard_c::~LogStmtGuard_c ()
  755. {
  756. if ( !m_bLogDone )
  757. return;
  758. int64_t tmDelta = sphMicroTimer() - m_tmStarted;
  759. LogSphinxqlClause ( g_tLogDoneStmt, (int)( tmDelta / 1000 ) );
  760. }