PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.0.3
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.0.3
5.13.0 5.12.1 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 2 years ago Actions.php 2 years ago ArchiveSelector.php 2 years ago ArchiveTableCreator.php 2 years ago ArchiveTableDao.php 2 years ago ArchiveWriter.php 2 years ago ArchivingDbAdapter.php 2 years ago LogAggregator.php 2 years ago LogQueryBuilder.php 2 years ago LogTableTemporary.php 2 years ago Model.php 2 years ago RawLogDao.php 2 years ago TableMetadata.php 2 years ago
ArchiveWriter.php
304 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 *
9 */
10 namespace Piwik\DataAccess;
11
12 use Exception;
13 use Piwik\Archive\Chunk;
14 use Piwik\ArchiveProcessor\Rules;
15 use Piwik\ArchiveProcessor;
16 use Piwik\Container\StaticContainer;
17 use Piwik\Date;
18 use Piwik\Db;
19 use Piwik\Db\BatchInsert;
20 use Piwik\Log\LoggerInterface;
21 /**
22 * This class is used to create a new Archive.
23 * An Archive is a set of reports (numeric and data tables).
24 * New data can be inserted in the archive with insertRecord/insertBulkRecords
25 */
26 class ArchiveWriter
27 {
28 /**
29 * Flag stored at the end of the archiving
30 *
31 * @var int
32 */
33 const DONE_OK = 1;
34 /**
35 * Flag stored at the start of the archiving
36 * When requesting an Archive, we make sure that non-finished archive are not considered valid
37 *
38 * @var int
39 */
40 const DONE_ERROR = 2;
41 /**
42 * Flag indicates the archive is over a period that is not finished, eg. the current day, current week, etc.
43 * Archives flagged will be regularly purged from the DB.
44 *
45 * This flag is deprecated, new archives should not be written as temporary.
46 *
47 * @var int
48 * @deprecated it should not be used anymore as temporary archives have been removed. It still exists though for
49 * historical reasons.
50 */
51 const DONE_OK_TEMPORARY = 3;
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 * Flag indicating that the archive is
60 *
61 * @var int
62 */
63 const DONE_PARTIAL = 5;
64 protected $fields = ['idarchive', 'idsite', 'date1', 'date2', 'period', 'ts_archived', 'name', 'value'];
65 private $recordsToWriteSpool = ['numeric' => [], 'blob' => []];
66 const MAX_SPOOL_SIZE = 50;
67 /**
68 * @var int|false
69 */
70 public $idArchive;
71 /**
72 * @var int|null
73 */
74 private $idSite;
75 /**
76 * @var \Piwik\Segment
77 */
78 private $segment;
79 /**
80 * @var \Piwik\Period
81 */
82 private $period;
83 /**
84 * @var ArchiveProcessor\Parameters
85 */
86 private $parameters;
87 /**
88 * @var string
89 */
90 private $earliestNow;
91 /**
92 * @var string
93 */
94 private $doneFlag;
95 /**
96 * @var Date|null
97 */
98 private $dateStart;
99 /**
100 * ArchiveWriter constructor.
101 * @param ArchiveProcessor\Parameters $params
102 * @param bool $isArchiveTemporary Deprecated. Has no effect.
103 * @throws Exception
104 */
105 public function __construct(ArchiveProcessor\Parameters $params)
106 {
107 $this->idArchive = false;
108 $this->idSite = $params->getSite()->getId();
109 $this->segment = $params->getSegment();
110 $this->period = $params->getPeriod();
111 $this->parameters = $params;
112 $idSites = [$this->idSite];
113 $this->doneFlag = Rules::getDoneStringFlagFor($idSites, $this->segment, $this->period->getLabel(), $params->getRequestedPlugin());
114 $this->dateStart = $this->period->getDateStart();
115 }
116 /**
117 * @param string $name
118 * @param string|string[] $values A blob string or an array of blob strings. If an array
119 * is used, the first element in the array will be inserted
120 * with the `$name` name. The others will be splitted into chunks. All subtables
121 * within one chunk will be serialized as an array where the index is the
122 * subtableId.
123 */
124 public function insertBlobRecord($name, $values)
125 {
126 if (is_array($values)) {
127 if (isset($values[0])) {
128 // we always store the root table in a single blob for fast access
129 $this->insertRecord($name, $this->compress($values[0]));
130 unset($values[0]);
131 }
132 if (!empty($values)) {
133 // we move all subtables into chunks
134 $chunk = new Chunk();
135 $chunks = $chunk->moveArchiveBlobsIntoChunks($name, $values);
136 foreach ($chunks as $index => $subtables) {
137 $this->insertRecord($index, $this->compress(serialize($subtables)));
138 }
139 }
140 } else {
141 $values = $this->compress($values);
142 $this->insertRecord($name, $values);
143 }
144 }
145 public function getIdArchive()
146 {
147 if ($this->idArchive === false) {
148 throw new Exception("Must call allocateNewArchiveId() first");
149 }
150 return $this->idArchive;
151 }
152 public function initNewArchive()
153 {
154 $idArchive = $this->allocateNewArchiveId();
155 $this->logArchiveStatusAsIncomplete();
156 return $idArchive;
157 }
158 public function finalizeArchive()
159 {
160 $this->flushSpools();
161 $numericTable = $this->getTableNumeric();
162 $idArchive = $this->getIdArchive();
163 $doneValue = $this->parameters->isPartialArchive() ? self::DONE_PARTIAL : self::DONE_OK;
164 $this->checkDoneValueIsOnlyPartialForPluginArchives($doneValue);
165 // check and log
166 $this->getModel()->updateArchiveStatus($numericTable, $idArchive, $this->doneFlag, $doneValue);
167 if (!$this->parameters->isPartialArchive() && !empty($this->earliestNow)) {
168 $this->getModel()->deleteOlderArchives($this->parameters, $this->doneFlag, $this->earliestNow, $this->idArchive);
169 }
170 }
171 protected function compress($data)
172 {
173 if (Db::get()->hasBlobDataType()) {
174 return gzcompress($data);
175 }
176 return $data;
177 }
178 protected function allocateNewArchiveId()
179 {
180 $numericTable = $this->getTableNumeric();
181 $this->idArchive = $this->getModel()->allocateNewArchiveId($numericTable);
182 return $this->idArchive;
183 }
184 private function getModel()
185 {
186 return new \Piwik\DataAccess\Model();
187 }
188 protected function logArchiveStatusAsIncomplete()
189 {
190 $this->insertRecord($this->doneFlag, self::DONE_ERROR);
191 }
192 private function batchInsertSpool($valueType)
193 {
194 $records = $this->recordsToWriteSpool[$valueType];
195 $bindSql = $this->getInsertRecordBind();
196 $values = [];
197 $valueSeen = false;
198 foreach ($records as $record) {
199 // don't record zero
200 if (empty($record[1])) {
201 continue;
202 }
203 $bind = $bindSql;
204 $bind[] = $record[0];
205 // name
206 $bind[] = $record[1];
207 // value
208 $values[] = $bind;
209 $valueSeen = $record[1];
210 }
211 if (empty($values)) {
212 return true;
213 }
214 $tableName = $this->getTableNameToInsert($valueSeen);
215 $fields = $this->getInsertFields();
216 // For numeric records it's faster to do the insert directly; for blobs the data infile is better
217 if ($valueType === 'numeric') {
218 BatchInsert::tableInsertBatchSql($tableName, $fields, $values);
219 } else {
220 BatchInsert::tableInsertBatch($tableName, $fields, $values, $throwException = false, $charset = 'latin1');
221 }
222 return true;
223 }
224 /**
225 * Inserts a record in the right table (either NUMERIC or BLOB)
226 *
227 * @param string $name
228 * @param mixed $value
229 *
230 * @return bool
231 */
232 public function insertRecord($name, $value)
233 {
234 if ($this->isRecordZero($value)) {
235 return false;
236 }
237 $valueType = $this->isRecordNumeric($value) ? 'numeric' : 'blob';
238 $this->recordsToWriteSpool[$valueType][] = [0 => $name, 1 => $value];
239 if (count($this->recordsToWriteSpool[$valueType]) >= self::MAX_SPOOL_SIZE) {
240 $this->flushSpool($valueType);
241 }
242 return true;
243 }
244 public function flushSpools()
245 {
246 $this->flushSpool('numeric');
247 $this->flushSpool('blob');
248 }
249 private function flushSpool($valueType)
250 {
251 $numRecords = count($this->recordsToWriteSpool[$valueType]);
252 if ($numRecords > 1) {
253 $this->batchInsertSpool($valueType);
254 } elseif ($numRecords === 1) {
255 [$name, $value] = $this->recordsToWriteSpool[$valueType][0];
256 $tableName = $this->getTableNameToInsert($value);
257 $fields = $this->getInsertFields();
258 $record = $this->getInsertRecordBind();
259 $this->getModel()->insertRecord($tableName, $fields, $record, $name, $value);
260 }
261 $this->recordsToWriteSpool[$valueType] = [];
262 }
263 protected function getInsertRecordBind()
264 {
265 $now = Date::now()->getDatetime();
266 if (empty($this->earliestNow)) {
267 $this->earliestNow = $now;
268 }
269 return [$this->getIdArchive(), $this->idSite, $this->dateStart->toString('Y-m-d'), $this->period->getDateEnd()->toString('Y-m-d'), $this->period->getId(), $now];
270 }
271 protected function getTableNameToInsert($value)
272 {
273 if ($this->isRecordNumeric($value)) {
274 return $this->getTableNumeric();
275 }
276 return \Piwik\DataAccess\ArchiveTableCreator::getBlobTable($this->dateStart);
277 }
278 protected function getTableNumeric()
279 {
280 return \Piwik\DataAccess\ArchiveTableCreator::getNumericTable($this->dateStart);
281 }
282 protected function getInsertFields()
283 {
284 return $this->fields;
285 }
286 protected function isRecordZero($value)
287 {
288 return $value === '0' || $value === false || $value === 0 || $value === 0.0;
289 }
290 private function isRecordNumeric($value)
291 {
292 return is_numeric($value);
293 }
294 private function checkDoneValueIsOnlyPartialForPluginArchives($doneValue)
295 {
296 // if the done flag is not like done%.PluginName, then it shouldn't be a partial archive.
297 // log a warning.
298 if ($doneValue == self::DONE_PARTIAL && strpos($this->doneFlag, '.') == false) {
299 $ex = new \Exception(sprintf("Trying to create a partial archive w/ an all plugins done flag (done flag = %s). This should not happen.", $this->doneFlag));
300 StaticContainer::get(LoggerInterface::class)->warning('{exception}', ['exception' => $ex]);
301 }
302 }
303 }
304