Subversion Repositories oidplus

Rev

Rev 496 | Rev 511 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
295 daniel-mar 1
<?php
2
 
3
/*
4
 * OIDplus 2.0
5
 * Copyright 2019 Daniel Marschall, ViaThinkSoft
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
 
20
abstract class OIDplusDatabaseConnection {
21
        protected /*bool*/ $connected = false;
22
        protected /*?bool*/ $html = null;
23
        protected /*?string*/ $last_query = null;
502 daniel-mar 24
        protected /*bool*/ $slangDetectionDone = false;
295 daniel-mar 25
 
348 daniel-mar 26
        public abstract static function getPlugin(): OIDplusDatabasePlugin;
295 daniel-mar 27
        protected abstract function doQuery(string $sql, /*?array*/ $prepared_args=null): OIDplusQueryResult;
28
        public abstract function error(): string;
29
        public abstract function transaction_begin()/*: void*/;
30
        public abstract function transaction_commit()/*: void*/;
31
        public abstract function transaction_rollback()/*: void*/;
32
        public abstract function transaction_supported(): bool;
33
        public abstract function transaction_level(): int;
34
        protected abstract function doConnect()/*: void*/;
35
        protected abstract function doDisconnect()/*: void*/;
36
 
37
        public function insert_id(): int {
38
                // This is the "fallback" variant. If your database provider (e.g. PDO) supports
39
                // a function to detect the last inserted id, please override this
40
                // function in order to use that specialized function (since it is usually
41
                // more reliable).
316 daniel-mar 42
                return $this->getSlang()->insert_id($this);
295 daniel-mar 43
        }
44
 
45
        public final function query(string $sql, /*?array*/ $prepared_args=null): OIDplusQueryResult {
46
 
47
                $query_logfile = OIDplus::baseConfig()->getValue('QUERY_LOGFILE', '');
48
                if (!empty($query_logfile)) {
49
                        $ts = explode(" ",microtime());
50
                        $ts = date("Y-m-d H:i:s",$ts[1]).substr((string)$ts[0],1,4);
51
                        static $log_session_id = "";
52
                        if (empty($log_session_id)) {
53
                                $log_session_id = rand(10000,99999);
54
                        }
55
                        $file = isset($_SERVER['REQUEST_URI']) ? ' | '.$_SERVER['REQUEST_URI'] : '';
56
                        file_put_contents($query_logfile, "$ts <$log_session_id$file> $sql\n", FILE_APPEND);
57
                }
58
 
59
                $this->last_query = $sql;
60
                $sql = str_replace('###', OIDplus::baseConfig()->getValue('TABLENAME_PREFIX', ''), $sql);
502 daniel-mar 61
 
62
                if ($this->slangDetectionDone) {
63
                        $slang = $this->getSlang();
64
                        if ($slang) {
65
                                $sql = $slang->filterQuery($sql);
66
                        }
67
                }
68
 
295 daniel-mar 69
                return $this->doQuery($sql, $prepared_args);
70
        }
71
 
72
        public final function connect()/*: void*/ {
73
                if ($this->connected) return;
74
                $this->beforeConnect();
75
                $this->doConnect();
76
                $this->connected = true;
77
                register_shutdown_function(array($this, 'disconnect'));
318 daniel-mar 78
                $this->afterConnectMandatory();
295 daniel-mar 79
                $this->afterConnect();
80
        }
81
 
82
        public final function disconnect()/*: void*/ {
83
                if (!$this->connected) return;
84
                $this->beforeDisconnect();
85
                $this->doDisconnect();
86
                $this->connected = false;
87
                $this->afterDisconnect();
88
        }
89
 
90
        public function natOrder($fieldname, $order='asc'): string {
325 daniel-mar 91
                $slang = $this->getSlang();
92
                if (!is_null($slang)) {
93
                        return $slang->natOrder($fieldname, $order);
295 daniel-mar 94
                } else {
95
                        $order = strtolower($order);
96
                        if (($order != 'asc') && ($order != 'desc')) {
360 daniel-mar 97
                                throw new OIDplusException(_L('Invalid order "%1" (needs to be "asc" or "desc")',$order));
295 daniel-mar 98
                        }
99
 
100
                        // For (yet) unsupported DBMS, we do not offer natural sort
101
                        return "$fieldname $order";
102
                }
103
        }
104
 
105
        protected function beforeDisconnect()/*: void*/ {}
106
 
107
        protected function afterDisconnect()/*: void*/ {}
108
 
109
        protected function beforeConnect()/*: void*/ {}
110
 
318 daniel-mar 111
        protected function afterConnect()/*: void*/ {}
112
 
113
        private function afterConnectMandatory()/*: void*/ {
295 daniel-mar 114
                // Check if the config table exists. This is important because the database version is stored in it
115
                $this->initRequireTables(array('config'));
116
 
117
                // Do the database tables need an update?
118
                // It is important that we do it immediately after connecting,
119
                // because the database structure might change and therefore various things might fail.
120
                // Note: The config setting "database_version" is inserted in setup/sql/...sql, not in the OIDplus core init
121
 
122
                $res = $this->query("SELECT value FROM ###config WHERE name = 'database_version'");
123
                $row = $res->fetch_array();
124
                if ($row == null) {
360 daniel-mar 125
                        throw new OIDplusConfigInitializationException(_L('Cannot determine database version (the entry "database_version" inside the table "###config" is probably missing)'));
295 daniel-mar 126
                }
127
                $version = $row['value'];
128
                if (!is_numeric($version) || ($version < 200) || ($version > 999)) {
360 daniel-mar 129
                        throw new OIDplusConfigInitializationException(_L('Entry "database_version" inside the table "###config" seems to be wrong (expect number between 200 and 999)'));
295 daniel-mar 130
                }
502 daniel-mar 131
 
496 daniel-mar 132
                $update_files = glob(OIDplus::localpath().'includes/db_updates/update*.inc.php');
386 daniel-mar 133
                foreach ($update_files as $update_file) {
134
                        include_once $update_file;
135
                }
136
                while (function_exists($function_name = "oidplus_dbupdate_".$version."_".($version+1))) {
295 daniel-mar 137
                        $prev_version = $version;
386 daniel-mar 138
                        $function_name($this, $version);
295 daniel-mar 139
                        if ($version != $prev_version+1) {
140
                                // This should usually not happen, since the update-file should increase the version
141
                                // or throw an Exception by itself
360 daniel-mar 142
                                throw new OIDplusException(_L('Database update %1 -> %2 failed (script reports new version to be %3)',$prev_version,$prev_version+1,$version));
295 daniel-mar 143
                        }
144
                }
145
 
146
                // Now that our database is up-to-date, we check if database tables are existing
147
                // without config table, because it was checked above
148
                $this->initRequireTables(array('objects', 'asn1id', 'iri', 'ra'/*, 'config'*/));
316 daniel-mar 149
 
150
                // In case an auto-detection of the slang is required (for generic providers like PDO or ODBC),
151
                // we must not be inside a transaction, because the detection requires intentionally submitting
152
                // invalid queries to detect the correct DBMS. If we would be inside a transaction, providers like
153
                // PDO would automatically roll-back. Therefore, we detect the slang right at the beginning,
154
                // before any transaction is used.
155
                $this->getSlang();
295 daniel-mar 156
        }
157
 
158
        private function initRequireTables($tableNames)/*: void*/ {
159
                $msgs = array();
160
                foreach ($tableNames as $tableName) {
161
                        $prefix = OIDplus::baseConfig()->getValue('TABLENAME_PREFIX', '');
162
                        if (!$this->tableExists($prefix.$tableName)) {
360 daniel-mar 163
                                $msgs[] = _L('Table %1 is missing!',$prefix.$tableName);
295 daniel-mar 164
                        }
165
                }
166
                if (count($msgs) > 0) {
167
                        throw new OIDplusConfigInitializationException(implode("\n\n",$msgs));
168
                }
169
        }
170
 
171
        public function tableExists($tableName): bool {
172
                try {
318 daniel-mar 173
                        // Attention: This query could interrupt transactions if Rollback-On-Error is enabled
295 daniel-mar 174
                        $this->query("select 0 from ".$tableName." where 1=0");
175
                        return true;
176
                } catch (Exception $e) {
177
                        return false;
178
                }
179
        }
180
 
181
        public function isConnected(): bool {
182
                return $this->connected;
183
        }
184
 
185
        public function init($html = true)/*: void*/ {
186
                $this->html = $html;
187
        }
188
 
189
        public function sqlDate(): string {
325 daniel-mar 190
                $slang = $this->getSlang();
191
                if (!is_null($slang)) {
192
                        return $slang->sqlDate();
295 daniel-mar 193
                } else {
489 daniel-mar 194
                        return "'" . date('Y-m-d H:i:s') . "'";
295 daniel-mar 195
                }
196
        }
197
 
502 daniel-mar 198
        protected function doGetSlang(bool $mustExist=true)/*: ?OIDplusSqlSlangPlugin*/ {
199
                $res = null;
326 daniel-mar 200
 
502 daniel-mar 201
                if (OIDplus::baseConfig()->exists('FORCE_DBMS_SLANG')) {
202
                        $name = OIDplus::baseConfig()->getValue('FORCE_DBMS_SLANG', '');
203
                        $res = OIDplus::getSqlSlangPlugin($name);
204
                        if ($mustExist && is_null($res)) {
205
                                throw new OIDplusConfigInitializationException(_L('Enforced SQL slang (via setting FORCE_DBMS_SLANG) "%1" does not exist.',$name));
206
                        }
207
                } else {
208
                        foreach (OIDplus::getSqlSlangPlugins() as $plugin) {
209
                                if ($plugin->detect($this)) {
210
                                        if (OIDplus::baseConfig()->getValue('DEBUG') && !is_null($res)) {
211
                                                throw new OIDplusException(_L('DB-Slang detection failed: Multiple slangs were detected. Use base config setting FORCE_DBMS_SLANG to define one.'));
212
                                        }
213
 
214
                                        $res = $plugin;
215
 
216
                                        if (!OIDplus::baseConfig()->getValue('DEBUG')) {
295 daniel-mar 217
                                                break;
218
                                        }
219
                                }
220
                        }
502 daniel-mar 221
                        if ($mustExist && is_null($res)) {
222
                                throw new OIDplusException(_L('Cannot determine the SQL slang of your DBMS. Your DBMS is probably not supported.'));
223
                        }
295 daniel-mar 224
                }
225
 
502 daniel-mar 226
                return $res;
227
        }
228
 
229
        public final function getSlang(bool $mustExist=true)/*: ?OIDplusSqlSlangPlugin*/ {
230
                static /*?OIDplusSqlSlangPlugin*/ $slangCache = null;
231
 
232
                if ($this->slangDetectionDone) {
233
                        return $slangCache;
234
                }
235
 
236
                $slangCache = $this->doGetSlang();
237
                $this->slangDetectionDone = true;
326 daniel-mar 238
                return $slangCache;
295 daniel-mar 239
        }
489 daniel-mar 240
}