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