Passed
Push — master ( cdfad9...e39d65 )
by Joas
12:09 queued 10s
created
lib/private/Files/Cache/Cache.php 1 patch
Indentation   +985 added lines, -985 removed lines patch added patch discarded remove patch
@@ -62,989 +62,989 @@
 block discarded – undo
62 62
  * - ChangePropagator: updates the mtime and etags of parent folders whenever a change to the cache is made to the cache by the updater
63 63
  */
64 64
 class Cache implements ICache {
65
-	use MoveFromCacheTrait {
66
-		MoveFromCacheTrait::moveFromCache as moveFromCacheFallback;
67
-	}
68
-
69
-	/**
70
-	 * @var array partial data for the cache
71
-	 */
72
-	protected $partial = [];
73
-
74
-	/**
75
-	 * @var string
76
-	 */
77
-	protected $storageId;
78
-
79
-	private $storage;
80
-
81
-	/**
82
-	 * @var Storage $storageCache
83
-	 */
84
-	protected $storageCache;
85
-
86
-	/** @var IMimeTypeLoader */
87
-	protected $mimetypeLoader;
88
-
89
-	/**
90
-	 * @var IDBConnection
91
-	 */
92
-	protected $connection;
93
-
94
-	protected $eventDispatcher;
95
-
96
-	/** @var QuerySearchHelper */
97
-	protected $querySearchHelper;
98
-
99
-	/**
100
-	 * @param IStorage $storage
101
-	 */
102
-	public function __construct(IStorage $storage) {
103
-		$this->storageId = $storage->getId();
104
-		$this->storage = $storage;
105
-		if (strlen($this->storageId) > 64) {
106
-			$this->storageId = md5($this->storageId);
107
-		}
108
-
109
-		$this->storageCache = new Storage($storage);
110
-		$this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
111
-		$this->connection = \OC::$server->getDatabaseConnection();
112
-		$this->eventDispatcher = \OC::$server->getEventDispatcher();
113
-		$this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader);
114
-	}
115
-
116
-	private function getQueryBuilder() {
117
-		return new CacheQueryBuilder(
118
-			$this->connection,
119
-			\OC::$server->getSystemConfig(),
120
-			\OC::$server->getLogger(),
121
-			$this
122
-		);
123
-	}
124
-
125
-	/**
126
-	 * Get the numeric storage id for this cache's storage
127
-	 *
128
-	 * @return int
129
-	 */
130
-	public function getNumericStorageId() {
131
-		return $this->storageCache->getNumericId();
132
-	}
133
-
134
-	/**
135
-	 * get the stored metadata of a file or folder
136
-	 *
137
-	 * @param string | int $file either the path of a file or folder or the file id for a file or folder
138
-	 * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache
139
-	 */
140
-	public function get($file) {
141
-		$query = $this->getQueryBuilder();
142
-		$query->selectFileCache();
143
-
144
-		if (is_string($file) or $file == '') {
145
-			// normalize file
146
-			$file = $this->normalize($file);
147
-
148
-			$query->whereStorageId()
149
-				->wherePath($file);
150
-		} else { //file id
151
-			$query->whereFileId($file);
152
-		}
153
-
154
-		$result = $query->execute();
155
-		$data = $result->fetch();
156
-		$result->closeCursor();
157
-
158
-		//merge partial data
159
-		if (!$data and is_string($file) and isset($this->partial[$file])) {
160
-			return $this->partial[$file];
161
-		} elseif (!$data) {
162
-			return $data;
163
-		} else {
164
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
165
-		}
166
-	}
167
-
168
-	/**
169
-	 * Create a CacheEntry from database row
170
-	 *
171
-	 * @param array $data
172
-	 * @param IMimeTypeLoader $mimetypeLoader
173
-	 * @return CacheEntry
174
-	 */
175
-	public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) {
176
-		//fix types
177
-		$data['fileid'] = (int)$data['fileid'];
178
-		$data['parent'] = (int)$data['parent'];
179
-		$data['size'] = 0 + $data['size'];
180
-		$data['mtime'] = (int)$data['mtime'];
181
-		$data['storage_mtime'] = (int)$data['storage_mtime'];
182
-		$data['encryptedVersion'] = (int)$data['encrypted'];
183
-		$data['encrypted'] = (bool)$data['encrypted'];
184
-		$data['storage_id'] = $data['storage'];
185
-		$data['storage'] = (int)$data['storage'];
186
-		$data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']);
187
-		$data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']);
188
-		if ($data['storage_mtime'] == 0) {
189
-			$data['storage_mtime'] = $data['mtime'];
190
-		}
191
-		$data['permissions'] = (int)$data['permissions'];
192
-		if (isset($data['creation_time'])) {
193
-			$data['creation_time'] = (int) $data['creation_time'];
194
-		}
195
-		if (isset($data['upload_time'])) {
196
-			$data['upload_time'] = (int) $data['upload_time'];
197
-		}
198
-		return new CacheEntry($data);
199
-	}
200
-
201
-	/**
202
-	 * get the metadata of all files stored in $folder
203
-	 *
204
-	 * @param string $folder
205
-	 * @return ICacheEntry[]
206
-	 */
207
-	public function getFolderContents($folder) {
208
-		$fileId = $this->getId($folder);
209
-		return $this->getFolderContentsById($fileId);
210
-	}
211
-
212
-	/**
213
-	 * get the metadata of all files stored in $folder
214
-	 *
215
-	 * @param int $fileId the file id of the folder
216
-	 * @return ICacheEntry[]
217
-	 */
218
-	public function getFolderContentsById($fileId) {
219
-		if ($fileId > -1) {
220
-			$query = $this->getQueryBuilder();
221
-			$query->selectFileCache()
222
-				->whereParent($fileId)
223
-				->orderBy('name', 'ASC');
224
-
225
-			$result = $query->execute();
226
-			$files = $result->fetchAll();
227
-			$result->closeCursor();
228
-
229
-			return array_map(function (array $data) {
230
-				return self::cacheEntryFromData($data, $this->mimetypeLoader);
231
-			}, $files);
232
-		}
233
-		return [];
234
-	}
235
-
236
-	/**
237
-	 * insert or update meta data for a file or folder
238
-	 *
239
-	 * @param string $file
240
-	 * @param array $data
241
-	 *
242
-	 * @return int file id
243
-	 * @throws \RuntimeException
244
-	 */
245
-	public function put($file, array $data) {
246
-		if (($id = $this->getId($file)) > -1) {
247
-			$this->update($id, $data);
248
-			return $id;
249
-		} else {
250
-			return $this->insert($file, $data);
251
-		}
252
-	}
253
-
254
-	/**
255
-	 * insert meta data for a new file or folder
256
-	 *
257
-	 * @param string $file
258
-	 * @param array $data
259
-	 *
260
-	 * @return int file id
261
-	 * @throws \RuntimeException
262
-	 */
263
-	public function insert($file, array $data) {
264
-		// normalize file
265
-		$file = $this->normalize($file);
266
-
267
-		if (isset($this->partial[$file])) { //add any saved partial data
268
-			$data = array_merge($this->partial[$file], $data);
269
-			unset($this->partial[$file]);
270
-		}
271
-
272
-		$requiredFields = ['size', 'mtime', 'mimetype'];
273
-		foreach ($requiredFields as $field) {
274
-			if (!isset($data[$field])) { //data not complete save as partial and return
275
-				$this->partial[$file] = $data;
276
-				return -1;
277
-			}
278
-		}
279
-
280
-		$data['path'] = $file;
281
-		if (!isset($data['parent'])) {
282
-			$data['parent'] = $this->getParentId($file);
283
-		}
284
-		$data['name'] = basename($file);
285
-
286
-		[$values, $extensionValues] = $this->normalizeData($data);
287
-		$values['storage'] = $this->getNumericStorageId();
288
-
289
-		try {
290
-			$builder = $this->connection->getQueryBuilder();
291
-			$builder->insert('filecache');
292
-
293
-			foreach ($values as $column => $value) {
294
-				$builder->setValue($column, $builder->createNamedParameter($value));
295
-			}
296
-
297
-			if ($builder->execute()) {
298
-				$fileId = $builder->getLastInsertId();
299
-
300
-				if (count($extensionValues)) {
301
-					$query = $this->getQueryBuilder();
302
-					$query->insert('filecache_extended');
303
-
304
-					$query->setValue('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT));
305
-					foreach ($extensionValues as $column => $value) {
306
-						$query->setValue($column, $query->createNamedParameter($value));
307
-					}
308
-					$query->execute();
309
-				}
310
-
311
-				$this->eventDispatcher->dispatch(CacheInsertEvent::class, new CacheInsertEvent($this->storage, $file, $fileId));
312
-				return $fileId;
313
-			}
314
-		} catch (UniqueConstraintViolationException $e) {
315
-			// entry exists already
316
-			if ($this->connection->inTransaction()) {
317
-				$this->connection->commit();
318
-				$this->connection->beginTransaction();
319
-			}
320
-		}
321
-
322
-		// The file was created in the mean time
323
-		if (($id = $this->getId($file)) > -1) {
324
-			$this->update($id, $data);
325
-			return $id;
326
-		} else {
327
-			throw new \RuntimeException('File entry could not be inserted but could also not be selected with getId() in order to perform an update. Please try again.');
328
-		}
329
-	}
330
-
331
-	/**
332
-	 * update the metadata of an existing file or folder in the cache
333
-	 *
334
-	 * @param int $id the fileid of the existing file or folder
335
-	 * @param array $data [$key => $value] the metadata to update, only the fields provided in the array will be updated, non-provided values will remain unchanged
336
-	 */
337
-	public function update($id, array $data) {
338
-		if (isset($data['path'])) {
339
-			// normalize path
340
-			$data['path'] = $this->normalize($data['path']);
341
-		}
342
-
343
-		if (isset($data['name'])) {
344
-			// normalize path
345
-			$data['name'] = $this->normalize($data['name']);
346
-		}
347
-
348
-		[$values, $extensionValues] = $this->normalizeData($data);
349
-
350
-		if (count($values)) {
351
-			$query = $this->getQueryBuilder();
352
-
353
-			$query->update('filecache')
354
-				->whereFileId($id)
355
-				->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
356
-					return $query->expr()->orX(
357
-						$query->expr()->neq($key, $query->createNamedParameter($value)),
358
-						$query->expr()->isNull($key)
359
-					);
360
-				}, array_keys($values), array_values($values))));
361
-
362
-			foreach ($values as $key => $value) {
363
-				$query->set($key, $query->createNamedParameter($value));
364
-			}
365
-
366
-			$query->execute();
367
-		}
368
-
369
-		if (count($extensionValues)) {
370
-			try {
371
-				$query = $this->getQueryBuilder();
372
-				$query->insert('filecache_extended');
373
-
374
-				$query->setValue('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT));
375
-				foreach ($extensionValues as $column => $value) {
376
-					$query->setValue($column, $query->createNamedParameter($value));
377
-				}
378
-
379
-				$query->execute();
380
-			} catch (UniqueConstraintViolationException $e) {
381
-				$query = $this->getQueryBuilder();
382
-				$query->update('filecache_extended')
383
-					->whereFileId($id)
384
-					->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
385
-						return $query->expr()->orX(
386
-							$query->expr()->neq($key, $query->createNamedParameter($value)),
387
-							$query->expr()->isNull($key)
388
-						);
389
-					}, array_keys($extensionValues), array_values($extensionValues))));
390
-
391
-				foreach ($extensionValues as $key => $value) {
392
-					$query->set($key, $query->createNamedParameter($value));
393
-				}
394
-
395
-				$query->execute();
396
-			}
397
-		}
398
-
399
-		$path = $this->getPathById($id);
400
-		// path can still be null if the file doesn't exist
401
-		if ($path !== null) {
402
-			$this->eventDispatcher->dispatch(CacheUpdateEvent::class, new CacheUpdateEvent($this->storage, $path, $id));
403
-		}
404
-	}
405
-
406
-	/**
407
-	 * extract query parts and params array from data array
408
-	 *
409
-	 * @param array $data
410
-	 * @return array
411
-	 */
412
-	protected function normalizeData(array $data): array {
413
-		$fields = [
414
-			'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted',
415
-			'etag', 'permissions', 'checksum', 'storage'];
416
-		$extensionFields = ['metadata_etag', 'creation_time', 'upload_time'];
417
-
418
-		$doNotCopyStorageMTime = false;
419
-		if (array_key_exists('mtime', $data) && $data['mtime'] === null) {
420
-			// this horrific magic tells it to not copy storage_mtime to mtime
421
-			unset($data['mtime']);
422
-			$doNotCopyStorageMTime = true;
423
-		}
424
-
425
-		$params = [];
426
-		$extensionParams = [];
427
-		foreach ($data as $name => $value) {
428
-			if (array_search($name, $fields) !== false) {
429
-				if ($name === 'path') {
430
-					$params['path_hash'] = md5($value);
431
-				} elseif ($name === 'mimetype') {
432
-					$params['mimepart'] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/')));
433
-					$value = $this->mimetypeLoader->getId($value);
434
-				} elseif ($name === 'storage_mtime') {
435
-					if (!$doNotCopyStorageMTime && !isset($data['mtime'])) {
436
-						$params['mtime'] = $value;
437
-					}
438
-				} elseif ($name === 'encrypted') {
439
-					if (isset($data['encryptedVersion'])) {
440
-						$value = $data['encryptedVersion'];
441
-					} else {
442
-						// Boolean to integer conversion
443
-						$value = $value ? 1 : 0;
444
-					}
445
-				}
446
-				$params[$name] = $value;
447
-			}
448
-			if (array_search($name, $extensionFields) !== false) {
449
-				$extensionParams[$name] = $value;
450
-			}
451
-		}
452
-		return [$params, array_filter($extensionParams)];
453
-	}
454
-
455
-	/**
456
-	 * get the file id for a file
457
-	 *
458
-	 * A file id is a numeric id for a file or folder that's unique within an owncloud instance which stays the same for the lifetime of a file
459
-	 *
460
-	 * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing
461
-	 *
462
-	 * @param string $file
463
-	 * @return int
464
-	 */
465
-	public function getId($file) {
466
-		// normalize file
467
-		$file = $this->normalize($file);
468
-
469
-		$query = $this->getQueryBuilder();
470
-		$query->select('fileid')
471
-			->from('filecache')
472
-			->whereStorageId()
473
-			->wherePath($file);
474
-
475
-		$result = $query->execute();
476
-		$id = $result->fetchColumn();
477
-		$result->closeCursor();
478
-
479
-		return $id === false ? -1 : (int)$id;
480
-	}
481
-
482
-	/**
483
-	 * get the id of the parent folder of a file
484
-	 *
485
-	 * @param string $file
486
-	 * @return int
487
-	 */
488
-	public function getParentId($file) {
489
-		if ($file === '') {
490
-			return -1;
491
-		} else {
492
-			$parent = $this->getParentPath($file);
493
-			return (int)$this->getId($parent);
494
-		}
495
-	}
496
-
497
-	private function getParentPath($path) {
498
-		$parent = dirname($path);
499
-		if ($parent === '.') {
500
-			$parent = '';
501
-		}
502
-		return $parent;
503
-	}
504
-
505
-	/**
506
-	 * check if a file is available in the cache
507
-	 *
508
-	 * @param string $file
509
-	 * @return bool
510
-	 */
511
-	public function inCache($file) {
512
-		return $this->getId($file) != -1;
513
-	}
514
-
515
-	/**
516
-	 * remove a file or folder from the cache
517
-	 *
518
-	 * when removing a folder from the cache all files and folders inside the folder will be removed as well
519
-	 *
520
-	 * @param string $file
521
-	 */
522
-	public function remove($file) {
523
-		$entry = $this->get($file);
524
-
525
-		if ($entry) {
526
-			$query = $this->getQueryBuilder();
527
-			$query->delete('filecache')
528
-				->whereFileId($entry->getId());
529
-			$query->execute();
530
-
531
-			$query = $this->getQueryBuilder();
532
-			$query->delete('filecache_extended')
533
-				->whereFileId($entry->getId());
534
-			$query->execute();
535
-
536
-			if ($entry->getMimeType() == FileInfo::MIMETYPE_FOLDER) {
537
-				$this->removeChildren($entry);
538
-			}
539
-		}
540
-	}
541
-
542
-	/**
543
-	 * Get all sub folders of a folder
544
-	 *
545
-	 * @param ICacheEntry $entry the cache entry of the folder to get the subfolders for
546
-	 * @return ICacheEntry[] the cache entries for the subfolders
547
-	 */
548
-	private function getSubFolders(ICacheEntry $entry) {
549
-		$children = $this->getFolderContentsById($entry->getId());
550
-		return array_filter($children, function ($child) {
551
-			return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
552
-		});
553
-	}
554
-
555
-	/**
556
-	 * Recursively remove all children of a folder
557
-	 *
558
-	 * @param ICacheEntry $entry the cache entry of the folder to remove the children of
559
-	 * @throws \OC\DatabaseException
560
-	 */
561
-	private function removeChildren(ICacheEntry $entry) {
562
-		$parentIds = [$entry->getId()];
563
-		$queue = [$entry->getId()];
564
-
565
-		// we walk depth first trough the file tree, removing all filecache_extended attributes while we walk
566
-		// and collecting all folder ids to later use to delete the filecache entries
567
-		while ($entryId = array_pop($queue)) {
568
-			$children = $this->getFolderContentsById($entryId);
569
-			$childIds = array_map(function (ICacheEntry $cacheEntry) {
570
-				return $cacheEntry->getId();
571
-			}, $children);
572
-
573
-			$query = $this->getQueryBuilder();
574
-			$query->delete('filecache_extended')
575
-				->where($query->expr()->in('fileid', $query->createNamedParameter($childIds, IQueryBuilder::PARAM_INT_ARRAY)));
576
-			$query->execute();
577
-
578
-			/** @var ICacheEntry[] $childFolders */
579
-			$childFolders = array_filter($children, function ($child) {
580
-				return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
581
-			});
582
-			foreach ($childFolders as $folder) {
583
-				$parentIds[] = $folder->getId();
584
-				$queue[] = $folder->getId();
585
-			}
586
-		}
587
-
588
-		$query = $this->getQueryBuilder();
589
-		$query->delete('filecache')
590
-			->whereParentIn($parentIds);
591
-		$query->execute();
592
-	}
593
-
594
-	/**
595
-	 * Move a file or folder in the cache
596
-	 *
597
-	 * @param string $source
598
-	 * @param string $target
599
-	 */
600
-	public function move($source, $target) {
601
-		$this->moveFromCache($this, $source, $target);
602
-	}
603
-
604
-	/**
605
-	 * Get the storage id and path needed for a move
606
-	 *
607
-	 * @param string $path
608
-	 * @return array [$storageId, $internalPath]
609
-	 */
610
-	protected function getMoveInfo($path) {
611
-		return [$this->getNumericStorageId(), $path];
612
-	}
613
-
614
-	/**
615
-	 * Move a file or folder in the cache
616
-	 *
617
-	 * @param \OCP\Files\Cache\ICache $sourceCache
618
-	 * @param string $sourcePath
619
-	 * @param string $targetPath
620
-	 * @throws \OC\DatabaseException
621
-	 * @throws \Exception if the given storages have an invalid id
622
-	 */
623
-	public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
624
-		if ($sourceCache instanceof Cache) {
625
-			// normalize source and target
626
-			$sourcePath = $this->normalize($sourcePath);
627
-			$targetPath = $this->normalize($targetPath);
628
-
629
-			$sourceData = $sourceCache->get($sourcePath);
630
-			$sourceId = $sourceData['fileid'];
631
-			$newParentId = $this->getParentId($targetPath);
632
-
633
-			[$sourceStorageId, $sourcePath] = $sourceCache->getMoveInfo($sourcePath);
634
-			[$targetStorageId, $targetPath] = $this->getMoveInfo($targetPath);
635
-
636
-			if (is_null($sourceStorageId) || $sourceStorageId === false) {
637
-				throw new \Exception('Invalid source storage id: ' . $sourceStorageId);
638
-			}
639
-			if (is_null($targetStorageId) || $targetStorageId === false) {
640
-				throw new \Exception('Invalid target storage id: ' . $targetStorageId);
641
-			}
642
-
643
-			$this->connection->beginTransaction();
644
-			if ($sourceData['mimetype'] === 'httpd/unix-directory') {
645
-				//update all child entries
646
-				$sourceLength = mb_strlen($sourcePath);
647
-				$query = $this->connection->getQueryBuilder();
648
-
649
-				$fun = $query->func();
650
-				$newPathFunction = $fun->concat(
651
-					$query->createNamedParameter($targetPath),
652
-					$fun->substring('path', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash
653
-				);
654
-				$query->update('filecache')
655
-					->set('storage', $query->createNamedParameter($targetStorageId, IQueryBuilder::PARAM_INT))
656
-					->set('path_hash', $fun->md5($newPathFunction))
657
-					->set('path', $newPathFunction)
658
-					->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT)))
659
-					->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%')));
660
-
661
-				try {
662
-					$query->execute();
663
-				} catch (\OC\DatabaseException $e) {
664
-					$this->connection->rollBack();
665
-					throw $e;
666
-				}
667
-			}
668
-
669
-			$query = $this->getQueryBuilder();
670
-			$query->update('filecache')
671
-				->set('storage', $query->createNamedParameter($targetStorageId))
672
-				->set('path', $query->createNamedParameter($targetPath))
673
-				->set('path_hash', $query->createNamedParameter(md5($targetPath)))
674
-				->set('name', $query->createNamedParameter(basename($targetPath)))
675
-				->set('parent', $query->createNamedParameter($newParentId, IQueryBuilder::PARAM_INT))
676
-				->whereFileId($sourceId);
677
-			$query->execute();
678
-
679
-			$this->connection->commit();
680
-		} else {
681
-			$this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
682
-		}
683
-	}
684
-
685
-	/**
686
-	 * remove all entries for files that are stored on the storage from the cache
687
-	 */
688
-	public function clear() {
689
-		$query = $this->getQueryBuilder();
690
-		$query->delete('filecache')
691
-			->whereStorageId();
692
-		$query->execute();
693
-
694
-		$query = $this->connection->getQueryBuilder();
695
-		$query->delete('storages')
696
-			->where($query->expr()->eq('id', $query->createNamedParameter($this->storageId)));
697
-		$query->execute();
698
-	}
699
-
700
-	/**
701
-	 * Get the scan status of a file
702
-	 *
703
-	 * - Cache::NOT_FOUND: File is not in the cache
704
-	 * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known
705
-	 * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned
706
-	 * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned
707
-	 *
708
-	 * @param string $file
709
-	 *
710
-	 * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
711
-	 */
712
-	public function getStatus($file) {
713
-		// normalize file
714
-		$file = $this->normalize($file);
715
-
716
-		$query = $this->getQueryBuilder();
717
-		$query->select('size')
718
-			->from('filecache')
719
-			->whereStorageId()
720
-			->wherePath($file);
721
-
722
-		$result = $query->execute();
723
-		$size = $result->fetchColumn();
724
-		$result->closeCursor();
725
-
726
-		if ($size !== false) {
727
-			if ((int)$size === -1) {
728
-				return self::SHALLOW;
729
-			} else {
730
-				return self::COMPLETE;
731
-			}
732
-		} else {
733
-			if (isset($this->partial[$file])) {
734
-				return self::PARTIAL;
735
-			} else {
736
-				return self::NOT_FOUND;
737
-			}
738
-		}
739
-	}
740
-
741
-	/**
742
-	 * search for files matching $pattern
743
-	 *
744
-	 * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%')
745
-	 * @return ICacheEntry[] an array of cache entries where the name matches the search pattern
746
-	 */
747
-	public function search($pattern) {
748
-		// normalize pattern
749
-		$pattern = $this->normalize($pattern);
750
-
751
-		if ($pattern === '%%') {
752
-			return [];
753
-		}
754
-
755
-		$query = $this->getQueryBuilder();
756
-		$query->selectFileCache()
757
-			->whereStorageId()
758
-			->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
759
-
760
-		$result = $query->execute();
761
-		$files = $result->fetchAll();
762
-		$result->closeCursor();
763
-
764
-		return array_map(function (array $data) {
765
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
766
-		}, $files);
767
-	}
768
-
769
-	/**
770
-	 * @param Statement $result
771
-	 * @return CacheEntry[]
772
-	 */
773
-	private function searchResultToCacheEntries(Statement $result) {
774
-		$files = $result->fetchAll();
775
-
776
-		return array_map(function (array $data) {
777
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
778
-		}, $files);
779
-	}
780
-
781
-	/**
782
-	 * search for files by mimetype
783
-	 *
784
-	 * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
785
-	 *        where it will search for all mimetypes in the group ('image/*')
786
-	 * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
787
-	 */
788
-	public function searchByMime($mimetype) {
789
-		$mimeId = $this->mimetypeLoader->getId($mimetype);
790
-
791
-		$query = $this->getQueryBuilder();
792
-		$query->selectFileCache()
793
-			->whereStorageId();
794
-
795
-		if (strpos($mimetype, '/')) {
796
-			$query->andWhere($query->expr()->eq('mimetype', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT)));
797
-		} else {
798
-			$query->andWhere($query->expr()->eq('mimepart', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT)));
799
-		}
800
-
801
-		$result = $query->execute();
802
-		$files = $result->fetchAll();
803
-		$result->closeCursor();
804
-
805
-		return array_map(function (array $data) {
806
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
807
-		}, $files);
808
-	}
809
-
810
-	public function searchQuery(ISearchQuery $searchQuery) {
811
-		$builder = $this->getQueryBuilder();
812
-
813
-		$query = $builder->selectFileCache('file');
814
-
815
-		$query->whereStorageId();
816
-
817
-		if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) {
818
-			$query
819
-				->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
820
-				->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
821
-					$builder->expr()->eq('tagmap.type', 'tag.type'),
822
-					$builder->expr()->eq('tagmap.categoryid', 'tag.id')
823
-				))
824
-				->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files')))
825
-				->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID())));
826
-		}
827
-
828
-		$searchExpr = $this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation());
829
-		if ($searchExpr) {
830
-			$query->andWhere($searchExpr);
831
-		}
832
-
833
-		if ($searchQuery->limitToHome() && ($this instanceof HomeCache)) {
834
-			$query->andWhere($builder->expr()->like('path', $query->expr()->literal('files/%')));
835
-		}
836
-
837
-		$this->querySearchHelper->addSearchOrdersToQuery($query, $searchQuery->getOrder());
838
-
839
-		if ($searchQuery->getLimit()) {
840
-			$query->setMaxResults($searchQuery->getLimit());
841
-		}
842
-		if ($searchQuery->getOffset()) {
843
-			$query->setFirstResult($searchQuery->getOffset());
844
-		}
845
-
846
-		$result = $query->execute();
847
-		return $this->searchResultToCacheEntries($result);
848
-	}
849
-
850
-	/**
851
-	 * Re-calculate the folder size and the size of all parent folders
852
-	 *
853
-	 * @param string|boolean $path
854
-	 * @param array $data (optional) meta data of the folder
855
-	 */
856
-	public function correctFolderSize($path, $data = null, $isBackgroundScan = false) {
857
-		$this->calculateFolderSize($path, $data);
858
-		if ($path !== '') {
859
-			$parent = dirname($path);
860
-			if ($parent === '.' or $parent === '/') {
861
-				$parent = '';
862
-			}
863
-			if ($isBackgroundScan) {
864
-				$parentData = $this->get($parent);
865
-				if ($parentData['size'] !== -1 && $this->getIncompleteChildrenCount($parentData['fileid']) === 0) {
866
-					$this->correctFolderSize($parent, $parentData, $isBackgroundScan);
867
-				}
868
-			} else {
869
-				$this->correctFolderSize($parent);
870
-			}
871
-		}
872
-	}
873
-
874
-	/**
875
-	 * get the incomplete count that shares parent $folder
876
-	 *
877
-	 * @param int $fileId the file id of the folder
878
-	 * @return int
879
-	 */
880
-	public function getIncompleteChildrenCount($fileId) {
881
-		if ($fileId > -1) {
882
-			$query = $this->getQueryBuilder();
883
-			$query->select($query->func()->count())
884
-				->from('filecache')
885
-				->whereParent($fileId)
886
-				->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
887
-
888
-			$result = $query->execute();
889
-			$size = (int)$result->fetchColumn();
890
-			$result->closeCursor();
891
-
892
-			return $size;
893
-		}
894
-		return -1;
895
-	}
896
-
897
-	/**
898
-	 * calculate the size of a folder and set it in the cache
899
-	 *
900
-	 * @param string $path
901
-	 * @param array $entry (optional) meta data of the folder
902
-	 * @return int
903
-	 */
904
-	public function calculateFolderSize($path, $entry = null) {
905
-		$totalSize = 0;
906
-		if (is_null($entry) or !isset($entry['fileid'])) {
907
-			$entry = $this->get($path);
908
-		}
909
-		if (isset($entry['mimetype']) && $entry['mimetype'] === FileInfo::MIMETYPE_FOLDER) {
910
-			$id = $entry['fileid'];
911
-
912
-			$query = $this->getQueryBuilder();
913
-			$query->selectAlias($query->func()->sum('size'), 'f1')
914
-				->selectAlias($query->func()->min('size'), 'f2')
915
-				->from('filecache')
916
-				->whereStorageId()
917
-				->whereParent($id);
918
-
919
-			$result = $query->execute();
920
-			$row = $result->fetch();
921
-			$result->closeCursor();
922
-
923
-			if ($row) {
924
-				[$sum, $min] = array_values($row);
925
-				$sum = 0 + $sum;
926
-				$min = 0 + $min;
927
-				if ($min === -1) {
928
-					$totalSize = $min;
929
-				} else {
930
-					$totalSize = $sum;
931
-				}
932
-				if ($entry['size'] !== $totalSize) {
933
-					$this->update($id, ['size' => $totalSize]);
934
-				}
935
-			}
936
-		}
937
-		return $totalSize;
938
-	}
939
-
940
-	/**
941
-	 * get all file ids on the files on the storage
942
-	 *
943
-	 * @return int[]
944
-	 */
945
-	public function getAll() {
946
-		$query = $this->getQueryBuilder();
947
-		$query->select('fileid')
948
-			->from('filecache')
949
-			->whereStorageId();
950
-
951
-		$result = $query->execute();
952
-		$files = $result->fetchAll(\PDO::FETCH_COLUMN);
953
-		$result->closeCursor();
954
-
955
-		return array_map(function ($id) {
956
-			return (int)$id;
957
-		}, $files);
958
-	}
959
-
960
-	/**
961
-	 * find a folder in the cache which has not been fully scanned
962
-	 *
963
-	 * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
964
-	 * use the one with the highest id gives the best result with the background scanner, since that is most
965
-	 * likely the folder where we stopped scanning previously
966
-	 *
967
-	 * @return string|bool the path of the folder or false when no folder matched
968
-	 */
969
-	public function getIncomplete() {
970
-		$query = $this->getQueryBuilder();
971
-		$query->select('path')
972
-			->from('filecache')
973
-			->whereStorageId()
974
-			->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
975
-			->orderBy('fileid', 'DESC');
976
-
977
-		$result = $query->execute();
978
-		$path = $result->fetchColumn();
979
-		$result->closeCursor();
980
-
981
-		return $path;
982
-	}
983
-
984
-	/**
985
-	 * get the path of a file on this storage by it's file id
986
-	 *
987
-	 * @param int $id the file id of the file or folder to search
988
-	 * @return string|null the path of the file (relative to the storage) or null if a file with the given id does not exists within this cache
989
-	 */
990
-	public function getPathById($id) {
991
-		$query = $this->getQueryBuilder();
992
-		$query->select('path')
993
-			->from('filecache')
994
-			->whereStorageId()
995
-			->whereFileId($id);
996
-
997
-		$result = $query->execute();
998
-		$path = $result->fetchColumn();
999
-		$result->closeCursor();
1000
-
1001
-		if ($path === false) {
1002
-			return null;
1003
-		}
1004
-
1005
-		return (string) $path;
1006
-	}
1007
-
1008
-	/**
1009
-	 * get the storage id of the storage for a file and the internal path of the file
1010
-	 * unlike getPathById this does not limit the search to files on this storage and
1011
-	 * instead does a global search in the cache table
1012
-	 *
1013
-	 * @param int $id
1014
-	 * @return array first element holding the storage id, second the path
1015
-	 * @deprecated use getPathById() instead
1016
-	 */
1017
-	public static function getById($id) {
1018
-		$query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
1019
-		$query->select('path', 'storage')
1020
-			->from('filecache')
1021
-			->where($query->expr()->eq('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
1022
-
1023
-		$result = $query->execute();
1024
-		$row = $result->fetch();
1025
-		$result->closeCursor();
1026
-
1027
-		if ($row) {
1028
-			$numericId = $row['storage'];
1029
-			$path = $row['path'];
1030
-		} else {
1031
-			return null;
1032
-		}
1033
-
1034
-		if ($id = Storage::getStorageId($numericId)) {
1035
-			return [$id, $path];
1036
-		} else {
1037
-			return null;
1038
-		}
1039
-	}
1040
-
1041
-	/**
1042
-	 * normalize the given path
1043
-	 *
1044
-	 * @param string $path
1045
-	 * @return string
1046
-	 */
1047
-	public function normalize($path) {
1048
-		return trim(\OC_Util::normalizeUnicode($path), '/');
1049
-	}
65
+    use MoveFromCacheTrait {
66
+        MoveFromCacheTrait::moveFromCache as moveFromCacheFallback;
67
+    }
68
+
69
+    /**
70
+     * @var array partial data for the cache
71
+     */
72
+    protected $partial = [];
73
+
74
+    /**
75
+     * @var string
76
+     */
77
+    protected $storageId;
78
+
79
+    private $storage;
80
+
81
+    /**
82
+     * @var Storage $storageCache
83
+     */
84
+    protected $storageCache;
85
+
86
+    /** @var IMimeTypeLoader */
87
+    protected $mimetypeLoader;
88
+
89
+    /**
90
+     * @var IDBConnection
91
+     */
92
+    protected $connection;
93
+
94
+    protected $eventDispatcher;
95
+
96
+    /** @var QuerySearchHelper */
97
+    protected $querySearchHelper;
98
+
99
+    /**
100
+     * @param IStorage $storage
101
+     */
102
+    public function __construct(IStorage $storage) {
103
+        $this->storageId = $storage->getId();
104
+        $this->storage = $storage;
105
+        if (strlen($this->storageId) > 64) {
106
+            $this->storageId = md5($this->storageId);
107
+        }
108
+
109
+        $this->storageCache = new Storage($storage);
110
+        $this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
111
+        $this->connection = \OC::$server->getDatabaseConnection();
112
+        $this->eventDispatcher = \OC::$server->getEventDispatcher();
113
+        $this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader);
114
+    }
115
+
116
+    private function getQueryBuilder() {
117
+        return new CacheQueryBuilder(
118
+            $this->connection,
119
+            \OC::$server->getSystemConfig(),
120
+            \OC::$server->getLogger(),
121
+            $this
122
+        );
123
+    }
124
+
125
+    /**
126
+     * Get the numeric storage id for this cache's storage
127
+     *
128
+     * @return int
129
+     */
130
+    public function getNumericStorageId() {
131
+        return $this->storageCache->getNumericId();
132
+    }
133
+
134
+    /**
135
+     * get the stored metadata of a file or folder
136
+     *
137
+     * @param string | int $file either the path of a file or folder or the file id for a file or folder
138
+     * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache
139
+     */
140
+    public function get($file) {
141
+        $query = $this->getQueryBuilder();
142
+        $query->selectFileCache();
143
+
144
+        if (is_string($file) or $file == '') {
145
+            // normalize file
146
+            $file = $this->normalize($file);
147
+
148
+            $query->whereStorageId()
149
+                ->wherePath($file);
150
+        } else { //file id
151
+            $query->whereFileId($file);
152
+        }
153
+
154
+        $result = $query->execute();
155
+        $data = $result->fetch();
156
+        $result->closeCursor();
157
+
158
+        //merge partial data
159
+        if (!$data and is_string($file) and isset($this->partial[$file])) {
160
+            return $this->partial[$file];
161
+        } elseif (!$data) {
162
+            return $data;
163
+        } else {
164
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
165
+        }
166
+    }
167
+
168
+    /**
169
+     * Create a CacheEntry from database row
170
+     *
171
+     * @param array $data
172
+     * @param IMimeTypeLoader $mimetypeLoader
173
+     * @return CacheEntry
174
+     */
175
+    public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) {
176
+        //fix types
177
+        $data['fileid'] = (int)$data['fileid'];
178
+        $data['parent'] = (int)$data['parent'];
179
+        $data['size'] = 0 + $data['size'];
180
+        $data['mtime'] = (int)$data['mtime'];
181
+        $data['storage_mtime'] = (int)$data['storage_mtime'];
182
+        $data['encryptedVersion'] = (int)$data['encrypted'];
183
+        $data['encrypted'] = (bool)$data['encrypted'];
184
+        $data['storage_id'] = $data['storage'];
185
+        $data['storage'] = (int)$data['storage'];
186
+        $data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']);
187
+        $data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']);
188
+        if ($data['storage_mtime'] == 0) {
189
+            $data['storage_mtime'] = $data['mtime'];
190
+        }
191
+        $data['permissions'] = (int)$data['permissions'];
192
+        if (isset($data['creation_time'])) {
193
+            $data['creation_time'] = (int) $data['creation_time'];
194
+        }
195
+        if (isset($data['upload_time'])) {
196
+            $data['upload_time'] = (int) $data['upload_time'];
197
+        }
198
+        return new CacheEntry($data);
199
+    }
200
+
201
+    /**
202
+     * get the metadata of all files stored in $folder
203
+     *
204
+     * @param string $folder
205
+     * @return ICacheEntry[]
206
+     */
207
+    public function getFolderContents($folder) {
208
+        $fileId = $this->getId($folder);
209
+        return $this->getFolderContentsById($fileId);
210
+    }
211
+
212
+    /**
213
+     * get the metadata of all files stored in $folder
214
+     *
215
+     * @param int $fileId the file id of the folder
216
+     * @return ICacheEntry[]
217
+     */
218
+    public function getFolderContentsById($fileId) {
219
+        if ($fileId > -1) {
220
+            $query = $this->getQueryBuilder();
221
+            $query->selectFileCache()
222
+                ->whereParent($fileId)
223
+                ->orderBy('name', 'ASC');
224
+
225
+            $result = $query->execute();
226
+            $files = $result->fetchAll();
227
+            $result->closeCursor();
228
+
229
+            return array_map(function (array $data) {
230
+                return self::cacheEntryFromData($data, $this->mimetypeLoader);
231
+            }, $files);
232
+        }
233
+        return [];
234
+    }
235
+
236
+    /**
237
+     * insert or update meta data for a file or folder
238
+     *
239
+     * @param string $file
240
+     * @param array $data
241
+     *
242
+     * @return int file id
243
+     * @throws \RuntimeException
244
+     */
245
+    public function put($file, array $data) {
246
+        if (($id = $this->getId($file)) > -1) {
247
+            $this->update($id, $data);
248
+            return $id;
249
+        } else {
250
+            return $this->insert($file, $data);
251
+        }
252
+    }
253
+
254
+    /**
255
+     * insert meta data for a new file or folder
256
+     *
257
+     * @param string $file
258
+     * @param array $data
259
+     *
260
+     * @return int file id
261
+     * @throws \RuntimeException
262
+     */
263
+    public function insert($file, array $data) {
264
+        // normalize file
265
+        $file = $this->normalize($file);
266
+
267
+        if (isset($this->partial[$file])) { //add any saved partial data
268
+            $data = array_merge($this->partial[$file], $data);
269
+            unset($this->partial[$file]);
270
+        }
271
+
272
+        $requiredFields = ['size', 'mtime', 'mimetype'];
273
+        foreach ($requiredFields as $field) {
274
+            if (!isset($data[$field])) { //data not complete save as partial and return
275
+                $this->partial[$file] = $data;
276
+                return -1;
277
+            }
278
+        }
279
+
280
+        $data['path'] = $file;
281
+        if (!isset($data['parent'])) {
282
+            $data['parent'] = $this->getParentId($file);
283
+        }
284
+        $data['name'] = basename($file);
285
+
286
+        [$values, $extensionValues] = $this->normalizeData($data);
287
+        $values['storage'] = $this->getNumericStorageId();
288
+
289
+        try {
290
+            $builder = $this->connection->getQueryBuilder();
291
+            $builder->insert('filecache');
292
+
293
+            foreach ($values as $column => $value) {
294
+                $builder->setValue($column, $builder->createNamedParameter($value));
295
+            }
296
+
297
+            if ($builder->execute()) {
298
+                $fileId = $builder->getLastInsertId();
299
+
300
+                if (count($extensionValues)) {
301
+                    $query = $this->getQueryBuilder();
302
+                    $query->insert('filecache_extended');
303
+
304
+                    $query->setValue('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT));
305
+                    foreach ($extensionValues as $column => $value) {
306
+                        $query->setValue($column, $query->createNamedParameter($value));
307
+                    }
308
+                    $query->execute();
309
+                }
310
+
311
+                $this->eventDispatcher->dispatch(CacheInsertEvent::class, new CacheInsertEvent($this->storage, $file, $fileId));
312
+                return $fileId;
313
+            }
314
+        } catch (UniqueConstraintViolationException $e) {
315
+            // entry exists already
316
+            if ($this->connection->inTransaction()) {
317
+                $this->connection->commit();
318
+                $this->connection->beginTransaction();
319
+            }
320
+        }
321
+
322
+        // The file was created in the mean time
323
+        if (($id = $this->getId($file)) > -1) {
324
+            $this->update($id, $data);
325
+            return $id;
326
+        } else {
327
+            throw new \RuntimeException('File entry could not be inserted but could also not be selected with getId() in order to perform an update. Please try again.');
328
+        }
329
+    }
330
+
331
+    /**
332
+     * update the metadata of an existing file or folder in the cache
333
+     *
334
+     * @param int $id the fileid of the existing file or folder
335
+     * @param array $data [$key => $value] the metadata to update, only the fields provided in the array will be updated, non-provided values will remain unchanged
336
+     */
337
+    public function update($id, array $data) {
338
+        if (isset($data['path'])) {
339
+            // normalize path
340
+            $data['path'] = $this->normalize($data['path']);
341
+        }
342
+
343
+        if (isset($data['name'])) {
344
+            // normalize path
345
+            $data['name'] = $this->normalize($data['name']);
346
+        }
347
+
348
+        [$values, $extensionValues] = $this->normalizeData($data);
349
+
350
+        if (count($values)) {
351
+            $query = $this->getQueryBuilder();
352
+
353
+            $query->update('filecache')
354
+                ->whereFileId($id)
355
+                ->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
356
+                    return $query->expr()->orX(
357
+                        $query->expr()->neq($key, $query->createNamedParameter($value)),
358
+                        $query->expr()->isNull($key)
359
+                    );
360
+                }, array_keys($values), array_values($values))));
361
+
362
+            foreach ($values as $key => $value) {
363
+                $query->set($key, $query->createNamedParameter($value));
364
+            }
365
+
366
+            $query->execute();
367
+        }
368
+
369
+        if (count($extensionValues)) {
370
+            try {
371
+                $query = $this->getQueryBuilder();
372
+                $query->insert('filecache_extended');
373
+
374
+                $query->setValue('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT));
375
+                foreach ($extensionValues as $column => $value) {
376
+                    $query->setValue($column, $query->createNamedParameter($value));
377
+                }
378
+
379
+                $query->execute();
380
+            } catch (UniqueConstraintViolationException $e) {
381
+                $query = $this->getQueryBuilder();
382
+                $query->update('filecache_extended')
383
+                    ->whereFileId($id)
384
+                    ->andWhere($query->expr()->orX(...array_map(function ($key, $value) use ($query) {
385
+                        return $query->expr()->orX(
386
+                            $query->expr()->neq($key, $query->createNamedParameter($value)),
387
+                            $query->expr()->isNull($key)
388
+                        );
389
+                    }, array_keys($extensionValues), array_values($extensionValues))));
390
+
391
+                foreach ($extensionValues as $key => $value) {
392
+                    $query->set($key, $query->createNamedParameter($value));
393
+                }
394
+
395
+                $query->execute();
396
+            }
397
+        }
398
+
399
+        $path = $this->getPathById($id);
400
+        // path can still be null if the file doesn't exist
401
+        if ($path !== null) {
402
+            $this->eventDispatcher->dispatch(CacheUpdateEvent::class, new CacheUpdateEvent($this->storage, $path, $id));
403
+        }
404
+    }
405
+
406
+    /**
407
+     * extract query parts and params array from data array
408
+     *
409
+     * @param array $data
410
+     * @return array
411
+     */
412
+    protected function normalizeData(array $data): array {
413
+        $fields = [
414
+            'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted',
415
+            'etag', 'permissions', 'checksum', 'storage'];
416
+        $extensionFields = ['metadata_etag', 'creation_time', 'upload_time'];
417
+
418
+        $doNotCopyStorageMTime = false;
419
+        if (array_key_exists('mtime', $data) && $data['mtime'] === null) {
420
+            // this horrific magic tells it to not copy storage_mtime to mtime
421
+            unset($data['mtime']);
422
+            $doNotCopyStorageMTime = true;
423
+        }
424
+
425
+        $params = [];
426
+        $extensionParams = [];
427
+        foreach ($data as $name => $value) {
428
+            if (array_search($name, $fields) !== false) {
429
+                if ($name === 'path') {
430
+                    $params['path_hash'] = md5($value);
431
+                } elseif ($name === 'mimetype') {
432
+                    $params['mimepart'] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/')));
433
+                    $value = $this->mimetypeLoader->getId($value);
434
+                } elseif ($name === 'storage_mtime') {
435
+                    if (!$doNotCopyStorageMTime && !isset($data['mtime'])) {
436
+                        $params['mtime'] = $value;
437
+                    }
438
+                } elseif ($name === 'encrypted') {
439
+                    if (isset($data['encryptedVersion'])) {
440
+                        $value = $data['encryptedVersion'];
441
+                    } else {
442
+                        // Boolean to integer conversion
443
+                        $value = $value ? 1 : 0;
444
+                    }
445
+                }
446
+                $params[$name] = $value;
447
+            }
448
+            if (array_search($name, $extensionFields) !== false) {
449
+                $extensionParams[$name] = $value;
450
+            }
451
+        }
452
+        return [$params, array_filter($extensionParams)];
453
+    }
454
+
455
+    /**
456
+     * get the file id for a file
457
+     *
458
+     * A file id is a numeric id for a file or folder that's unique within an owncloud instance which stays the same for the lifetime of a file
459
+     *
460
+     * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing
461
+     *
462
+     * @param string $file
463
+     * @return int
464
+     */
465
+    public function getId($file) {
466
+        // normalize file
467
+        $file = $this->normalize($file);
468
+
469
+        $query = $this->getQueryBuilder();
470
+        $query->select('fileid')
471
+            ->from('filecache')
472
+            ->whereStorageId()
473
+            ->wherePath($file);
474
+
475
+        $result = $query->execute();
476
+        $id = $result->fetchColumn();
477
+        $result->closeCursor();
478
+
479
+        return $id === false ? -1 : (int)$id;
480
+    }
481
+
482
+    /**
483
+     * get the id of the parent folder of a file
484
+     *
485
+     * @param string $file
486
+     * @return int
487
+     */
488
+    public function getParentId($file) {
489
+        if ($file === '') {
490
+            return -1;
491
+        } else {
492
+            $parent = $this->getParentPath($file);
493
+            return (int)$this->getId($parent);
494
+        }
495
+    }
496
+
497
+    private function getParentPath($path) {
498
+        $parent = dirname($path);
499
+        if ($parent === '.') {
500
+            $parent = '';
501
+        }
502
+        return $parent;
503
+    }
504
+
505
+    /**
506
+     * check if a file is available in the cache
507
+     *
508
+     * @param string $file
509
+     * @return bool
510
+     */
511
+    public function inCache($file) {
512
+        return $this->getId($file) != -1;
513
+    }
514
+
515
+    /**
516
+     * remove a file or folder from the cache
517
+     *
518
+     * when removing a folder from the cache all files and folders inside the folder will be removed as well
519
+     *
520
+     * @param string $file
521
+     */
522
+    public function remove($file) {
523
+        $entry = $this->get($file);
524
+
525
+        if ($entry) {
526
+            $query = $this->getQueryBuilder();
527
+            $query->delete('filecache')
528
+                ->whereFileId($entry->getId());
529
+            $query->execute();
530
+
531
+            $query = $this->getQueryBuilder();
532
+            $query->delete('filecache_extended')
533
+                ->whereFileId($entry->getId());
534
+            $query->execute();
535
+
536
+            if ($entry->getMimeType() == FileInfo::MIMETYPE_FOLDER) {
537
+                $this->removeChildren($entry);
538
+            }
539
+        }
540
+    }
541
+
542
+    /**
543
+     * Get all sub folders of a folder
544
+     *
545
+     * @param ICacheEntry $entry the cache entry of the folder to get the subfolders for
546
+     * @return ICacheEntry[] the cache entries for the subfolders
547
+     */
548
+    private function getSubFolders(ICacheEntry $entry) {
549
+        $children = $this->getFolderContentsById($entry->getId());
550
+        return array_filter($children, function ($child) {
551
+            return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
552
+        });
553
+    }
554
+
555
+    /**
556
+     * Recursively remove all children of a folder
557
+     *
558
+     * @param ICacheEntry $entry the cache entry of the folder to remove the children of
559
+     * @throws \OC\DatabaseException
560
+     */
561
+    private function removeChildren(ICacheEntry $entry) {
562
+        $parentIds = [$entry->getId()];
563
+        $queue = [$entry->getId()];
564
+
565
+        // we walk depth first trough the file tree, removing all filecache_extended attributes while we walk
566
+        // and collecting all folder ids to later use to delete the filecache entries
567
+        while ($entryId = array_pop($queue)) {
568
+            $children = $this->getFolderContentsById($entryId);
569
+            $childIds = array_map(function (ICacheEntry $cacheEntry) {
570
+                return $cacheEntry->getId();
571
+            }, $children);
572
+
573
+            $query = $this->getQueryBuilder();
574
+            $query->delete('filecache_extended')
575
+                ->where($query->expr()->in('fileid', $query->createNamedParameter($childIds, IQueryBuilder::PARAM_INT_ARRAY)));
576
+            $query->execute();
577
+
578
+            /** @var ICacheEntry[] $childFolders */
579
+            $childFolders = array_filter($children, function ($child) {
580
+                return $child->getMimeType() == FileInfo::MIMETYPE_FOLDER;
581
+            });
582
+            foreach ($childFolders as $folder) {
583
+                $parentIds[] = $folder->getId();
584
+                $queue[] = $folder->getId();
585
+            }
586
+        }
587
+
588
+        $query = $this->getQueryBuilder();
589
+        $query->delete('filecache')
590
+            ->whereParentIn($parentIds);
591
+        $query->execute();
592
+    }
593
+
594
+    /**
595
+     * Move a file or folder in the cache
596
+     *
597
+     * @param string $source
598
+     * @param string $target
599
+     */
600
+    public function move($source, $target) {
601
+        $this->moveFromCache($this, $source, $target);
602
+    }
603
+
604
+    /**
605
+     * Get the storage id and path needed for a move
606
+     *
607
+     * @param string $path
608
+     * @return array [$storageId, $internalPath]
609
+     */
610
+    protected function getMoveInfo($path) {
611
+        return [$this->getNumericStorageId(), $path];
612
+    }
613
+
614
+    /**
615
+     * Move a file or folder in the cache
616
+     *
617
+     * @param \OCP\Files\Cache\ICache $sourceCache
618
+     * @param string $sourcePath
619
+     * @param string $targetPath
620
+     * @throws \OC\DatabaseException
621
+     * @throws \Exception if the given storages have an invalid id
622
+     */
623
+    public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
624
+        if ($sourceCache instanceof Cache) {
625
+            // normalize source and target
626
+            $sourcePath = $this->normalize($sourcePath);
627
+            $targetPath = $this->normalize($targetPath);
628
+
629
+            $sourceData = $sourceCache->get($sourcePath);
630
+            $sourceId = $sourceData['fileid'];
631
+            $newParentId = $this->getParentId($targetPath);
632
+
633
+            [$sourceStorageId, $sourcePath] = $sourceCache->getMoveInfo($sourcePath);
634
+            [$targetStorageId, $targetPath] = $this->getMoveInfo($targetPath);
635
+
636
+            if (is_null($sourceStorageId) || $sourceStorageId === false) {
637
+                throw new \Exception('Invalid source storage id: ' . $sourceStorageId);
638
+            }
639
+            if (is_null($targetStorageId) || $targetStorageId === false) {
640
+                throw new \Exception('Invalid target storage id: ' . $targetStorageId);
641
+            }
642
+
643
+            $this->connection->beginTransaction();
644
+            if ($sourceData['mimetype'] === 'httpd/unix-directory') {
645
+                //update all child entries
646
+                $sourceLength = mb_strlen($sourcePath);
647
+                $query = $this->connection->getQueryBuilder();
648
+
649
+                $fun = $query->func();
650
+                $newPathFunction = $fun->concat(
651
+                    $query->createNamedParameter($targetPath),
652
+                    $fun->substring('path', $query->createNamedParameter($sourceLength + 1, IQueryBuilder::PARAM_INT))// +1 for the leading slash
653
+                );
654
+                $query->update('filecache')
655
+                    ->set('storage', $query->createNamedParameter($targetStorageId, IQueryBuilder::PARAM_INT))
656
+                    ->set('path_hash', $fun->md5($newPathFunction))
657
+                    ->set('path', $newPathFunction)
658
+                    ->where($query->expr()->eq('storage', $query->createNamedParameter($sourceStorageId, IQueryBuilder::PARAM_INT)))
659
+                    ->andWhere($query->expr()->like('path', $query->createNamedParameter($this->connection->escapeLikeParameter($sourcePath) . '/%')));
660
+
661
+                try {
662
+                    $query->execute();
663
+                } catch (\OC\DatabaseException $e) {
664
+                    $this->connection->rollBack();
665
+                    throw $e;
666
+                }
667
+            }
668
+
669
+            $query = $this->getQueryBuilder();
670
+            $query->update('filecache')
671
+                ->set('storage', $query->createNamedParameter($targetStorageId))
672
+                ->set('path', $query->createNamedParameter($targetPath))
673
+                ->set('path_hash', $query->createNamedParameter(md5($targetPath)))
674
+                ->set('name', $query->createNamedParameter(basename($targetPath)))
675
+                ->set('parent', $query->createNamedParameter($newParentId, IQueryBuilder::PARAM_INT))
676
+                ->whereFileId($sourceId);
677
+            $query->execute();
678
+
679
+            $this->connection->commit();
680
+        } else {
681
+            $this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
682
+        }
683
+    }
684
+
685
+    /**
686
+     * remove all entries for files that are stored on the storage from the cache
687
+     */
688
+    public function clear() {
689
+        $query = $this->getQueryBuilder();
690
+        $query->delete('filecache')
691
+            ->whereStorageId();
692
+        $query->execute();
693
+
694
+        $query = $this->connection->getQueryBuilder();
695
+        $query->delete('storages')
696
+            ->where($query->expr()->eq('id', $query->createNamedParameter($this->storageId)));
697
+        $query->execute();
698
+    }
699
+
700
+    /**
701
+     * Get the scan status of a file
702
+     *
703
+     * - Cache::NOT_FOUND: File is not in the cache
704
+     * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known
705
+     * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned
706
+     * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned
707
+     *
708
+     * @param string $file
709
+     *
710
+     * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
711
+     */
712
+    public function getStatus($file) {
713
+        // normalize file
714
+        $file = $this->normalize($file);
715
+
716
+        $query = $this->getQueryBuilder();
717
+        $query->select('size')
718
+            ->from('filecache')
719
+            ->whereStorageId()
720
+            ->wherePath($file);
721
+
722
+        $result = $query->execute();
723
+        $size = $result->fetchColumn();
724
+        $result->closeCursor();
725
+
726
+        if ($size !== false) {
727
+            if ((int)$size === -1) {
728
+                return self::SHALLOW;
729
+            } else {
730
+                return self::COMPLETE;
731
+            }
732
+        } else {
733
+            if (isset($this->partial[$file])) {
734
+                return self::PARTIAL;
735
+            } else {
736
+                return self::NOT_FOUND;
737
+            }
738
+        }
739
+    }
740
+
741
+    /**
742
+     * search for files matching $pattern
743
+     *
744
+     * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%')
745
+     * @return ICacheEntry[] an array of cache entries where the name matches the search pattern
746
+     */
747
+    public function search($pattern) {
748
+        // normalize pattern
749
+        $pattern = $this->normalize($pattern);
750
+
751
+        if ($pattern === '%%') {
752
+            return [];
753
+        }
754
+
755
+        $query = $this->getQueryBuilder();
756
+        $query->selectFileCache()
757
+            ->whereStorageId()
758
+            ->andWhere($query->expr()->iLike('name', $query->createNamedParameter($pattern)));
759
+
760
+        $result = $query->execute();
761
+        $files = $result->fetchAll();
762
+        $result->closeCursor();
763
+
764
+        return array_map(function (array $data) {
765
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
766
+        }, $files);
767
+    }
768
+
769
+    /**
770
+     * @param Statement $result
771
+     * @return CacheEntry[]
772
+     */
773
+    private function searchResultToCacheEntries(Statement $result) {
774
+        $files = $result->fetchAll();
775
+
776
+        return array_map(function (array $data) {
777
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
778
+        }, $files);
779
+    }
780
+
781
+    /**
782
+     * search for files by mimetype
783
+     *
784
+     * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
785
+     *        where it will search for all mimetypes in the group ('image/*')
786
+     * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
787
+     */
788
+    public function searchByMime($mimetype) {
789
+        $mimeId = $this->mimetypeLoader->getId($mimetype);
790
+
791
+        $query = $this->getQueryBuilder();
792
+        $query->selectFileCache()
793
+            ->whereStorageId();
794
+
795
+        if (strpos($mimetype, '/')) {
796
+            $query->andWhere($query->expr()->eq('mimetype', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT)));
797
+        } else {
798
+            $query->andWhere($query->expr()->eq('mimepart', $query->createNamedParameter($mimeId, IQueryBuilder::PARAM_INT)));
799
+        }
800
+
801
+        $result = $query->execute();
802
+        $files = $result->fetchAll();
803
+        $result->closeCursor();
804
+
805
+        return array_map(function (array $data) {
806
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
807
+        }, $files);
808
+    }
809
+
810
+    public function searchQuery(ISearchQuery $searchQuery) {
811
+        $builder = $this->getQueryBuilder();
812
+
813
+        $query = $builder->selectFileCache('file');
814
+
815
+        $query->whereStorageId();
816
+
817
+        if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) {
818
+            $query
819
+                ->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
820
+                ->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
821
+                    $builder->expr()->eq('tagmap.type', 'tag.type'),
822
+                    $builder->expr()->eq('tagmap.categoryid', 'tag.id')
823
+                ))
824
+                ->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files')))
825
+                ->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID())));
826
+        }
827
+
828
+        $searchExpr = $this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation());
829
+        if ($searchExpr) {
830
+            $query->andWhere($searchExpr);
831
+        }
832
+
833
+        if ($searchQuery->limitToHome() && ($this instanceof HomeCache)) {
834
+            $query->andWhere($builder->expr()->like('path', $query->expr()->literal('files/%')));
835
+        }
836
+
837
+        $this->querySearchHelper->addSearchOrdersToQuery($query, $searchQuery->getOrder());
838
+
839
+        if ($searchQuery->getLimit()) {
840
+            $query->setMaxResults($searchQuery->getLimit());
841
+        }
842
+        if ($searchQuery->getOffset()) {
843
+            $query->setFirstResult($searchQuery->getOffset());
844
+        }
845
+
846
+        $result = $query->execute();
847
+        return $this->searchResultToCacheEntries($result);
848
+    }
849
+
850
+    /**
851
+     * Re-calculate the folder size and the size of all parent folders
852
+     *
853
+     * @param string|boolean $path
854
+     * @param array $data (optional) meta data of the folder
855
+     */
856
+    public function correctFolderSize($path, $data = null, $isBackgroundScan = false) {
857
+        $this->calculateFolderSize($path, $data);
858
+        if ($path !== '') {
859
+            $parent = dirname($path);
860
+            if ($parent === '.' or $parent === '/') {
861
+                $parent = '';
862
+            }
863
+            if ($isBackgroundScan) {
864
+                $parentData = $this->get($parent);
865
+                if ($parentData['size'] !== -1 && $this->getIncompleteChildrenCount($parentData['fileid']) === 0) {
866
+                    $this->correctFolderSize($parent, $parentData, $isBackgroundScan);
867
+                }
868
+            } else {
869
+                $this->correctFolderSize($parent);
870
+            }
871
+        }
872
+    }
873
+
874
+    /**
875
+     * get the incomplete count that shares parent $folder
876
+     *
877
+     * @param int $fileId the file id of the folder
878
+     * @return int
879
+     */
880
+    public function getIncompleteChildrenCount($fileId) {
881
+        if ($fileId > -1) {
882
+            $query = $this->getQueryBuilder();
883
+            $query->select($query->func()->count())
884
+                ->from('filecache')
885
+                ->whereParent($fileId)
886
+                ->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
887
+
888
+            $result = $query->execute();
889
+            $size = (int)$result->fetchColumn();
890
+            $result->closeCursor();
891
+
892
+            return $size;
893
+        }
894
+        return -1;
895
+    }
896
+
897
+    /**
898
+     * calculate the size of a folder and set it in the cache
899
+     *
900
+     * @param string $path
901
+     * @param array $entry (optional) meta data of the folder
902
+     * @return int
903
+     */
904
+    public function calculateFolderSize($path, $entry = null) {
905
+        $totalSize = 0;
906
+        if (is_null($entry) or !isset($entry['fileid'])) {
907
+            $entry = $this->get($path);
908
+        }
909
+        if (isset($entry['mimetype']) && $entry['mimetype'] === FileInfo::MIMETYPE_FOLDER) {
910
+            $id = $entry['fileid'];
911
+
912
+            $query = $this->getQueryBuilder();
913
+            $query->selectAlias($query->func()->sum('size'), 'f1')
914
+                ->selectAlias($query->func()->min('size'), 'f2')
915
+                ->from('filecache')
916
+                ->whereStorageId()
917
+                ->whereParent($id);
918
+
919
+            $result = $query->execute();
920
+            $row = $result->fetch();
921
+            $result->closeCursor();
922
+
923
+            if ($row) {
924
+                [$sum, $min] = array_values($row);
925
+                $sum = 0 + $sum;
926
+                $min = 0 + $min;
927
+                if ($min === -1) {
928
+                    $totalSize = $min;
929
+                } else {
930
+                    $totalSize = $sum;
931
+                }
932
+                if ($entry['size'] !== $totalSize) {
933
+                    $this->update($id, ['size' => $totalSize]);
934
+                }
935
+            }
936
+        }
937
+        return $totalSize;
938
+    }
939
+
940
+    /**
941
+     * get all file ids on the files on the storage
942
+     *
943
+     * @return int[]
944
+     */
945
+    public function getAll() {
946
+        $query = $this->getQueryBuilder();
947
+        $query->select('fileid')
948
+            ->from('filecache')
949
+            ->whereStorageId();
950
+
951
+        $result = $query->execute();
952
+        $files = $result->fetchAll(\PDO::FETCH_COLUMN);
953
+        $result->closeCursor();
954
+
955
+        return array_map(function ($id) {
956
+            return (int)$id;
957
+        }, $files);
958
+    }
959
+
960
+    /**
961
+     * find a folder in the cache which has not been fully scanned
962
+     *
963
+     * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
964
+     * use the one with the highest id gives the best result with the background scanner, since that is most
965
+     * likely the folder where we stopped scanning previously
966
+     *
967
+     * @return string|bool the path of the folder or false when no folder matched
968
+     */
969
+    public function getIncomplete() {
970
+        $query = $this->getQueryBuilder();
971
+        $query->select('path')
972
+            ->from('filecache')
973
+            ->whereStorageId()
974
+            ->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
975
+            ->orderBy('fileid', 'DESC');
976
+
977
+        $result = $query->execute();
978
+        $path = $result->fetchColumn();
979
+        $result->closeCursor();
980
+
981
+        return $path;
982
+    }
983
+
984
+    /**
985
+     * get the path of a file on this storage by it's file id
986
+     *
987
+     * @param int $id the file id of the file or folder to search
988
+     * @return string|null the path of the file (relative to the storage) or null if a file with the given id does not exists within this cache
989
+     */
990
+    public function getPathById($id) {
991
+        $query = $this->getQueryBuilder();
992
+        $query->select('path')
993
+            ->from('filecache')
994
+            ->whereStorageId()
995
+            ->whereFileId($id);
996
+
997
+        $result = $query->execute();
998
+        $path = $result->fetchColumn();
999
+        $result->closeCursor();
1000
+
1001
+        if ($path === false) {
1002
+            return null;
1003
+        }
1004
+
1005
+        return (string) $path;
1006
+    }
1007
+
1008
+    /**
1009
+     * get the storage id of the storage for a file and the internal path of the file
1010
+     * unlike getPathById this does not limit the search to files on this storage and
1011
+     * instead does a global search in the cache table
1012
+     *
1013
+     * @param int $id
1014
+     * @return array first element holding the storage id, second the path
1015
+     * @deprecated use getPathById() instead
1016
+     */
1017
+    public static function getById($id) {
1018
+        $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
1019
+        $query->select('path', 'storage')
1020
+            ->from('filecache')
1021
+            ->where($query->expr()->eq('fileid', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
1022
+
1023
+        $result = $query->execute();
1024
+        $row = $result->fetch();
1025
+        $result->closeCursor();
1026
+
1027
+        if ($row) {
1028
+            $numericId = $row['storage'];
1029
+            $path = $row['path'];
1030
+        } else {
1031
+            return null;
1032
+        }
1033
+
1034
+        if ($id = Storage::getStorageId($numericId)) {
1035
+            return [$id, $path];
1036
+        } else {
1037
+            return null;
1038
+        }
1039
+    }
1040
+
1041
+    /**
1042
+     * normalize the given path
1043
+     *
1044
+     * @param string $path
1045
+     * @return string
1046
+     */
1047
+    public function normalize($path) {
1048
+        return trim(\OC_Util::normalizeUnicode($path), '/');
1049
+    }
1050 1050
 }
Please login to merge, or discard this patch.
lib/private/DB/MissingPrimaryKeyInformation.php 1 patch
Indentation   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -28,15 +28,15 @@
 block discarded – undo
28 28
 namespace OC\DB;
29 29
 
30 30
 class MissingPrimaryKeyInformation {
31
-	private $listOfMissingPrimaryKeys = [];
31
+    private $listOfMissingPrimaryKeys = [];
32 32
 
33
-	public function addHintForMissingSubject(string $tableName) {
34
-		$this->listOfMissingPrimaryKeys[] = [
35
-			'tableName' => $tableName,
36
-		];
37
-	}
33
+    public function addHintForMissingSubject(string $tableName) {
34
+        $this->listOfMissingPrimaryKeys[] = [
35
+            'tableName' => $tableName,
36
+        ];
37
+    }
38 38
 
39
-	public function getListOfMissingPrimaryKeys(): array {
40
-		return $this->listOfMissingPrimaryKeys;
41
-	}
39
+    public function getListOfMissingPrimaryKeys(): array {
40
+        return $this->listOfMissingPrimaryKeys;
41
+    }
42 42
 }
Please login to merge, or discard this patch.
lib/private/DB/Connection.php 1 patch
Indentation   +440 added lines, -440 removed lines patch added patch discarded remove patch
@@ -50,444 +50,444 @@
 block discarded – undo
50 50
 use OCP\PreConditionNotMetException;
51 51
 
52 52
 class Connection extends ReconnectWrapper implements IDBConnection {
53
-	/** @var string */
54
-	protected $tablePrefix;
55
-
56
-	/** @var \OC\DB\Adapter $adapter */
57
-	protected $adapter;
58
-
59
-	/** @var SystemConfig */
60
-	private $systemConfig;
61
-
62
-	/** @var ILogger */
63
-	private $logger;
64
-
65
-	protected $lockedTable = null;
66
-
67
-	/** @var int */
68
-	protected $queriesBuilt = 0;
69
-
70
-	/** @var int */
71
-	protected $queriesExecuted = 0;
72
-
73
-	public function connect() {
74
-		try {
75
-			return parent::connect();
76
-		} catch (DBALException $e) {
77
-			// throw a new exception to prevent leaking info from the stacktrace
78
-			throw new DBALException('Failed to connect to the database: ' . $e->getMessage(), $e->getCode());
79
-		}
80
-	}
81
-
82
-	public function getStats(): array {
83
-		return [
84
-			'built' => $this->queriesBuilt,
85
-			'executed' => $this->queriesExecuted,
86
-		];
87
-	}
88
-
89
-	/**
90
-	 * Returns a QueryBuilder for the connection.
91
-	 *
92
-	 * @return \OCP\DB\QueryBuilder\IQueryBuilder
93
-	 */
94
-	public function getQueryBuilder() {
95
-		$this->queriesBuilt++;
96
-		return new QueryBuilder(
97
-			$this,
98
-			$this->systemConfig,
99
-			$this->logger
100
-		);
101
-	}
102
-
103
-	/**
104
-	 * Gets the QueryBuilder for the connection.
105
-	 *
106
-	 * @return \Doctrine\DBAL\Query\QueryBuilder
107
-	 * @deprecated please use $this->getQueryBuilder() instead
108
-	 */
109
-	public function createQueryBuilder() {
110
-		$backtrace = $this->getCallerBacktrace();
111
-		\OC::$server->getLogger()->debug('Doctrine QueryBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]);
112
-		$this->queriesBuilt++;
113
-		return parent::createQueryBuilder();
114
-	}
115
-
116
-	/**
117
-	 * Gets the ExpressionBuilder for the connection.
118
-	 *
119
-	 * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder
120
-	 * @deprecated please use $this->getQueryBuilder()->expr() instead
121
-	 */
122
-	public function getExpressionBuilder() {
123
-		$backtrace = $this->getCallerBacktrace();
124
-		\OC::$server->getLogger()->debug('Doctrine ExpressionBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]);
125
-		$this->queriesBuilt++;
126
-		return parent::getExpressionBuilder();
127
-	}
128
-
129
-	/**
130
-	 * Get the file and line that called the method where `getCallerBacktrace()` was used
131
-	 *
132
-	 * @return string
133
-	 */
134
-	protected function getCallerBacktrace() {
135
-		$traces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
136
-
137
-		// 0 is the method where we use `getCallerBacktrace`
138
-		// 1 is the target method which uses the method we want to log
139
-		if (isset($traces[1])) {
140
-			return $traces[1]['file'] . ':' . $traces[1]['line'];
141
-		}
142
-
143
-		return '';
144
-	}
145
-
146
-	/**
147
-	 * @return string
148
-	 */
149
-	public function getPrefix() {
150
-		return $this->tablePrefix;
151
-	}
152
-
153
-	/**
154
-	 * Initializes a new instance of the Connection class.
155
-	 *
156
-	 * @param array $params  The connection parameters.
157
-	 * @param \Doctrine\DBAL\Driver $driver
158
-	 * @param \Doctrine\DBAL\Configuration $config
159
-	 * @param \Doctrine\Common\EventManager $eventManager
160
-	 * @throws \Exception
161
-	 */
162
-	public function __construct(array $params, Driver $driver, Configuration $config = null,
163
-		EventManager $eventManager = null) {
164
-		if (!isset($params['adapter'])) {
165
-			throw new \Exception('adapter not set');
166
-		}
167
-		if (!isset($params['tablePrefix'])) {
168
-			throw new \Exception('tablePrefix not set');
169
-		}
170
-		parent::__construct($params, $driver, $config, $eventManager);
171
-		$this->adapter = new $params['adapter']($this);
172
-		$this->tablePrefix = $params['tablePrefix'];
173
-
174
-		$this->systemConfig = \OC::$server->getSystemConfig();
175
-		$this->logger = \OC::$server->getLogger();
176
-	}
177
-
178
-	/**
179
-	 * Prepares an SQL statement.
180
-	 *
181
-	 * @param string $statement The SQL statement to prepare.
182
-	 * @param int $limit
183
-	 * @param int $offset
184
-	 * @return \Doctrine\DBAL\Driver\Statement The prepared statement.
185
-	 */
186
-	public function prepare($statement, $limit = null, $offset = null) {
187
-		if ($limit === -1) {
188
-			$limit = null;
189
-		}
190
-		if (!is_null($limit)) {
191
-			$platform = $this->getDatabasePlatform();
192
-			$statement = $platform->modifyLimitQuery($statement, $limit, $offset);
193
-		}
194
-		$statement = $this->replaceTablePrefix($statement);
195
-		$statement = $this->adapter->fixupStatement($statement);
196
-
197
-		return parent::prepare($statement);
198
-	}
199
-
200
-	/**
201
-	 * Executes an, optionally parametrized, SQL query.
202
-	 *
203
-	 * If the query is parametrized, a prepared statement is used.
204
-	 * If an SQLLogger is configured, the execution is logged.
205
-	 *
206
-	 * @param string                                      $sql  The SQL query to execute.
207
-	 * @param array                                       $params The parameters to bind to the query, if any.
208
-	 * @param array                                       $types  The types the previous parameters are in.
209
-	 * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp    The query cache profile, optional.
210
-	 *
211
-	 * @return \Doctrine\DBAL\Driver\Statement The executed statement.
212
-	 *
213
-	 * @throws \Doctrine\DBAL\DBALException
214
-	 */
215
-	public function executeQuery($sql, array $params = [], $types = [], QueryCacheProfile $qcp = null) {
216
-		$sql = $this->replaceTablePrefix($sql);
217
-		$sql = $this->adapter->fixupStatement($sql);
218
-		$this->queriesExecuted++;
219
-		return parent::executeQuery($sql, $params, $types, $qcp);
220
-	}
221
-
222
-	public function executeUpdate($sql, array $params = [], array $types = []) {
223
-		$sql = $this->replaceTablePrefix($sql);
224
-		$sql = $this->adapter->fixupStatement($sql);
225
-		$this->queriesExecuted++;
226
-		return parent::executeUpdate($sql, $params, $types);
227
-	}
228
-
229
-	/**
230
-	 * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
231
-	 * and returns the number of affected rows.
232
-	 *
233
-	 * This method supports PDO binding types as well as DBAL mapping types.
234
-	 *
235
-	 * @param string $sql  The SQL query.
236
-	 * @param array  $params The query parameters.
237
-	 * @param array  $types  The parameter types.
238
-	 *
239
-	 * @return integer The number of affected rows.
240
-	 *
241
-	 * @throws \Doctrine\DBAL\DBALException
242
-	 */
243
-	public function executeStatement($sql, array $params = [], array $types = []) {
244
-		$sql = $this->replaceTablePrefix($sql);
245
-		$sql = $this->adapter->fixupStatement($sql);
246
-		$this->queriesExecuted++;
247
-		return parent::executeStatement($sql, $params, $types);
248
-	}
249
-
250
-	/**
251
-	 * Returns the ID of the last inserted row, or the last value from a sequence object,
252
-	 * depending on the underlying driver.
253
-	 *
254
-	 * Note: This method may not return a meaningful or consistent result across different drivers,
255
-	 * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
256
-	 * columns or sequences.
257
-	 *
258
-	 * @param string $seqName Name of the sequence object from which the ID should be returned.
259
-	 * @return string A string representation of the last inserted ID.
260
-	 */
261
-	public function lastInsertId($seqName = null) {
262
-		if ($seqName) {
263
-			$seqName = $this->replaceTablePrefix($seqName);
264
-		}
265
-		return $this->adapter->lastInsertId($seqName);
266
-	}
267
-
268
-	// internal use
269
-	public function realLastInsertId($seqName = null) {
270
-		return parent::lastInsertId($seqName);
271
-	}
272
-
273
-	/**
274
-	 * Insert a row if the matching row does not exists. To accomplish proper race condition avoidance
275
-	 * it is needed that there is also a unique constraint on the values. Then this method will
276
-	 * catch the exception and return 0.
277
-	 *
278
-	 * @param string $table The table name (will replace *PREFIX* with the actual prefix)
279
-	 * @param array $input data that should be inserted into the table  (column name => value)
280
-	 * @param array|null $compare List of values that should be checked for "if not exists"
281
-	 *				If this is null or an empty array, all keys of $input will be compared
282
-	 *				Please note: text fields (clob) must not be used in the compare array
283
-	 * @return int number of inserted rows
284
-	 * @throws \Doctrine\DBAL\DBALException
285
-	 * @deprecated 15.0.0 - use unique index and "try { $db->insert() } catch (UniqueConstraintViolationException $e) {}" instead, because it is more reliable and does not have the risk for deadlocks - see https://github.com/nextcloud/server/pull/12371
286
-	 */
287
-	public function insertIfNotExist($table, $input, array $compare = null) {
288
-		return $this->adapter->insertIfNotExist($table, $input, $compare);
289
-	}
290
-
291
-	public function insertIgnoreConflict(string $table, array $values) : int {
292
-		return $this->adapter->insertIgnoreConflict($table, $values);
293
-	}
294
-
295
-	private function getType($value) {
296
-		if (is_bool($value)) {
297
-			return IQueryBuilder::PARAM_BOOL;
298
-		} elseif (is_int($value)) {
299
-			return IQueryBuilder::PARAM_INT;
300
-		} else {
301
-			return IQueryBuilder::PARAM_STR;
302
-		}
303
-	}
304
-
305
-	/**
306
-	 * Insert or update a row value
307
-	 *
308
-	 * @param string $table
309
-	 * @param array $keys (column name => value)
310
-	 * @param array $values (column name => value)
311
-	 * @param array $updatePreconditionValues ensure values match preconditions (column name => value)
312
-	 * @return int number of new rows
313
-	 * @throws \Doctrine\DBAL\DBALException
314
-	 * @throws PreConditionNotMetException
315
-	 */
316
-	public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []) {
317
-		try {
318
-			$insertQb = $this->getQueryBuilder();
319
-			$insertQb->insert($table)
320
-				->values(
321
-					array_map(function ($value) use ($insertQb) {
322
-						return $insertQb->createNamedParameter($value, $this->getType($value));
323
-					}, array_merge($keys, $values))
324
-				);
325
-			return $insertQb->execute();
326
-		} catch (NotNullConstraintViolationException $e) {
327
-			throw $e;
328
-		} catch (ConstraintViolationException $e) {
329
-			// value already exists, try update
330
-			$updateQb = $this->getQueryBuilder();
331
-			$updateQb->update($table);
332
-			foreach ($values as $name => $value) {
333
-				$updateQb->set($name, $updateQb->createNamedParameter($value, $this->getType($value)));
334
-			}
335
-			$where = $updateQb->expr()->andX();
336
-			$whereValues = array_merge($keys, $updatePreconditionValues);
337
-			foreach ($whereValues as $name => $value) {
338
-				if ($value === '') {
339
-					$where->add($updateQb->expr()->emptyString(
340
-						$name
341
-					));
342
-				} else {
343
-					$where->add($updateQb->expr()->eq(
344
-						$name,
345
-						$updateQb->createNamedParameter($value, $this->getType($value)),
346
-						$this->getType($value)
347
-					));
348
-				}
349
-			}
350
-			$updateQb->where($where);
351
-			$affected = $updateQb->execute();
352
-
353
-			if ($affected === 0 && !empty($updatePreconditionValues)) {
354
-				throw new PreConditionNotMetException();
355
-			}
356
-
357
-			return 0;
358
-		}
359
-	}
360
-
361
-	/**
362
-	 * Create an exclusive read+write lock on a table
363
-	 *
364
-	 * @param string $tableName
365
-	 * @throws \BadMethodCallException When trying to acquire a second lock
366
-	 * @since 9.1.0
367
-	 */
368
-	public function lockTable($tableName) {
369
-		if ($this->lockedTable !== null) {
370
-			throw new \BadMethodCallException('Can not lock a new table until the previous lock is released.');
371
-		}
372
-
373
-		$tableName = $this->tablePrefix . $tableName;
374
-		$this->lockedTable = $tableName;
375
-		$this->adapter->lockTable($tableName);
376
-	}
377
-
378
-	/**
379
-	 * Release a previous acquired lock again
380
-	 *
381
-	 * @since 9.1.0
382
-	 */
383
-	public function unlockTable() {
384
-		$this->adapter->unlockTable();
385
-		$this->lockedTable = null;
386
-	}
387
-
388
-	/**
389
-	 * returns the error code and message as a string for logging
390
-	 * works with DoctrineException
391
-	 * @return string
392
-	 */
393
-	public function getError() {
394
-		$msg = $this->errorCode() . ': ';
395
-		$errorInfo = $this->errorInfo();
396
-		if (is_array($errorInfo)) {
397
-			$msg .= 'SQLSTATE = '.$errorInfo[0] . ', ';
398
-			$msg .= 'Driver Code = '.$errorInfo[1] . ', ';
399
-			$msg .= 'Driver Message = '.$errorInfo[2];
400
-		}
401
-		return $msg;
402
-	}
403
-
404
-	/**
405
-	 * Drop a table from the database if it exists
406
-	 *
407
-	 * @param string $table table name without the prefix
408
-	 */
409
-	public function dropTable($table) {
410
-		$table = $this->tablePrefix . trim($table);
411
-		$schema = $this->getSchemaManager();
412
-		if ($schema->tablesExist([$table])) {
413
-			$schema->dropTable($table);
414
-		}
415
-	}
416
-
417
-	/**
418
-	 * Check if a table exists
419
-	 *
420
-	 * @param string $table table name without the prefix
421
-	 * @return bool
422
-	 */
423
-	public function tableExists($table) {
424
-		$table = $this->tablePrefix . trim($table);
425
-		$schema = $this->getSchemaManager();
426
-		return $schema->tablesExist([$table]);
427
-	}
428
-
429
-	// internal use
430
-	/**
431
-	 * @param string $statement
432
-	 * @return string
433
-	 */
434
-	protected function replaceTablePrefix($statement) {
435
-		return str_replace('*PREFIX*', $this->tablePrefix, $statement);
436
-	}
437
-
438
-	/**
439
-	 * Check if a transaction is active
440
-	 *
441
-	 * @return bool
442
-	 * @since 8.2.0
443
-	 */
444
-	public function inTransaction() {
445
-		return $this->getTransactionNestingLevel() > 0;
446
-	}
447
-
448
-	/**
449
-	 * Escape a parameter to be used in a LIKE query
450
-	 *
451
-	 * @param string $param
452
-	 * @return string
453
-	 */
454
-	public function escapeLikeParameter($param) {
455
-		return addcslashes($param, '\\_%');
456
-	}
457
-
458
-	/**
459
-	 * Check whether or not the current database support 4byte wide unicode
460
-	 *
461
-	 * @return bool
462
-	 * @since 11.0.0
463
-	 */
464
-	public function supports4ByteText() {
465
-		if (!$this->getDatabasePlatform() instanceof MySqlPlatform) {
466
-			return true;
467
-		}
468
-		return $this->getParams()['charset'] === 'utf8mb4';
469
-	}
470
-
471
-
472
-	/**
473
-	 * Create the schema of the connected database
474
-	 *
475
-	 * @return Schema
476
-	 */
477
-	public function createSchema() {
478
-		$schemaManager = new MDB2SchemaManager($this);
479
-		$migrator = $schemaManager->getMigrator();
480
-		return $migrator->createSchema();
481
-	}
482
-
483
-	/**
484
-	 * Migrate the database to the given schema
485
-	 *
486
-	 * @param Schema $toSchema
487
-	 */
488
-	public function migrateToSchema(Schema $toSchema) {
489
-		$schemaManager = new MDB2SchemaManager($this);
490
-		$migrator = $schemaManager->getMigrator();
491
-		$migrator->migrate($toSchema);
492
-	}
53
+    /** @var string */
54
+    protected $tablePrefix;
55
+
56
+    /** @var \OC\DB\Adapter $adapter */
57
+    protected $adapter;
58
+
59
+    /** @var SystemConfig */
60
+    private $systemConfig;
61
+
62
+    /** @var ILogger */
63
+    private $logger;
64
+
65
+    protected $lockedTable = null;
66
+
67
+    /** @var int */
68
+    protected $queriesBuilt = 0;
69
+
70
+    /** @var int */
71
+    protected $queriesExecuted = 0;
72
+
73
+    public function connect() {
74
+        try {
75
+            return parent::connect();
76
+        } catch (DBALException $e) {
77
+            // throw a new exception to prevent leaking info from the stacktrace
78
+            throw new DBALException('Failed to connect to the database: ' . $e->getMessage(), $e->getCode());
79
+        }
80
+    }
81
+
82
+    public function getStats(): array {
83
+        return [
84
+            'built' => $this->queriesBuilt,
85
+            'executed' => $this->queriesExecuted,
86
+        ];
87
+    }
88
+
89
+    /**
90
+     * Returns a QueryBuilder for the connection.
91
+     *
92
+     * @return \OCP\DB\QueryBuilder\IQueryBuilder
93
+     */
94
+    public function getQueryBuilder() {
95
+        $this->queriesBuilt++;
96
+        return new QueryBuilder(
97
+            $this,
98
+            $this->systemConfig,
99
+            $this->logger
100
+        );
101
+    }
102
+
103
+    /**
104
+     * Gets the QueryBuilder for the connection.
105
+     *
106
+     * @return \Doctrine\DBAL\Query\QueryBuilder
107
+     * @deprecated please use $this->getQueryBuilder() instead
108
+     */
109
+    public function createQueryBuilder() {
110
+        $backtrace = $this->getCallerBacktrace();
111
+        \OC::$server->getLogger()->debug('Doctrine QueryBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]);
112
+        $this->queriesBuilt++;
113
+        return parent::createQueryBuilder();
114
+    }
115
+
116
+    /**
117
+     * Gets the ExpressionBuilder for the connection.
118
+     *
119
+     * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder
120
+     * @deprecated please use $this->getQueryBuilder()->expr() instead
121
+     */
122
+    public function getExpressionBuilder() {
123
+        $backtrace = $this->getCallerBacktrace();
124
+        \OC::$server->getLogger()->debug('Doctrine ExpressionBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]);
125
+        $this->queriesBuilt++;
126
+        return parent::getExpressionBuilder();
127
+    }
128
+
129
+    /**
130
+     * Get the file and line that called the method where `getCallerBacktrace()` was used
131
+     *
132
+     * @return string
133
+     */
134
+    protected function getCallerBacktrace() {
135
+        $traces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
136
+
137
+        // 0 is the method where we use `getCallerBacktrace`
138
+        // 1 is the target method which uses the method we want to log
139
+        if (isset($traces[1])) {
140
+            return $traces[1]['file'] . ':' . $traces[1]['line'];
141
+        }
142
+
143
+        return '';
144
+    }
145
+
146
+    /**
147
+     * @return string
148
+     */
149
+    public function getPrefix() {
150
+        return $this->tablePrefix;
151
+    }
152
+
153
+    /**
154
+     * Initializes a new instance of the Connection class.
155
+     *
156
+     * @param array $params  The connection parameters.
157
+     * @param \Doctrine\DBAL\Driver $driver
158
+     * @param \Doctrine\DBAL\Configuration $config
159
+     * @param \Doctrine\Common\EventManager $eventManager
160
+     * @throws \Exception
161
+     */
162
+    public function __construct(array $params, Driver $driver, Configuration $config = null,
163
+        EventManager $eventManager = null) {
164
+        if (!isset($params['adapter'])) {
165
+            throw new \Exception('adapter not set');
166
+        }
167
+        if (!isset($params['tablePrefix'])) {
168
+            throw new \Exception('tablePrefix not set');
169
+        }
170
+        parent::__construct($params, $driver, $config, $eventManager);
171
+        $this->adapter = new $params['adapter']($this);
172
+        $this->tablePrefix = $params['tablePrefix'];
173
+
174
+        $this->systemConfig = \OC::$server->getSystemConfig();
175
+        $this->logger = \OC::$server->getLogger();
176
+    }
177
+
178
+    /**
179
+     * Prepares an SQL statement.
180
+     *
181
+     * @param string $statement The SQL statement to prepare.
182
+     * @param int $limit
183
+     * @param int $offset
184
+     * @return \Doctrine\DBAL\Driver\Statement The prepared statement.
185
+     */
186
+    public function prepare($statement, $limit = null, $offset = null) {
187
+        if ($limit === -1) {
188
+            $limit = null;
189
+        }
190
+        if (!is_null($limit)) {
191
+            $platform = $this->getDatabasePlatform();
192
+            $statement = $platform->modifyLimitQuery($statement, $limit, $offset);
193
+        }
194
+        $statement = $this->replaceTablePrefix($statement);
195
+        $statement = $this->adapter->fixupStatement($statement);
196
+
197
+        return parent::prepare($statement);
198
+    }
199
+
200
+    /**
201
+     * Executes an, optionally parametrized, SQL query.
202
+     *
203
+     * If the query is parametrized, a prepared statement is used.
204
+     * If an SQLLogger is configured, the execution is logged.
205
+     *
206
+     * @param string                                      $sql  The SQL query to execute.
207
+     * @param array                                       $params The parameters to bind to the query, if any.
208
+     * @param array                                       $types  The types the previous parameters are in.
209
+     * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp    The query cache profile, optional.
210
+     *
211
+     * @return \Doctrine\DBAL\Driver\Statement The executed statement.
212
+     *
213
+     * @throws \Doctrine\DBAL\DBALException
214
+     */
215
+    public function executeQuery($sql, array $params = [], $types = [], QueryCacheProfile $qcp = null) {
216
+        $sql = $this->replaceTablePrefix($sql);
217
+        $sql = $this->adapter->fixupStatement($sql);
218
+        $this->queriesExecuted++;
219
+        return parent::executeQuery($sql, $params, $types, $qcp);
220
+    }
221
+
222
+    public function executeUpdate($sql, array $params = [], array $types = []) {
223
+        $sql = $this->replaceTablePrefix($sql);
224
+        $sql = $this->adapter->fixupStatement($sql);
225
+        $this->queriesExecuted++;
226
+        return parent::executeUpdate($sql, $params, $types);
227
+    }
228
+
229
+    /**
230
+     * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
231
+     * and returns the number of affected rows.
232
+     *
233
+     * This method supports PDO binding types as well as DBAL mapping types.
234
+     *
235
+     * @param string $sql  The SQL query.
236
+     * @param array  $params The query parameters.
237
+     * @param array  $types  The parameter types.
238
+     *
239
+     * @return integer The number of affected rows.
240
+     *
241
+     * @throws \Doctrine\DBAL\DBALException
242
+     */
243
+    public function executeStatement($sql, array $params = [], array $types = []) {
244
+        $sql = $this->replaceTablePrefix($sql);
245
+        $sql = $this->adapter->fixupStatement($sql);
246
+        $this->queriesExecuted++;
247
+        return parent::executeStatement($sql, $params, $types);
248
+    }
249
+
250
+    /**
251
+     * Returns the ID of the last inserted row, or the last value from a sequence object,
252
+     * depending on the underlying driver.
253
+     *
254
+     * Note: This method may not return a meaningful or consistent result across different drivers,
255
+     * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
256
+     * columns or sequences.
257
+     *
258
+     * @param string $seqName Name of the sequence object from which the ID should be returned.
259
+     * @return string A string representation of the last inserted ID.
260
+     */
261
+    public function lastInsertId($seqName = null) {
262
+        if ($seqName) {
263
+            $seqName = $this->replaceTablePrefix($seqName);
264
+        }
265
+        return $this->adapter->lastInsertId($seqName);
266
+    }
267
+
268
+    // internal use
269
+    public function realLastInsertId($seqName = null) {
270
+        return parent::lastInsertId($seqName);
271
+    }
272
+
273
+    /**
274
+     * Insert a row if the matching row does not exists. To accomplish proper race condition avoidance
275
+     * it is needed that there is also a unique constraint on the values. Then this method will
276
+     * catch the exception and return 0.
277
+     *
278
+     * @param string $table The table name (will replace *PREFIX* with the actual prefix)
279
+     * @param array $input data that should be inserted into the table  (column name => value)
280
+     * @param array|null $compare List of values that should be checked for "if not exists"
281
+     *				If this is null or an empty array, all keys of $input will be compared
282
+     *				Please note: text fields (clob) must not be used in the compare array
283
+     * @return int number of inserted rows
284
+     * @throws \Doctrine\DBAL\DBALException
285
+     * @deprecated 15.0.0 - use unique index and "try { $db->insert() } catch (UniqueConstraintViolationException $e) {}" instead, because it is more reliable and does not have the risk for deadlocks - see https://github.com/nextcloud/server/pull/12371
286
+     */
287
+    public function insertIfNotExist($table, $input, array $compare = null) {
288
+        return $this->adapter->insertIfNotExist($table, $input, $compare);
289
+    }
290
+
291
+    public function insertIgnoreConflict(string $table, array $values) : int {
292
+        return $this->adapter->insertIgnoreConflict($table, $values);
293
+    }
294
+
295
+    private function getType($value) {
296
+        if (is_bool($value)) {
297
+            return IQueryBuilder::PARAM_BOOL;
298
+        } elseif (is_int($value)) {
299
+            return IQueryBuilder::PARAM_INT;
300
+        } else {
301
+            return IQueryBuilder::PARAM_STR;
302
+        }
303
+    }
304
+
305
+    /**
306
+     * Insert or update a row value
307
+     *
308
+     * @param string $table
309
+     * @param array $keys (column name => value)
310
+     * @param array $values (column name => value)
311
+     * @param array $updatePreconditionValues ensure values match preconditions (column name => value)
312
+     * @return int number of new rows
313
+     * @throws \Doctrine\DBAL\DBALException
314
+     * @throws PreConditionNotMetException
315
+     */
316
+    public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []) {
317
+        try {
318
+            $insertQb = $this->getQueryBuilder();
319
+            $insertQb->insert($table)
320
+                ->values(
321
+                    array_map(function ($value) use ($insertQb) {
322
+                        return $insertQb->createNamedParameter($value, $this->getType($value));
323
+                    }, array_merge($keys, $values))
324
+                );
325
+            return $insertQb->execute();
326
+        } catch (NotNullConstraintViolationException $e) {
327
+            throw $e;
328
+        } catch (ConstraintViolationException $e) {
329
+            // value already exists, try update
330
+            $updateQb = $this->getQueryBuilder();
331
+            $updateQb->update($table);
332
+            foreach ($values as $name => $value) {
333
+                $updateQb->set($name, $updateQb->createNamedParameter($value, $this->getType($value)));
334
+            }
335
+            $where = $updateQb->expr()->andX();
336
+            $whereValues = array_merge($keys, $updatePreconditionValues);
337
+            foreach ($whereValues as $name => $value) {
338
+                if ($value === '') {
339
+                    $where->add($updateQb->expr()->emptyString(
340
+                        $name
341
+                    ));
342
+                } else {
343
+                    $where->add($updateQb->expr()->eq(
344
+                        $name,
345
+                        $updateQb->createNamedParameter($value, $this->getType($value)),
346
+                        $this->getType($value)
347
+                    ));
348
+                }
349
+            }
350
+            $updateQb->where($where);
351
+            $affected = $updateQb->execute();
352
+
353
+            if ($affected === 0 && !empty($updatePreconditionValues)) {
354
+                throw new PreConditionNotMetException();
355
+            }
356
+
357
+            return 0;
358
+        }
359
+    }
360
+
361
+    /**
362
+     * Create an exclusive read+write lock on a table
363
+     *
364
+     * @param string $tableName
365
+     * @throws \BadMethodCallException When trying to acquire a second lock
366
+     * @since 9.1.0
367
+     */
368
+    public function lockTable($tableName) {
369
+        if ($this->lockedTable !== null) {
370
+            throw new \BadMethodCallException('Can not lock a new table until the previous lock is released.');
371
+        }
372
+
373
+        $tableName = $this->tablePrefix . $tableName;
374
+        $this->lockedTable = $tableName;
375
+        $this->adapter->lockTable($tableName);
376
+    }
377
+
378
+    /**
379
+     * Release a previous acquired lock again
380
+     *
381
+     * @since 9.1.0
382
+     */
383
+    public function unlockTable() {
384
+        $this->adapter->unlockTable();
385
+        $this->lockedTable = null;
386
+    }
387
+
388
+    /**
389
+     * returns the error code and message as a string for logging
390
+     * works with DoctrineException
391
+     * @return string
392
+     */
393
+    public function getError() {
394
+        $msg = $this->errorCode() . ': ';
395
+        $errorInfo = $this->errorInfo();
396
+        if (is_array($errorInfo)) {
397
+            $msg .= 'SQLSTATE = '.$errorInfo[0] . ', ';
398
+            $msg .= 'Driver Code = '.$errorInfo[1] . ', ';
399
+            $msg .= 'Driver Message = '.$errorInfo[2];
400
+        }
401
+        return $msg;
402
+    }
403
+
404
+    /**
405
+     * Drop a table from the database if it exists
406
+     *
407
+     * @param string $table table name without the prefix
408
+     */
409
+    public function dropTable($table) {
410
+        $table = $this->tablePrefix . trim($table);
411
+        $schema = $this->getSchemaManager();
412
+        if ($schema->tablesExist([$table])) {
413
+            $schema->dropTable($table);
414
+        }
415
+    }
416
+
417
+    /**
418
+     * Check if a table exists
419
+     *
420
+     * @param string $table table name without the prefix
421
+     * @return bool
422
+     */
423
+    public function tableExists($table) {
424
+        $table = $this->tablePrefix . trim($table);
425
+        $schema = $this->getSchemaManager();
426
+        return $schema->tablesExist([$table]);
427
+    }
428
+
429
+    // internal use
430
+    /**
431
+     * @param string $statement
432
+     * @return string
433
+     */
434
+    protected function replaceTablePrefix($statement) {
435
+        return str_replace('*PREFIX*', $this->tablePrefix, $statement);
436
+    }
437
+
438
+    /**
439
+     * Check if a transaction is active
440
+     *
441
+     * @return bool
442
+     * @since 8.2.0
443
+     */
444
+    public function inTransaction() {
445
+        return $this->getTransactionNestingLevel() > 0;
446
+    }
447
+
448
+    /**
449
+     * Escape a parameter to be used in a LIKE query
450
+     *
451
+     * @param string $param
452
+     * @return string
453
+     */
454
+    public function escapeLikeParameter($param) {
455
+        return addcslashes($param, '\\_%');
456
+    }
457
+
458
+    /**
459
+     * Check whether or not the current database support 4byte wide unicode
460
+     *
461
+     * @return bool
462
+     * @since 11.0.0
463
+     */
464
+    public function supports4ByteText() {
465
+        if (!$this->getDatabasePlatform() instanceof MySqlPlatform) {
466
+            return true;
467
+        }
468
+        return $this->getParams()['charset'] === 'utf8mb4';
469
+    }
470
+
471
+
472
+    /**
473
+     * Create the schema of the connected database
474
+     *
475
+     * @return Schema
476
+     */
477
+    public function createSchema() {
478
+        $schemaManager = new MDB2SchemaManager($this);
479
+        $migrator = $schemaManager->getMigrator();
480
+        return $migrator->createSchema();
481
+    }
482
+
483
+    /**
484
+     * Migrate the database to the given schema
485
+     *
486
+     * @param Schema $toSchema
487
+     */
488
+    public function migrateToSchema(Schema $toSchema) {
489
+        $schemaManager = new MDB2SchemaManager($this);
490
+        $migrator = $schemaManager->getMigrator();
491
+        $migrator->migrate($toSchema);
492
+    }
493 493
 }
Please login to merge, or discard this patch.
lib/public/IDBConnection.php 1 patch
Indentation   +272 added lines, -272 removed lines patch added patch discarded remove patch
@@ -48,276 +48,276 @@
 block discarded – undo
48 48
  * @since 6.0.0
49 49
  */
50 50
 interface IDBConnection {
51
-	public const ADD_MISSING_INDEXES_EVENT = self::class . '::ADD_MISSING_INDEXES';
52
-	public const CHECK_MISSING_INDEXES_EVENT = self::class . '::CHECK_MISSING_INDEXES';
53
-	public const ADD_MISSING_PRIMARY_KEYS_EVENT = self::class . '::ADD_MISSING_PRIMARY_KEYS';
54
-	public const CHECK_MISSING_PRIMARY_KEYS_EVENT = self::class . '::CHECK_MISSING_PRIMARY_KEYS';
55
-	public const ADD_MISSING_COLUMNS_EVENT = self::class . '::ADD_MISSING_COLUMNS';
56
-	public const CHECK_MISSING_COLUMNS_EVENT = self::class . '::CHECK_MISSING_COLUMNS';
57
-
58
-	/**
59
-	 * Gets the QueryBuilder for the connection.
60
-	 *
61
-	 * @return \OCP\DB\QueryBuilder\IQueryBuilder
62
-	 * @since 8.2.0
63
-	 */
64
-	public function getQueryBuilder();
65
-
66
-	/**
67
-	 * Used to abstract the ownCloud database access away
68
-	 * @param string $sql the sql query with ? placeholder for params
69
-	 * @param int $limit the maximum number of rows
70
-	 * @param int $offset from which row we want to start
71
-	 * @return \Doctrine\DBAL\Driver\Statement The prepared statement.
72
-	 * @since 6.0.0
73
-	 */
74
-	public function prepare($sql, $limit = null, $offset = null);
75
-
76
-	/**
77
-	 * Executes an, optionally parameterized, SQL query.
78
-	 *
79
-	 * If the query is parameterized, a prepared statement is used.
80
-	 * If an SQLLogger is configured, the execution is logged.
81
-	 *
82
-	 * @param string $sql The SQL query to execute.
83
-	 * @param string[] $params The parameters to bind to the query, if any.
84
-	 * @param array $types The types the previous parameters are in.
85
-	 * @return \Doctrine\DBAL\Driver\Statement The executed statement.
86
-	 * @since 8.0.0
87
-	 */
88
-	public function executeQuery($sql, array $params = [], $types = []);
89
-
90
-	/**
91
-	 * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
92
-	 * and returns the number of affected rows.
93
-	 *
94
-	 * This method supports PDO binding types as well as DBAL mapping types.
95
-	 *
96
-	 * @param string $sql The SQL query.
97
-	 * @param array $params The query parameters.
98
-	 * @param array $types The parameter types.
99
-	 * @return integer The number of affected rows.
100
-	 * @since 8.0.0
101
-	 *
102
-	 * @deprecated 21.0.0 use executeStatement
103
-	 */
104
-	public function executeUpdate($sql, array $params = [], array $types = []);
105
-
106
-	/**
107
-	 * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
108
-	 * and returns the number of affected rows.
109
-	 *
110
-	 * This method supports PDO binding types as well as DBAL mapping types.
111
-	 *
112
-	 * @param string $sql The SQL query.
113
-	 * @param array $params The query parameters.
114
-	 * @param array $types The parameter types.
115
-	 * @return integer The number of affected rows.
116
-	 * @since 21.0.0
117
-	 */
118
-	public function executeStatement($sql, array $params = [], array $types = []);
119
-
120
-	/**
121
-	 * Used to get the id of the just inserted element
122
-	 * @param string $table the name of the table where we inserted the item
123
-	 * @return int the id of the inserted element
124
-	 * @since 6.0.0
125
-	 */
126
-	public function lastInsertId($table = null);
127
-
128
-	/**
129
-	 * Insert a row if the matching row does not exists. To accomplish proper race condition avoidance
130
-	 * it is needed that there is also a unique constraint on the values. Then this method will
131
-	 * catch the exception and return 0.
132
-	 *
133
-	 * @param string $table The table name (will replace *PREFIX* with the actual prefix)
134
-	 * @param array $input data that should be inserted into the table  (column name => value)
135
-	 * @param array|null $compare List of values that should be checked for "if not exists"
136
-	 *				If this is null or an empty array, all keys of $input will be compared
137
-	 *				Please note: text fields (clob) must not be used in the compare array
138
-	 * @return int number of inserted rows
139
-	 * @throws \Doctrine\DBAL\DBALException
140
-	 * @since 6.0.0 - parameter $compare was added in 8.1.0, return type changed from boolean in 8.1.0
141
-	 * @deprecated 15.0.0 - use unique index and "try { $db->insert() } catch (UniqueConstraintViolationException $e) {}" instead, because it is more reliable and does not have the risk for deadlocks - see https://github.com/nextcloud/server/pull/12371
142
-	 */
143
-	public function insertIfNotExist($table, $input, array $compare = null);
144
-
145
-
146
-	/**
147
-	 *
148
-	 * Insert a row if the row does not exist. Eventual conflicts during insert will be ignored.
149
-	 *
150
-	 * Implementation is not fully finished and should not be used!
151
-	 *
152
-	 * @param string $table The table name (will replace *PREFIX* with the actual prefix)
153
-	 * @param array $values data that should be inserted into the table  (column name => value)
154
-	 * @return int number of inserted rows
155
-	 * @since 16.0.0
156
-	 */
157
-	public function insertIgnoreConflict(string $table,array $values) : int;
158
-
159
-	/**
160
-	 * Insert or update a row value
161
-	 *
162
-	 * @param string $table
163
-	 * @param array $keys (column name => value)
164
-	 * @param array $values (column name => value)
165
-	 * @param array $updatePreconditionValues ensure values match preconditions (column name => value)
166
-	 * @return int number of new rows
167
-	 * @throws \Doctrine\DBAL\DBALException
168
-	 * @throws PreconditionNotMetException
169
-	 * @since 9.0.0
170
-	 */
171
-	public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []);
172
-
173
-	/**
174
-	 * Create an exclusive read+write lock on a table
175
-	 *
176
-	 * Important Note: Due to the nature how locks work on different DBs, it is
177
-	 * only possible to lock one table at a time. You should also NOT start a
178
-	 * transaction while holding a lock.
179
-	 *
180
-	 * @param string $tableName
181
-	 * @since 9.1.0
182
-	 */
183
-	public function lockTable($tableName);
184
-
185
-	/**
186
-	 * Release a previous acquired lock again
187
-	 *
188
-	 * @since 9.1.0
189
-	 */
190
-	public function unlockTable();
191
-
192
-	/**
193
-	 * Start a transaction
194
-	 * @since 6.0.0
195
-	 */
196
-	public function beginTransaction();
197
-
198
-	/**
199
-	 * Check if a transaction is active
200
-	 *
201
-	 * @return bool
202
-	 * @since 8.2.0
203
-	 */
204
-	public function inTransaction();
205
-
206
-	/**
207
-	 * Commit the database changes done during a transaction that is in progress
208
-	 * @since 6.0.0
209
-	 */
210
-	public function commit();
211
-
212
-	/**
213
-	 * Rollback the database changes done during a transaction that is in progress
214
-	 * @since 6.0.0
215
-	 */
216
-	public function rollBack();
217
-
218
-	/**
219
-	 * Gets the error code and message as a string for logging
220
-	 * @return string
221
-	 * @since 6.0.0
222
-	 */
223
-	public function getError();
224
-
225
-	/**
226
-	 * Fetch the SQLSTATE associated with the last database operation.
227
-	 *
228
-	 * @return integer The last error code.
229
-	 * @since 8.0.0
230
-	 */
231
-	public function errorCode();
232
-
233
-	/**
234
-	 * Fetch extended error information associated with the last database operation.
235
-	 *
236
-	 * @return array The last error information.
237
-	 * @since 8.0.0
238
-	 */
239
-	public function errorInfo();
240
-
241
-	/**
242
-	 * Establishes the connection with the database.
243
-	 *
244
-	 * @return bool
245
-	 * @since 8.0.0
246
-	 */
247
-	public function connect();
248
-
249
-	/**
250
-	 * Close the database connection
251
-	 * @since 8.0.0
252
-	 */
253
-	public function close();
254
-
255
-	/**
256
-	 * Quotes a given input parameter.
257
-	 *
258
-	 * @param mixed $input Parameter to be quoted.
259
-	 * @param int $type Type of the parameter.
260
-	 * @return string The quoted parameter.
261
-	 * @since 8.0.0
262
-	 */
263
-	public function quote($input, $type = IQueryBuilder::PARAM_STR);
264
-
265
-	/**
266
-	 * Gets the DatabasePlatform instance that provides all the metadata about
267
-	 * the platform this driver connects to.
268
-	 *
269
-	 * @return \Doctrine\DBAL\Platforms\AbstractPlatform The database platform.
270
-	 * @since 8.0.0
271
-	 */
272
-	public function getDatabasePlatform();
273
-
274
-	/**
275
-	 * Drop a table from the database if it exists
276
-	 *
277
-	 * @param string $table table name without the prefix
278
-	 * @since 8.0.0
279
-	 */
280
-	public function dropTable($table);
281
-
282
-	/**
283
-	 * Check if a table exists
284
-	 *
285
-	 * @param string $table table name without the prefix
286
-	 * @return bool
287
-	 * @since 8.0.0
288
-	 */
289
-	public function tableExists($table);
290
-
291
-	/**
292
-	 * Escape a parameter to be used in a LIKE query
293
-	 *
294
-	 * @param string $param
295
-	 * @return string
296
-	 * @since 9.0.0
297
-	 */
298
-	public function escapeLikeParameter($param);
299
-
300
-	/**
301
-	 * Check whether or not the current database support 4byte wide unicode
302
-	 *
303
-	 * @return bool
304
-	 * @since 11.0.0
305
-	 */
306
-	public function supports4ByteText();
307
-
308
-	/**
309
-	 * Create the schema of the connected database
310
-	 *
311
-	 * @return Schema
312
-	 * @since 13.0.0
313
-	 */
314
-	public function createSchema();
315
-
316
-	/**
317
-	 * Migrate the database to the given schema
318
-	 *
319
-	 * @param Schema $toSchema
320
-	 * @since 13.0.0
321
-	 */
322
-	public function migrateToSchema(Schema $toSchema);
51
+    public const ADD_MISSING_INDEXES_EVENT = self::class . '::ADD_MISSING_INDEXES';
52
+    public const CHECK_MISSING_INDEXES_EVENT = self::class . '::CHECK_MISSING_INDEXES';
53
+    public const ADD_MISSING_PRIMARY_KEYS_EVENT = self::class . '::ADD_MISSING_PRIMARY_KEYS';
54
+    public const CHECK_MISSING_PRIMARY_KEYS_EVENT = self::class . '::CHECK_MISSING_PRIMARY_KEYS';
55
+    public const ADD_MISSING_COLUMNS_EVENT = self::class . '::ADD_MISSING_COLUMNS';
56
+    public const CHECK_MISSING_COLUMNS_EVENT = self::class . '::CHECK_MISSING_COLUMNS';
57
+
58
+    /**
59
+     * Gets the QueryBuilder for the connection.
60
+     *
61
+     * @return \OCP\DB\QueryBuilder\IQueryBuilder
62
+     * @since 8.2.0
63
+     */
64
+    public function getQueryBuilder();
65
+
66
+    /**
67
+     * Used to abstract the ownCloud database access away
68
+     * @param string $sql the sql query with ? placeholder for params
69
+     * @param int $limit the maximum number of rows
70
+     * @param int $offset from which row we want to start
71
+     * @return \Doctrine\DBAL\Driver\Statement The prepared statement.
72
+     * @since 6.0.0
73
+     */
74
+    public function prepare($sql, $limit = null, $offset = null);
75
+
76
+    /**
77
+     * Executes an, optionally parameterized, SQL query.
78
+     *
79
+     * If the query is parameterized, a prepared statement is used.
80
+     * If an SQLLogger is configured, the execution is logged.
81
+     *
82
+     * @param string $sql The SQL query to execute.
83
+     * @param string[] $params The parameters to bind to the query, if any.
84
+     * @param array $types The types the previous parameters are in.
85
+     * @return \Doctrine\DBAL\Driver\Statement The executed statement.
86
+     * @since 8.0.0
87
+     */
88
+    public function executeQuery($sql, array $params = [], $types = []);
89
+
90
+    /**
91
+     * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
92
+     * and returns the number of affected rows.
93
+     *
94
+     * This method supports PDO binding types as well as DBAL mapping types.
95
+     *
96
+     * @param string $sql The SQL query.
97
+     * @param array $params The query parameters.
98
+     * @param array $types The parameter types.
99
+     * @return integer The number of affected rows.
100
+     * @since 8.0.0
101
+     *
102
+     * @deprecated 21.0.0 use executeStatement
103
+     */
104
+    public function executeUpdate($sql, array $params = [], array $types = []);
105
+
106
+    /**
107
+     * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
108
+     * and returns the number of affected rows.
109
+     *
110
+     * This method supports PDO binding types as well as DBAL mapping types.
111
+     *
112
+     * @param string $sql The SQL query.
113
+     * @param array $params The query parameters.
114
+     * @param array $types The parameter types.
115
+     * @return integer The number of affected rows.
116
+     * @since 21.0.0
117
+     */
118
+    public function executeStatement($sql, array $params = [], array $types = []);
119
+
120
+    /**
121
+     * Used to get the id of the just inserted element
122
+     * @param string $table the name of the table where we inserted the item
123
+     * @return int the id of the inserted element
124
+     * @since 6.0.0
125
+     */
126
+    public function lastInsertId($table = null);
127
+
128
+    /**
129
+     * Insert a row if the matching row does not exists. To accomplish proper race condition avoidance
130
+     * it is needed that there is also a unique constraint on the values. Then this method will
131
+     * catch the exception and return 0.
132
+     *
133
+     * @param string $table The table name (will replace *PREFIX* with the actual prefix)
134
+     * @param array $input data that should be inserted into the table  (column name => value)
135
+     * @param array|null $compare List of values that should be checked for "if not exists"
136
+     *				If this is null or an empty array, all keys of $input will be compared
137
+     *				Please note: text fields (clob) must not be used in the compare array
138
+     * @return int number of inserted rows
139
+     * @throws \Doctrine\DBAL\DBALException
140
+     * @since 6.0.0 - parameter $compare was added in 8.1.0, return type changed from boolean in 8.1.0
141
+     * @deprecated 15.0.0 - use unique index and "try { $db->insert() } catch (UniqueConstraintViolationException $e) {}" instead, because it is more reliable and does not have the risk for deadlocks - see https://github.com/nextcloud/server/pull/12371
142
+     */
143
+    public function insertIfNotExist($table, $input, array $compare = null);
144
+
145
+
146
+    /**
147
+     *
148
+     * Insert a row if the row does not exist. Eventual conflicts during insert will be ignored.
149
+     *
150
+     * Implementation is not fully finished and should not be used!
151
+     *
152
+     * @param string $table The table name (will replace *PREFIX* with the actual prefix)
153
+     * @param array $values data that should be inserted into the table  (column name => value)
154
+     * @return int number of inserted rows
155
+     * @since 16.0.0
156
+     */
157
+    public function insertIgnoreConflict(string $table,array $values) : int;
158
+
159
+    /**
160
+     * Insert or update a row value
161
+     *
162
+     * @param string $table
163
+     * @param array $keys (column name => value)
164
+     * @param array $values (column name => value)
165
+     * @param array $updatePreconditionValues ensure values match preconditions (column name => value)
166
+     * @return int number of new rows
167
+     * @throws \Doctrine\DBAL\DBALException
168
+     * @throws PreconditionNotMetException
169
+     * @since 9.0.0
170
+     */
171
+    public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []);
172
+
173
+    /**
174
+     * Create an exclusive read+write lock on a table
175
+     *
176
+     * Important Note: Due to the nature how locks work on different DBs, it is
177
+     * only possible to lock one table at a time. You should also NOT start a
178
+     * transaction while holding a lock.
179
+     *
180
+     * @param string $tableName
181
+     * @since 9.1.0
182
+     */
183
+    public function lockTable($tableName);
184
+
185
+    /**
186
+     * Release a previous acquired lock again
187
+     *
188
+     * @since 9.1.0
189
+     */
190
+    public function unlockTable();
191
+
192
+    /**
193
+     * Start a transaction
194
+     * @since 6.0.0
195
+     */
196
+    public function beginTransaction();
197
+
198
+    /**
199
+     * Check if a transaction is active
200
+     *
201
+     * @return bool
202
+     * @since 8.2.0
203
+     */
204
+    public function inTransaction();
205
+
206
+    /**
207
+     * Commit the database changes done during a transaction that is in progress
208
+     * @since 6.0.0
209
+     */
210
+    public function commit();
211
+
212
+    /**
213
+     * Rollback the database changes done during a transaction that is in progress
214
+     * @since 6.0.0
215
+     */
216
+    public function rollBack();
217
+
218
+    /**
219
+     * Gets the error code and message as a string for logging
220
+     * @return string
221
+     * @since 6.0.0
222
+     */
223
+    public function getError();
224
+
225
+    /**
226
+     * Fetch the SQLSTATE associated with the last database operation.
227
+     *
228
+     * @return integer The last error code.
229
+     * @since 8.0.0
230
+     */
231
+    public function errorCode();
232
+
233
+    /**
234
+     * Fetch extended error information associated with the last database operation.
235
+     *
236
+     * @return array The last error information.
237
+     * @since 8.0.0
238
+     */
239
+    public function errorInfo();
240
+
241
+    /**
242
+     * Establishes the connection with the database.
243
+     *
244
+     * @return bool
245
+     * @since 8.0.0
246
+     */
247
+    public function connect();
248
+
249
+    /**
250
+     * Close the database connection
251
+     * @since 8.0.0
252
+     */
253
+    public function close();
254
+
255
+    /**
256
+     * Quotes a given input parameter.
257
+     *
258
+     * @param mixed $input Parameter to be quoted.
259
+     * @param int $type Type of the parameter.
260
+     * @return string The quoted parameter.
261
+     * @since 8.0.0
262
+     */
263
+    public function quote($input, $type = IQueryBuilder::PARAM_STR);
264
+
265
+    /**
266
+     * Gets the DatabasePlatform instance that provides all the metadata about
267
+     * the platform this driver connects to.
268
+     *
269
+     * @return \Doctrine\DBAL\Platforms\AbstractPlatform The database platform.
270
+     * @since 8.0.0
271
+     */
272
+    public function getDatabasePlatform();
273
+
274
+    /**
275
+     * Drop a table from the database if it exists
276
+     *
277
+     * @param string $table table name without the prefix
278
+     * @since 8.0.0
279
+     */
280
+    public function dropTable($table);
281
+
282
+    /**
283
+     * Check if a table exists
284
+     *
285
+     * @param string $table table name without the prefix
286
+     * @return bool
287
+     * @since 8.0.0
288
+     */
289
+    public function tableExists($table);
290
+
291
+    /**
292
+     * Escape a parameter to be used in a LIKE query
293
+     *
294
+     * @param string $param
295
+     * @return string
296
+     * @since 9.0.0
297
+     */
298
+    public function escapeLikeParameter($param);
299
+
300
+    /**
301
+     * Check whether or not the current database support 4byte wide unicode
302
+     *
303
+     * @return bool
304
+     * @since 11.0.0
305
+     */
306
+    public function supports4ByteText();
307
+
308
+    /**
309
+     * Create the schema of the connected database
310
+     *
311
+     * @return Schema
312
+     * @since 13.0.0
313
+     */
314
+    public function createSchema();
315
+
316
+    /**
317
+     * Migrate the database to the given schema
318
+     *
319
+     * @param Schema $toSchema
320
+     * @since 13.0.0
321
+     */
322
+    public function migrateToSchema(Schema $toSchema);
323 323
 }
Please login to merge, or discard this patch.