PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 4.0.3
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v4.0.3
5.12.0 5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / DataAccess / ArchiveWriter.php
matomo / app / core / DataAccess Last commit date
LogQueryBuilder 5 years ago Actions.php 5 years ago ArchiveSelector.php 5 years ago ArchiveTableCreator.php 5 years ago ArchiveTableDao.php 5 years ago ArchiveWriter.php 5 years ago ArchivingDbAdapter.php 5 years ago LogAggregator.php 5 years ago LogQueryBuilder.php 5 years ago LogTableTemporary.php 5 years ago Model.php 5 years ago RawLogDao.php 5 years ago TableMetadata.php 5 years ago
ArchiveWriter.php
337 lines
1 <?php
2 /**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 *
8 */
9 namespace Piwik\DataAccess;
10
11 use Exception;
12 use Piwik\Archive\Chunk;
13 use Piwik\ArchiveProcessor\Rules;
14 use Piwik\ArchiveProcessor;
15 use Piwik\Date;
16 use Piwik\Db;
17 use Piwik\Db\BatchInsert;
18
19 /**
20 * This class is used to create a new Archive.
21 * An Archive is a set of reports (numeric and data tables).
22 * New data can be inserted in the archive with insertRecord/insertBulkRecords
23 */
24 class ArchiveWriter
25 {
26 /**
27 * Flag stored at the end of the archiving
28 *
29 * @var int
30 */
31 const DONE_OK = 1;
32 /**
33 * Flag stored at the start of the archiving
34 * When requesting an Archive, we make sure that non-finished archive are not considered valid
35 *
36 * @var int
37 */
38 const DONE_ERROR = 2;
39
40 /**
41 * Flag indicates the archive is over a period that is not finished, eg. the current day, current week, etc.
42 * Archives flagged will be regularly purged from the DB.
43 *
44 * This flag is deprecated, new archives should not be written as temporary.
45 *
46 * @var int
47 * @deprecated it should not be used anymore as temporary archives have been removed. It still exists though for
48 * historical reasons.
49 */
50 const DONE_OK_TEMPORARY = 3;
51
52 /**
53 * Flag indicated that archive is done but was marked as invalid later and needs to be re-processed during next archiving process
54 *
55 * @var int
56 */
57 const DONE_INVALIDATED = 4;
58
59 /**
60 * Flag indicating that the archive is
61 *
62 * @var int
63 */
64 const DONE_PARTIAL = 5;
65
66 protected $fields = array('idarchive',
67 'idsite',
68 'date1',
69 'date2',
70 'period',
71 'ts_archived',
72 'name',
73 'value');
74
75 private $recordsToWriteSpool = array(
76 'numeric' => array(),
77 'blob' => array()
78 );
79
80 const MAX_SPOOL_SIZE = 50;
81
82 /**
83 * @var ArchiveProcessor\Parameters
84 */
85 private $parameters;
86
87 /**
88 * @var string
89 */
90 private $earliestNow;
91
92 /**
93 * ArchiveWriter constructor.
94 * @param ArchiveProcessor\Parameters $params
95 * @param bool $isArchiveTemporary Deprecated. Has no effect.
96 * @throws Exception
97 */
98 public function __construct(ArchiveProcessor\Parameters $params)
99 {
100 $this->idArchive = false;
101 $this->idSite = $params->getSite()->getId();
102 $this->segment = $params->getSegment();
103 $this->period = $params->getPeriod();
104 $this->parameters = $params;
105
106 $idSites = array($this->idSite);
107 $this->doneFlag = Rules::getDoneStringFlagFor($idSites, $this->segment, $this->period->getLabel(), $params->getRequestedPlugin());
108
109 $this->dateStart = $this->period->getDateStart();
110 }
111
112 /**
113 * @param string $name
114 * @param string|string[] $values A blob string or an array of blob strings. If an array
115 * is used, the first element in the array will be inserted
116 * with the `$name` name. The others will be splitted into chunks. All subtables
117 * within one chunk will be serialized as an array where the index is the
118 * subtableId.
119 */
120 public function insertBlobRecord($name, $values)
121 {
122 if (is_array($values)) {
123
124 if (isset($values[0])) {
125 // we always store the root table in a single blob for fast access
126 $this->insertRecord($name, $this->compress($values[0]));
127 unset($values[0]);
128 }
129
130 if (!empty($values)) {
131 // we move all subtables into chunks
132 $chunk = new Chunk();
133 $chunks = $chunk->moveArchiveBlobsIntoChunks($name, $values);
134 foreach ($chunks as $index => $subtables) {
135 $this->insertRecord($index, $this->compress(serialize($subtables)));
136 }
137 }
138 } else {
139 $values = $this->compress($values);
140 $this->insertRecord($name, $values);
141 }
142 }
143
144 public function getIdArchive()
145 {
146 if ($this->idArchive === false) {
147 throw new Exception("Must call allocateNewArchiveId() first");
148 }
149
150 return $this->idArchive;
151 }
152
153 public function initNewArchive()
154 {
155 $idArchive = $this->allocateNewArchiveId();
156 $this->logArchiveStatusAsIncomplete();
157 return $idArchive;
158 }
159
160 public function finalizeArchive()
161 {
162 $this->flushSpools();
163
164 $numericTable = $this->getTableNumeric();
165 $idArchive = $this->getIdArchive();
166
167 $doneValue = $this->parameters->isPartialArchive() ? self::DONE_PARTIAL : self::DONE_OK;
168 $this->getModel()->updateArchiveStatus($numericTable, $idArchive, $this->doneFlag, $doneValue);
169
170 if (!$this->parameters->isPartialArchive()
171 // sanity check, just in case nothing was inserted (the archive status should always be inserted)
172 && !empty($this->earliestNow)
173 ) {
174 $this->getModel()->deleteOlderArchives($this->parameters, $this->doneFlag, $this->earliestNow, $this->idArchive);
175 }
176 }
177
178 protected function compress($data)
179 {
180 if (Db::get()->hasBlobDataType()) {
181 return gzcompress($data);
182 }
183
184 return $data;
185 }
186
187 protected function allocateNewArchiveId()
188 {
189 $numericTable = $this->getTableNumeric();
190
191 $this->idArchive = $this->getModel()->allocateNewArchiveId($numericTable);
192 return $this->idArchive;
193 }
194
195 private function getModel()
196 {
197 return new Model();
198 }
199
200 protected function logArchiveStatusAsIncomplete()
201 {
202 $this->insertRecord($this->doneFlag, self::DONE_ERROR);
203 }
204
205 private function batchInsertSpool($valueType)
206 {
207 $records = $this->recordsToWriteSpool[$valueType];
208
209 $bindSql = $this->getInsertRecordBind();
210 $values = array();
211
212 $valueSeen = false;
213 foreach ($records as $record) {
214 // don't record zero
215 if (empty($record[1])) {
216 continue;
217 }
218
219 $bind = $bindSql;
220 $bind[] = $record[0]; // name
221 $bind[] = $record[1]; // value
222 $values[] = $bind;
223
224 $valueSeen = $record[1];
225 }
226
227 if (empty($values)) {
228 return true;
229 }
230
231 $tableName = $this->getTableNameToInsert($valueSeen);
232 $fields = $this->getInsertFields();
233
234 // For numeric records it's faster to do the insert directly; for blobs the data infile is better
235 if ($valueType === 'numeric') {
236 BatchInsert::tableInsertBatchSql($tableName, $fields, $values);
237 } else {
238 BatchInsert::tableInsertBatch($tableName, $fields, $values, $throwException = false, $charset = 'latin1');
239 }
240
241 return true;
242 }
243
244 /**
245 * Inserts a record in the right table (either NUMERIC or BLOB)
246 *
247 * @param string $name
248 * @param mixed $value
249 *
250 * @return bool
251 */
252 public function insertRecord($name, $value)
253 {
254 if ($this->isRecordZero($value)) {
255 return false;
256 }
257
258 $valueType = $this->isRecordNumeric($value) ? 'numeric' : 'blob';
259 $this->recordsToWriteSpool[$valueType][] = array(
260 0 => $name,
261 1 => $value
262 );
263
264 if (count($this->recordsToWriteSpool[$valueType]) >= self::MAX_SPOOL_SIZE) {
265 $this->flushSpool($valueType);
266 }
267
268 return true;
269 }
270
271 public function flushSpools()
272 {
273 $this->flushSpool('numeric');
274 $this->flushSpool('blob');
275 }
276
277 private function flushSpool($valueType)
278 {
279 $numRecords = count($this->recordsToWriteSpool[$valueType]);
280
281 if ($numRecords > 1) {
282 $this->batchInsertSpool($valueType);
283 } elseif ($numRecords === 1) {
284 list($name, $value) = $this->recordsToWriteSpool[$valueType][0];
285 $tableName = $this->getTableNameToInsert($value);
286 $fields = $this->getInsertFields();
287 $record = $this->getInsertRecordBind();
288
289 $this->getModel()->insertRecord($tableName, $fields, $record, $name, $value);
290 }
291 $this->recordsToWriteSpool[$valueType] = array();
292 }
293
294 protected function getInsertRecordBind()
295 {
296 $now = Date::now()->getDatetime();
297 if (empty($this->earliestNow)) {
298 $this->earliestNow = $now;
299 }
300 return array($this->getIdArchive(),
301 $this->idSite,
302 $this->dateStart->toString('Y-m-d'),
303 $this->period->getDateEnd()->toString('Y-m-d'),
304 $this->period->getId(),
305 $now);
306 }
307
308 protected function getTableNameToInsert($value)
309 {
310 if ($this->isRecordNumeric($value)) {
311 return $this->getTableNumeric();
312 }
313
314 return ArchiveTableCreator::getBlobTable($this->dateStart);
315 }
316
317 protected function getTableNumeric()
318 {
319 return ArchiveTableCreator::getNumericTable($this->dateStart);
320 }
321
322 protected function getInsertFields()
323 {
324 return $this->fields;
325 }
326
327 protected function isRecordZero($value)
328 {
329 return ($value === '0' || $value === false || $value === 0 || $value === 0.0);
330 }
331
332 private function isRecordNumeric($value)
333 {
334 return is_numeric($value);
335 }
336 }
337