Generate file attachment transactions for explicit Remarkup attachments on common...
[phabricator.git] / src / applications / cache / PhabricatorKeyValueDatabaseCache.php
blobd6674c56718635804fbbb3b5f19822a5650523f7
1 <?php
3 final class PhabricatorKeyValueDatabaseCache
4 extends PhutilKeyValueCache {
6 const CACHE_FORMAT_RAW = 'raw';
7 const CACHE_FORMAT_DEFLATE = 'deflate';
9 public function setKeys(array $keys, $ttl = null) {
10 if (PhabricatorEnv::isReadOnly()) {
11 return;
14 if ($keys) {
15 $map = $this->digestKeys(array_keys($keys));
16 $conn_w = $this->establishConnection('w');
18 $sql = array();
19 foreach ($map as $key => $hash) {
20 $value = $keys[$key];
22 list($format, $storage_value) = $this->willWriteValue($key, $value);
24 $sql[] = qsprintf(
25 $conn_w,
26 '(%s, %s, %s, %B, %d, %nd)',
27 $hash,
28 $key,
29 $format,
30 $storage_value,
31 time(),
32 $ttl ? (time() + $ttl) : null);
35 $guard = AphrontWriteGuard::beginScopedUnguardedWrites();
36 foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) {
37 queryfx(
38 $conn_w,
39 'INSERT INTO %T
40 (cacheKeyHash, cacheKey, cacheFormat, cacheData,
41 cacheCreated, cacheExpires) VALUES %LQ
42 ON DUPLICATE KEY UPDATE
43 cacheKey = VALUES(cacheKey),
44 cacheFormat = VALUES(cacheFormat),
45 cacheData = VALUES(cacheData),
46 cacheCreated = VALUES(cacheCreated),
47 cacheExpires = VALUES(cacheExpires)',
48 $this->getTableName(),
49 $chunk);
51 unset($guard);
54 return $this;
57 public function getKeys(array $keys) {
58 $results = array();
59 if ($keys) {
60 $map = $this->digestKeys($keys);
62 $rows = queryfx_all(
63 $this->establishConnection('r'),
64 'SELECT * FROM %T WHERE cacheKeyHash IN (%Ls)',
65 $this->getTableName(),
66 $map);
67 $rows = ipull($rows, null, 'cacheKey');
69 foreach ($keys as $key) {
70 if (empty($rows[$key])) {
71 continue;
74 $row = $rows[$key];
76 if ($row['cacheExpires'] && ($row['cacheExpires'] < time())) {
77 continue;
80 try {
81 $results[$key] = $this->didReadValue(
82 $row['cacheFormat'],
83 $row['cacheData']);
84 } catch (Exception $ex) {
85 // Treat this as a cache miss.
86 phlog($ex);
91 return $results;
94 public function deleteKeys(array $keys) {
95 if ($keys) {
96 $map = $this->digestKeys($keys);
97 queryfx(
98 $this->establishConnection('w'),
99 'DELETE FROM %T WHERE cacheKeyHash IN (%Ls)',
100 $this->getTableName(),
101 $map);
104 return $this;
107 public function destroyCache() {
108 queryfx(
109 $this->establishConnection('w'),
110 'DELETE FROM %T',
111 $this->getTableName());
112 return $this;
116 /* -( Raw Cache Access )--------------------------------------------------- */
119 public function establishConnection($mode) {
120 // TODO: This is the only concrete table we have on the database right
121 // now.
122 return id(new PhabricatorMarkupCache())->establishConnection($mode);
125 public function getTableName() {
126 return 'cache_general';
130 /* -( Implementation )----------------------------------------------------- */
133 private function digestKeys(array $keys) {
134 $map = array();
135 foreach ($keys as $key) {
136 $map[$key] = PhabricatorHash::digestForIndex($key);
138 return $map;
141 private function willWriteValue($key, $value) {
142 if (!is_string($value)) {
143 throw new Exception(pht('Only strings may be written to the DB cache!'));
146 static $can_deflate;
147 if ($can_deflate === null) {
148 $can_deflate = function_exists('gzdeflate');
151 if ($can_deflate) {
152 $deflated = PhabricatorCaches::maybeDeflateData($value);
153 if ($deflated !== null) {
154 return array(self::CACHE_FORMAT_DEFLATE, $deflated);
158 return array(self::CACHE_FORMAT_RAW, $value);
161 private function didReadValue($format, $value) {
162 switch ($format) {
163 case self::CACHE_FORMAT_RAW:
164 return $value;
165 case self::CACHE_FORMAT_DEFLATE:
166 return PhabricatorCaches::inflateData($value);
167 default:
168 throw new Exception(pht('Unknown cache format.'));