Subversion Repositories oidplus

Rev

Rev 990 | Rev 1086 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

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