]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/backend/ADODB_mysql.php
Use HTML 5 DOCTYPE; force CHARSET=utf-8
[SourceForge/phpwiki.git] / lib / WikiDB / backend / ADODB_mysql.php
1 <?php
2
3 require_once 'lib/WikiDB/backend/ADODB.php';
4
5 /*
6  * PROBLEM: mysql seems to be the simpliest (or most stupid) db on earth.
7  * (tested with 4.0.18)
8  * See http://sql-info.de/mysql/gotchas.html for mysql specific quirks.
9  *
10  * Whenever a table is write-locked, you cannot even write to other unrelated
11  * tables. So it seems that we have to lock all tables!
12  * As workaround we try it with application locks, uniquely named locks,
13  * to prevent from concurrent writes of locks with the same name.
14  * The lock name is a strcat of the involved tables.
15  *
16  * See also http://use.perl.org/~Smylers/journal/34246 for strict mode and warnings.
17  */
18 define('DO_APP_LOCK', true);
19 define('DO_FULL_LOCK', false);
20
21 /**
22  * WikiDB layer for ADODB-mysql, called by lib/WikiDB/ADODB.php.
23  * Now with support for the newer ADODB library, the ADODB extension library
24  * and more database drivers.
25  * To use transactions use the mysqlt driver: "mysqlt:..."
26  *
27  * @author: Lawrence Akka, Reini Urban
28  */
29 class WikiDB_backend_ADODB_mysql
30     extends WikiDB_backend_ADODB
31 {
32     /**
33      * Constructor.
34      */
35     function WikiDB_backend_ADODB_mysql($dbparams)
36     {
37         $this->WikiDB_backend_ADODB($dbparams);
38         if (!$this->_dbh->_connectionID) return;
39
40         $this->_serverinfo = $this->_dbh->ServerInfo();
41         if (!empty($this->_serverinfo['version'])) {
42             $arr = explode('.', $this->_serverinfo['version']);
43             $this->_serverinfo['version'] = (string)(($arr[0] * 100) + $arr[1]) . "." . (integer)$arr[2];
44         }
45         if ($this->_serverinfo['version'] < 323.0) {
46             // Older MySQL's don't have CASE WHEN ... END
47             $this->_expressions['maxmajor'] = "MAX(IF(minor_edit=0,version,0))";
48             $this->_expressions['maxminor'] = "MAX(IF(minor_edit<>0,version,0))";
49         }
50
51         // esp. needed for utf databases
52         if ($this->_serverinfo['version'] > 401.0) {
53             mysql_query("SET NAMES 'UTF-8'");
54         }
55     }
56
57     /**
58      * Kill timed out processes. ( so far only called on about every 50-th save. )
59      */
60     function _timeout()
61     {
62         if (empty($this->_dbparams['timeout'])) return;
63         $result = mysql_query("SHOW processlist");
64         while ($row = mysql_fetch_array($result)) {
65             if ($row["db"] == $this->_dsn['database']
66                 and $row["User"] == $this->_dsn['username']
67                     and $row["Time"] > $this->_dbparams['timeout']
68                         and $row["Command"] == "Sleep"
69             ) {
70                 $process_id = $row["Id"];
71                 mysql_query("KILL $process_id");
72             }
73         }
74     }
75
76     /**
77      * Pack tables.
78      */
79     function optimize()
80     {
81         $dbh = &$this->_dbh;
82         $this->_timeout();
83         foreach ($this->_table_names as $table) {
84             $dbh->Execute("OPTIMIZE TABLE $table");
85         }
86         return 1;
87     }
88
89     /**
90      * Lock tables. As fine-grained application lock, which locks only the
91      * same transaction (conflicting updates and edits), and as full table
92      * write lock.
93      *
94      * New: which tables as params,
95      *      support nested locks via app locks
96      */
97     function _lock_tables($tables, $write_lock = true)
98     {
99         if (!$tables) return;
100         if (DO_APP_LOCK) {
101             $lock = join('-', $tables);
102             $result = $this->_dbh->GetRow("SELECT GET_LOCK('$lock',10)");
103             if (!$result or $result[0] == 0) {
104                 trigger_error("WARNING: Couldn't obtain application lock " . $lock . "\n<br />",
105                     E_USER_WARNING);
106                 return;
107             }
108         }
109         if (DO_FULL_LOCK) {
110             // if this is not enough:
111             $lock_type = $write_lock ? "WRITE" : "READ";
112             foreach ($this->_table_names as $key => $table) {
113                 $locks[] = "$table $lock_type";
114             }
115             $this->_dbh->Execute("LOCK TABLES " . join(",", $locks));
116         }
117     }
118
119     /**
120      * Release the locks.
121      * Support nested locks
122      */
123     function _unlock_tables($tables)
124     {
125         if (!$tables) {
126             $this->_dbh->Execute("UNLOCK TABLES");
127             return;
128         }
129         if (DO_APP_LOCK) {
130             $lock = join('-', $tables);
131             $result = $this->_dbh->Execute("SELECT RELEASE_LOCK('$lock')");
132         }
133         if (DO_FULL_LOCK) {
134             // if this is not enough:
135             $this->_dbh->Execute("UNLOCK TABLES");
136         }
137     }
138
139     function increaseHitCount($pagename)
140     {
141         $dbh = &$this->_dbh;
142         // Hits is the only thing we can update in a fast manner.
143         // Note that this will fail silently if the page does not
144         // have a record in the page table.  Since it's just the
145         // hit count, who cares?
146         // LIMIT since 3.23
147         $dbh->Execute(sprintf("UPDATE LOW_PRIORITY %s SET hits=hits+1 WHERE pagename=%s %s",
148                 $this->_table_names['page_tbl'],
149                 $dbh->qstr($pagename),
150                 ($this->_serverinfo['version'] >= 323.0) ? "LIMIT 1" : "")
151         );
152         return;
153     }
154
155     function _get_pageid($pagename, $create_if_missing = false)
156     {
157
158         // check id_cache
159         global $request;
160         $cache =& $request->_dbi->_cache->_id_cache;
161         if (isset($cache[$pagename])) {
162             if ($cache[$pagename] or !$create_if_missing) {
163                 return $cache[$pagename];
164             }
165         }
166
167         // attributes play this game.
168         if ($pagename === '') return 0;
169
170         $dbh = &$this->_dbh;
171         $page_tbl = $this->_table_names['page_tbl'];
172         $query = sprintf("SELECT id FROM $page_tbl WHERE pagename=%s",
173             $dbh->qstr($pagename));
174         if (!$create_if_missing) {
175             $row = $dbh->GetRow($query);
176             return $row ? $row[0] : false;
177         }
178         $row = $dbh->GetRow($query);
179         if (!$row) {
180             // have auto-incrementing, atomic version
181             $rs = $dbh->Execute(sprintf("INSERT INTO $page_tbl"
182                     . " (id,pagename)"
183                     . " VALUES(NULL,%s)",
184                 $dbh->qstr($pagename)));
185             $id = $dbh->_insertid();
186         } else {
187             $id = $row[0];
188         }
189         assert($id);
190         return $id;
191     }
192
193     /**
194      * Create a new revision of a page.
195      */
196     function set_versiondata($pagename, $version, $data)
197     {
198         $dbh = &$this->_dbh;
199         $version_tbl = $this->_table_names['version_tbl'];
200
201         $minor_edit = (int)!empty($data['is_minor_edit']);
202         unset($data['is_minor_edit']);
203
204         $mtime = (int)$data['mtime'];
205         unset($data['mtime']);
206         assert(!empty($mtime));
207
208         @$content = (string)$data['%content'];
209         unset($data['%content']);
210         unset($data['%pagedata']);
211
212         $this->lock(array('page', 'recent', 'version', 'nonempty'));
213         $dbh->BeginTrans();
214         $dbh->CommitLock($version_tbl);
215         $id = $this->_get_pageid($pagename, true);
216         $backend_type = $this->backendType();
217         // optimize: mysql can do this with one REPLACE INTO.
218         $rs = $dbh->Execute(sprintf("REPLACE INTO $version_tbl"
219                 . " (id,version,mtime,minor_edit,content,versiondata)"
220                 . " VALUES(%d,%d,%d,%d,%s,%s)",
221             $id, $version, $mtime, $minor_edit,
222             $dbh->qstr($content),
223             $dbh->qstr($this->_serialize($data))));
224         $this->_update_recent_table($id);
225         $this->_update_nonempty_table($id);
226         if ($rs) $dbh->CommitTrans();
227         else $dbh->RollbackTrans();
228         $this->unlock(array('page', 'recent', 'version', 'nonempty'));
229     }
230
231 }
232
233 // Local Variables:
234 // mode: php
235 // tab-width: 8
236 // c-basic-offset: 4
237 // c-hanging-comment-ender-p: nil
238 // indent-tabs-mode: nil
239 // End: