Subversion Repositories oidplus

Rev

Rev 326 | Rev 360 | 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;
24
 
348 daniel-mar 25
        public abstract static function getPlugin(): OIDplusDatabasePlugin;
295 daniel-mar 26
        protected abstract function doQuery(string $sql, /*?array*/ $prepared_args=null): OIDplusQueryResult;
27
        public abstract function error(): string;
28
        public abstract function transaction_begin()/*: void*/;
29
        public abstract function transaction_commit()/*: void*/;
30
        public abstract function transaction_rollback()/*: void*/;
31
        public abstract function transaction_supported(): bool;
32
        public abstract function transaction_level(): int;
33
        protected abstract function doConnect()/*: void*/;
34
        protected abstract function doDisconnect()/*: void*/;
35
 
36
        public function insert_id(): int {
37
                // This is the "fallback" variant. If your database provider (e.g. PDO) supports
38
                // a function to detect the last inserted id, please override this
39
                // function in order to use that specialized function (since it is usually
40
                // more reliable).
316 daniel-mar 41
                return $this->getSlang()->insert_id($this);
295 daniel-mar 42
        }
43
 
44
        public final function query(string $sql, /*?array*/ $prepared_args=null): OIDplusQueryResult {
45
 
46
                $query_logfile = OIDplus::baseConfig()->getValue('QUERY_LOGFILE', '');
47
                if (!empty($query_logfile)) {
48
                        $ts = explode(" ",microtime());
49
                        $ts = date("Y-m-d H:i:s",$ts[1]).substr((string)$ts[0],1,4);
50
                        static $log_session_id = "";
51
                        if (empty($log_session_id)) {
52
                                $log_session_id = rand(10000,99999);
53
                        }
54
                        $file = isset($_SERVER['REQUEST_URI']) ? ' | '.$_SERVER['REQUEST_URI'] : '';
55
                        file_put_contents($query_logfile, "$ts <$log_session_id$file> $sql\n", FILE_APPEND);
56
                }
57
 
58
                $this->last_query = $sql;
59
                $sql = str_replace('###', OIDplus::baseConfig()->getValue('TABLENAME_PREFIX', ''), $sql);
60
                return $this->doQuery($sql, $prepared_args);
61
        }
62
 
63
        public final function connect()/*: void*/ {
64
                if ($this->connected) return;
65
                $this->beforeConnect();
66
                $this->doConnect();
67
                $this->connected = true;
68
                register_shutdown_function(array($this, 'disconnect'));
318 daniel-mar 69
                $this->afterConnectMandatory();
295 daniel-mar 70
                $this->afterConnect();
71
        }
72
 
73
        public final function disconnect()/*: void*/ {
74
                if (!$this->connected) return;
75
                $this->beforeDisconnect();
76
                $this->doDisconnect();
77
                $this->connected = false;
78
                $this->afterDisconnect();
79
        }
80
 
81
        public function natOrder($fieldname, $order='asc'): string {
325 daniel-mar 82
                $slang = $this->getSlang();
83
                if (!is_null($slang)) {
84
                        return $slang->natOrder($fieldname, $order);
295 daniel-mar 85
                } else {
86
                        $order = strtolower($order);
87
                        if (($order != 'asc') && ($order != 'desc')) {
88
                                throw new OIDplusException("Invalid order '$order' (needs to be 'asc' or 'desc')");
89
                        }
90
 
91
                        // For (yet) unsupported DBMS, we do not offer natural sort
92
                        return "$fieldname $order";
93
                }
94
        }
95
 
96
        protected function beforeDisconnect()/*: void*/ {}
97
 
98
        protected function afterDisconnect()/*: void*/ {}
99
 
100
        protected function beforeConnect()/*: void*/ {}
101
 
318 daniel-mar 102
        protected function afterConnect()/*: void*/ {}
103
 
104
        private function afterConnectMandatory()/*: void*/ {
295 daniel-mar 105
                // Check if the config table exists. This is important because the database version is stored in it
106
                $this->initRequireTables(array('config'));
107
 
108
                // Do the database tables need an update?
109
                // It is important that we do it immediately after connecting,
110
                // because the database structure might change and therefore various things might fail.
111
                // Note: The config setting "database_version" is inserted in setup/sql/...sql, not in the OIDplus core init
112
 
113
                $res = $this->query("SELECT value FROM ###config WHERE name = 'database_version'");
114
                $row = $res->fetch_array();
115
                if ($row == null) {
116
                        throw new OIDplusConfigInitializationException('Cannot determine database version (the entry "database_version" inside the table "###config" is probably missing)');
117
                }
118
                $version = $row['value'];
119
                if (!is_numeric($version) || ($version < 200) || ($version > 999)) {
120
                        throw new OIDplusConfigInitializationException('Entry "database_version" inside the table "###config" seems to be wrong (expect number between 200 and 999)');
121
                }
313 daniel-mar 122
 
123
                while (file_exists($file = OIDplus::basePath().'/includes/db_updates/update'.$version.'.inc.php')) {
295 daniel-mar 124
                        $prev_version = $version;
125
                        include $file; // run update-script
126
                        if ($version != $prev_version+1) {
127
                                // This should usually not happen, since the update-file should increase the version
128
                                // or throw an Exception by itself
129
                                throw new OIDplusException("Database update $prev_version -> ".($prev_version+1)." failed (script reports new version to be $version)");
130
                        }
131
                }
132
 
133
                // Now that our database is up-to-date, we check if database tables are existing
134
                // without config table, because it was checked above
135
                $this->initRequireTables(array('objects', 'asn1id', 'iri', 'ra'/*, 'config'*/));
316 daniel-mar 136
 
137
                // In case an auto-detection of the slang is required (for generic providers like PDO or ODBC),
138
                // we must not be inside a transaction, because the detection requires intentionally submitting
139
                // invalid queries to detect the correct DBMS. If we would be inside a transaction, providers like
140
                // PDO would automatically roll-back. Therefore, we detect the slang right at the beginning,
141
                // before any transaction is used.
142
                $this->getSlang();
295 daniel-mar 143
        }
144
 
145
        private function initRequireTables($tableNames)/*: void*/ {
146
                $msgs = array();
147
                foreach ($tableNames as $tableName) {
148
                        $prefix = OIDplus::baseConfig()->getValue('TABLENAME_PREFIX', '');
149
                        if (!$this->tableExists($prefix.$tableName)) {
150
                                $msgs[] = 'Table '.$prefix.$tableName.' is missing!';
151
                        }
152
                }
153
                if (count($msgs) > 0) {
154
                        throw new OIDplusConfigInitializationException(implode("\n\n",$msgs));
155
                }
156
        }
157
 
158
        public function tableExists($tableName): bool {
159
                try {
318 daniel-mar 160
                        // Attention: This query could interrupt transactions if Rollback-On-Error is enabled
295 daniel-mar 161
                        $this->query("select 0 from ".$tableName." where 1=0");
162
                        return true;
163
                } catch (Exception $e) {
164
                        return false;
165
                }
166
        }
167
 
168
        public function isConnected(): bool {
169
                return $this->connected;
170
        }
171
 
172
        public function init($html = true)/*: void*/ {
173
                $this->html = $html;
174
        }
175
 
176
        public function sqlDate(): string {
325 daniel-mar 177
                $slang = $this->getSlang();
178
                if (!is_null($slang)) {
179
                        return $slang->sqlDate();
295 daniel-mar 180
                } else {
181
                        return "'" . datetime('Y-m-d H:i:s') . "'";
182
                }
183
        }
184
 
316 daniel-mar 185
        public function getSlang(bool $mustExist=true)/*: ?OIDplusSqlSlangPlugin*/ {
326 daniel-mar 186
                static /*?OIDplusSqlSlangPlugin*/ $slangCache = null;
187
 
188
                if (is_null($slangCache)) {
295 daniel-mar 189
                        if (OIDplus::baseConfig()->exists('FORCE_DBMS_SLANG')) {
190
                                $name = OIDplus::baseConfig()->getValue('FORCE_DBMS_SLANG', '');
326 daniel-mar 191
                                $slangCache = OIDplus::getSqlSlangPlugin($name);
192
                                if ($mustExist && is_null($slangCache)) {
295 daniel-mar 193
                                        throw new OIDplusConfigInitializationException("Enforced SQL slang (via setting FORCE_DBMS_SLANG) '$name' does not exist.");
194
                                }
195
                        } else {
196
                                foreach (OIDplus::getSqlSlangPlugins() as $plugin) {
316 daniel-mar 197
                                        if ($plugin->detect($this)) {
326 daniel-mar 198
                                                $slangCache = $plugin;
295 daniel-mar 199
                                                break;
200
                                        }
201
                                }
326 daniel-mar 202
                                if ($mustExist && is_null($slangCache)) {
295 daniel-mar 203
                                        throw new OIDplusException("Cannot determine the SQL slang of your DBMS. Your DBMS is probably not supported.");
204
                                }
205
                        }
206
                }
207
 
326 daniel-mar 208
                return $slangCache;
295 daniel-mar 209
        }
210
}
211