String class refactored; background log writer option implemented; fixed incorrect...
[public/netxms.git] / src / server / core / loghandle.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2013 Victor Kirhenshtein
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 **
19 ** File: loghandle.cpp
20 **
21 **/
22
23 #include "nxcore.h"
24
25
26 /**
27 * Constructor
28 */
29 LogHandle::LogHandle(NXCORE_LOG *info)
30 {
31 m_log = info;
32 m_filter = NULL;
33 m_lock = MutexCreate();
34 m_resultSet = NULL;
35 m_rowCountLimit = 1000;
36 }
37
38 /**
39 * Destructor
40 */
41 LogHandle::~LogHandle()
42 {
43 deleteQueryResults();
44 delete m_filter;
45 MutexDestroy(m_lock);
46 }
47
48 /**
49 * Get column information
50 */
51 void LogHandle::getColumnInfo(NXCPMessage &msg)
52 {
53 UINT32 count = 0;
54 UINT32 varId = VID_COLUMN_INFO_BASE;
55 for(int i = 0; m_log->columns[i].name != NULL; i++, count++, varId += 7)
56 {
57 msg.setField(varId++, m_log->columns[i].name);
58 msg.setField(varId++, (WORD)m_log->columns[i].type);
59 msg.setField(varId++, m_log->columns[i].description);
60 }
61 msg.setField(VID_NUM_COLUMNS, count);
62 }
63
64 /**
65 * Delete query results
66 */
67 void LogHandle::deleteQueryResults()
68 {
69 if (m_resultSet != NULL)
70 {
71 DBFreeResult(m_resultSet);
72 m_resultSet = NULL;
73 }
74 }
75
76 /**
77 * Build query column list
78 */
79 void LogHandle::buildQueryColumnList()
80 {
81 m_queryColumns = _T("");
82 LOG_COLUMN *column = m_log->columns;
83 bool first = true;
84 while(column->name != NULL)
85 {
86 if (!first)
87 {
88 m_queryColumns += _T(",");
89 }
90 else
91 {
92 first = false;
93 }
94 m_queryColumns += column->name;
95 column++;
96 }
97 }
98
99 /**
100 * Do query according to filter
101 */
102 bool LogHandle::query(LogFilter *filter, INT64 *rowCount, const UINT32 userId)
103 {
104 deleteQueryResults();
105 delete m_filter;
106 m_filter = filter;
107
108 buildQueryColumnList();
109
110 m_maxRecordId = -1;
111 TCHAR query[256];
112 _sntprintf(query, 256, _T("SELECT coalesce(max(%s),0) FROM %s"), m_log->idColumn, m_log->table);
113 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
114 DB_RESULT hResult = DBSelect(hdb, query);
115 if (hResult != NULL)
116 {
117 if (DBGetNumRows > 0)
118 m_maxRecordId = DBGetFieldInt64(hResult, 0, 0);
119 DBFreeResult(hResult);
120 }
121 DBConnectionPoolReleaseConnection(hdb);
122 if (m_maxRecordId < 0)
123 return false;
124
125 return queryInternal(rowCount, userId);
126 }
127
128 /**
129 * Creates a SQL WHERE clause for restricting log to only objects accessible by given user.
130 */
131 String LogHandle::buildObjectAccessConstraint(const UINT32 userId)
132 {
133 String constraint;
134 ObjectArray<NetObj> *objects = g_idxObjectById.getObjects(true);
135 IntegerArray<UINT32> *allowed = new IntegerArray<UINT32>(objects->size());
136 IntegerArray<UINT32> *restricted = new IntegerArray<UINT32>(objects->size());
137 for(int i = 0; i < objects->size(); i++)
138 {
139 NetObj *object = objects->get(i);
140 if (object->isEventSource())
141 {
142 if (object->checkAccessRights(userId, OBJECT_ACCESS_READ))
143 {
144 allowed->add(object->getId());
145 }
146 else
147 {
148 restricted->add(object->getId());
149 }
150 }
151 object->decRefCount();
152 }
153 delete objects;
154
155 if (restricted->size() == 0)
156 {
157 // no restriction
158 }
159 else if (allowed->size() == 0)
160 {
161 constraint += _T("1=0"); // always false
162 }
163 else
164 {
165 IntegerArray<UINT32> *list;
166 if (allowed->size() < restricted->size())
167 {
168 list = allowed;
169 }
170 else
171 {
172 list = restricted;
173 constraint += _T("NOT (");
174 }
175
176 if (list->size() < 1000)
177 {
178 constraint.appendFormattedString(_T("%s IN ("), m_log->relatedObjectIdColumn);
179 for(int i = 0; i < list->size(); i++)
180 {
181 TCHAR buffer[32];
182 _sntprintf(buffer, 32, _T("%d,"), list->get(i));
183 constraint += buffer;
184 }
185 constraint.shrink();
186 constraint += _T(")");
187 }
188 else
189 {
190 for(int i = 0; i < list->size(); i++)
191 {
192 TCHAR buffer[32];
193 constraint.appendFormattedString(_T("(%s=%d)OR"), m_log->relatedObjectIdColumn, list->get(i));
194 constraint += buffer;
195 }
196 constraint.shrink(2);
197 }
198 if (allowed->size() >= restricted->size())
199 {
200 constraint += _T(")");
201 }
202 }
203 delete allowed;
204 delete restricted;
205 return constraint;
206 }
207
208 /**
209 * Do query with current filter and column set
210 */
211 bool LogHandle::queryInternal(INT64 *rowCount, const UINT32 userId)
212 {
213 QWORD qwTimeStart = GetCurrentTimeMs();
214 String query;
215 switch(g_dbSyntax)
216 {
217 case DB_SYNTAX_MSSQL:
218 query.appendFormattedString(_T("SELECT TOP %u %s FROM %s "), m_rowCountLimit, (const TCHAR *)m_queryColumns, m_log->table);
219 break;
220 case DB_SYNTAX_INFORMIX:
221 query.appendFormattedString(_T("SELECT FIRST %u %s FROM %s "), m_rowCountLimit, (const TCHAR *)m_queryColumns, m_log->table);
222 break;
223 case DB_SYNTAX_ORACLE:
224 query.appendFormattedString(_T("SELECT * FROM (SELECT %s FROM %s"), (const TCHAR *)m_queryColumns, m_log->table);
225 break;
226 case DB_SYNTAX_SQLITE:
227 case DB_SYNTAX_PGSQL:
228 case DB_SYNTAX_MYSQL:
229 case DB_SYNTAX_DB2:
230 query.appendFormattedString(_T("SELECT %s FROM %s"), (const TCHAR *)m_queryColumns, m_log->table);
231 break;
232 }
233
234 query.appendFormattedString(_T(" WHERE %s<=") INT64_FMT, m_log->idColumn, m_maxRecordId);
235
236 int filterSize = m_filter->getNumColumnFilter();
237 if (filterSize > 0)
238 {
239 for(int i = 0; i < filterSize; i++)
240 {
241 ColumnFilter *cf = m_filter->getColumnFilter(i);
242 query += _T(" AND (");
243 query += cf->generateSql();
244 query += _T(")");
245 }
246 }
247
248 if ((userId != 0) && (m_log->relatedObjectIdColumn != NULL) && ConfigReadInt(_T("ExtendedLogQueryAccessControl"), 0))
249 {
250 String constraint = buildObjectAccessConstraint(userId);
251 if (!constraint.isEmpty())
252 {
253 query += _T(" AND (");
254 query += constraint;
255 query += _T(")");
256 }
257 }
258
259 query += m_filter->buildOrderClause();
260
261 // Limit record count
262 switch(g_dbSyntax)
263 {
264 case DB_SYNTAX_MYSQL:
265 case DB_SYNTAX_PGSQL:
266 case DB_SYNTAX_SQLITE:
267 query.appendFormattedString(_T(" LIMIT %u"), m_rowCountLimit);
268 break;
269 case DB_SYNTAX_ORACLE:
270 query.appendFormattedString(_T(") WHERE ROWNUM<=%u"), m_rowCountLimit);
271 break;
272 case DB_SYNTAX_DB2:
273 query.appendFormattedString(_T(" FETCH FIRST %u ROWS ONLY"), m_rowCountLimit);
274 break;
275 }
276
277 DbgPrintf(4, _T("LOG QUERY: %s"), (const TCHAR *)query);
278
279 DB_HANDLE dbHandle = DBConnectionPoolAcquireConnection();
280 bool ret = false;
281 DbgPrintf(7, _T("LogHandle::query(): DB connection acquired"));
282 m_resultSet = DBSelect(dbHandle, (const TCHAR *)query);
283 if (m_resultSet != NULL)
284 {
285 *rowCount = DBGetNumRows(m_resultSet);
286 ret = true;
287 DbgPrintf(4, _T("Log query successfull, %d rows fetched in %d ms"), (int)(*rowCount), (int)(GetCurrentTimeMs() - qwTimeStart));
288 }
289 DBConnectionPoolReleaseConnection(dbHandle);
290
291 return ret;
292 }
293
294 /**
295 * Create table for sending data to client
296 */
297 Table *LogHandle::createTable()
298 {
299 Table *table = new Table();
300
301 LOG_COLUMN *column = m_log->columns;
302 bool first = true;
303 int columnCount = 0;
304 while (column->name != NULL)
305 {
306 table->addColumn(column->name);
307 column++;
308 columnCount++;
309 }
310
311 return table;
312 }
313
314 /**
315 * Get data from query result
316 */
317 Table *LogHandle::getData(INT64 startRow, INT64 numRows, bool refresh, const UINT32 userId)
318 {
319 DbgPrintf(4, _T("Log data request: startRow=%d, numRows=%d, refresh=%s, userId=%d"),
320 (int)startRow, (int)numRows, refresh ? _T("true") : _T("false"), userId);
321
322 if (m_resultSet == NULL)
323 return createTable(); // send empty table to indicate end of data
324
325 int resultSize = DBGetNumRows(m_resultSet);
326 if (((int)(startRow + numRows) >= resultSize) || refresh)
327 {
328 if ((resultSize < (int)m_rowCountLimit) && !refresh)
329 {
330 if (startRow >= resultSize)
331 return createTable(); // send empty table to indicate end of data
332 }
333 else
334 {
335 // possibly we have more rows or refresh was requested
336 UINT32 newLimit = (UINT32)(startRow + numRows);
337 if (newLimit > m_rowCountLimit)
338 m_rowCountLimit = (newLimit - m_rowCountLimit < 1000) ? (m_rowCountLimit + 1000) : newLimit;
339 deleteQueryResults();
340 INT64 rowCount;
341 if (!queryInternal(&rowCount, userId))
342 return NULL;
343 resultSize = DBGetNumRows(m_resultSet);
344 }
345 }
346
347 Table *table = createTable();
348 int maxRow = (int)min((int)(startRow + numRows), resultSize);
349 for(int i = (int)startRow; i < maxRow; i++)
350 {
351 table->addRow();
352 for(int j = 0; j < table->getNumColumns(); j++)
353 {
354 table->setPreallocated(j, DBGetField(m_resultSet, i, j, NULL, 0));
355 }
356 }
357
358 return table;
359 }