Rev 1234 | Rev 1240 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 1234 | Rev 1235 | ||
---|---|---|---|
1 | <?php |
1 | <?php |
2 | 2 | ||
3 | /* |
3 | /* |
4 | * OIDplus 2.0 |
4 | * OIDplus 2.0 |
5 | * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft |
5 | * Copyright 2019 - 2023 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 | namespace ViaThinkSoft\OIDplus; |
20 | namespace ViaThinkSoft\OIDplus; |
21 | 21 | ||
22 | // phpcs:disable PSR1.Files.SideEffects |
22 | // phpcs:disable PSR1.Files.SideEffects |
23 | \defined('INSIDE_OIDPLUS') or die; |
23 | \defined('INSIDE_OIDPLUS') or die; |
24 | // phpcs:enable PSR1.Files.SideEffects |
24 | // phpcs:enable PSR1.Files.SideEffects |
25 | 25 | ||
26 | class OIDplusDatabaseConnectionSqlSrv extends OIDplusDatabaseConnection { |
26 | class OIDplusDatabaseConnectionSqlSrv extends OIDplusDatabaseConnection { |
27 | 27 | ||
28 | /** |
28 | /** |
29 | * @var mixed|null |
29 | * @var mixed|null |
30 | */ |
30 | */ |
31 | private $conn = null; |
31 | private $conn = null; |
32 | 32 | ||
33 | /** |
33 | /** |
34 | * @var string|null |
34 | * @var string|null |
35 | */ |
35 | */ |
36 | private $last_error = null; |
36 | private $last_error = null; |
37 | 37 | ||
38 | /** |
38 | /** |
39 | * @var int |
39 | * @var int |
40 | */ |
40 | */ |
41 | private $rowsAffected = 0; |
41 | private $rowsAffected = 0; |
42 | 42 | ||
43 | /** |
43 | /** |
44 | * @return int |
44 | * @return int |
45 | */ |
45 | */ |
46 | public function rowsAffected(): int { |
46 | public function rowsAffected(): int { |
47 | return $this->rowsAffected; |
47 | return $this->rowsAffected; |
48 | } |
48 | } |
49 | 49 | ||
50 | /** |
50 | /** |
51 | * @param string $sql |
51 | * @param string $sql |
52 | * @param array|null $prepared_args |
52 | * @param array|null $prepared_args |
53 | * @return OIDplusQueryResultSqlSrv |
53 | * @return OIDplusQueryResultSqlSrv |
54 | * @throws OIDplusException |
54 | * @throws OIDplusException |
55 | */ |
55 | */ |
56 | public function doQuery(string $sql, array $prepared_args=null): OIDplusQueryResult { |
56 | public function doQuery(string $sql, array $prepared_args=null): OIDplusQueryResult { |
57 | $this->last_error = null; |
57 | $this->last_error = null; |
58 | try { |
58 | try { |
59 | $res = sqlsrv_query($this->conn, $sql, $prepared_args, |
59 | $res = sqlsrv_query($this->conn, $sql, $prepared_args, |
60 | array( |
60 | array( |
61 | // SQLSRV_CURSOR_FORWARD ('forward', default) |
61 | // SQLSRV_CURSOR_FORWARD ('forward', default) |
62 | // Lets you move one row at a time starting at the first row of the result set until you reach the end of the result set. |
62 | // Lets you move one row at a time starting at the first row of the result set until you reach the end of the result set. |
63 | // => Does not work with sqlsrv_num_rows(); |
63 | // => Does not work with sqlsrv_num_rows(); |
64 | 64 | ||
65 | // SQLSRV_CURSOR_STATIC ('static') |
65 | // SQLSRV_CURSOR_STATIC ('static') |
66 | // Lets you access rows in any order but will not reflect changes in the database. |
66 | // Lets you access rows in any order but will not reflect changes in the database. |
67 | // => Does not work with transaction rollback?! (Testcase failed) |
67 | // => Does not work with transaction rollback?! (Testcase failed) |
68 | 68 | ||
69 | // SQLSRV_CURSOR_DYNAMIC |
69 | // SQLSRV_CURSOR_DYNAMIC |
70 | // Lets you access rows in any order and will reflect changes in the database. |
70 | // Lets you access rows in any order and will reflect changes in the database. |
71 | // => Does not work with sqlsrv_num_rows(); |
71 | // => Does not work with sqlsrv_num_rows(); |
72 | 72 | ||
73 | // SQLSRV_CURSOR_KEYSET ('keyset') |
73 | // SQLSRV_CURSOR_KEYSET ('keyset') |
74 | // Lets you access rows in any order. However, a keyset cursor does not update the row count if a row is deleted from the table (a deleted row is returned with no values). |
74 | // Lets you access rows in any order. However, a keyset cursor does not update the row count if a row is deleted from the table (a deleted row is returned with no values). |
75 | // => Does not work with transaction rollback?! (Testcase failed) |
75 | // => Does not work with transaction rollback?! (Testcase failed) |
76 | 76 | ||
77 | // SQLSRV_CURSOR_CLIENT_BUFFERED ('buffered') |
77 | // SQLSRV_CURSOR_CLIENT_BUFFERED ('buffered') |
78 | // Lets you access rows in any order. Creates a client-side cursor query. |
78 | // Lets you access rows in any order. Creates a client-side cursor query. |
79 | // => Seems to work fine |
79 | // => Seems to work fine |
80 | 80 | ||
81 | 'Scrollable' => SQLSRV_CURSOR_CLIENT_BUFFERED |
81 | 'Scrollable' => SQLSRV_CURSOR_CLIENT_BUFFERED |
82 | ) |
82 | ) |
83 | ); |
83 | ); |
84 | } catch (\Exception $e) { |
84 | } catch (\Exception $e) { |
85 | $this->last_error = $e->getMessage(); |
85 | $this->last_error = $e->getMessage(); |
86 | throw new OIDplusSQLException($sql, $e->getMessage()); |
86 | throw new OIDplusSQLException($sql, $e->getMessage()); |
87 | } |
87 | } |
88 | 88 | ||
89 | if ($res === false) { |
89 | if ($res === false) { |
90 | $this->last_error = print_r(sqlsrv_errors(), true); |
90 | $this->last_error = print_r(sqlsrv_errors(), true); |
91 | throw new OIDplusSQLException($sql, $this->error()); |
91 | throw new OIDplusSQLException($sql, $this->error()); |
92 | } else { |
92 | } else { |
93 | if (str_starts_with(trim(strtolower($sql)),'select')) { |
93 | if (str_starts_with(trim(strtolower($sql)),'select')) { |
94 | $this->rowsAffected = sqlsrv_num_rows($res); |
94 | $this->rowsAffected = sqlsrv_num_rows($res); |
95 | } else { |
95 | } else { |
96 | $this->rowsAffected = sqlsrv_rows_affected($res); |
96 | $this->rowsAffected = sqlsrv_rows_affected($res); |
97 | } |
97 | } |
98 | return new OIDplusQueryResultSqlSrv($res); |
98 | return new OIDplusQueryResultSqlSrv($res); |
99 | } |
99 | } |
100 | } |
100 | } |
101 | 101 | ||
102 | 102 | ||
103 | /** |
103 | /** |
104 | * @return string |
104 | * @return string |
105 | */ |
105 | */ |
106 | public function error(): string { |
106 | public function error(): string { |
107 | $err = $this->last_error; |
107 | $err = $this->last_error; |
108 | if ($err === null) $err = ''; |
108 | if ($err === null) $err = ''; |
109 | return $err; |
109 | return $err; |
110 | } |
110 | } |
111 | 111 | ||
112 | /** |
112 | /** |
113 | * @return string |
113 | * @return string |
114 | */ |
114 | */ |
115 | private static function get_sqlsrv_dll_name(): string { |
115 | private static function get_sqlsrv_dll_name(): string { |
116 | ob_start(); |
116 | ob_start(); |
117 | phpinfo(INFO_GENERAL); |
117 | phpinfo(INFO_GENERAL); |
118 | $x = ob_get_contents(); |
118 | $x = ob_get_contents(); |
119 | ob_end_clean(); |
119 | ob_end_clean(); |
120 | 120 | ||
121 | $architecture = |
121 | $architecture = |
122 | preg_match('@Architecture.+(x86|x64)@', $x, $m) ? $m[1] : '*'; |
122 | preg_match('@Architecture.+(x86|x64)@', $x, $m) ? $m[1] : '*'; |
123 | 123 | ||
124 | $threadsafety = |
124 | $threadsafety = |
125 | preg_match('@Thread Safety.+(enabled|disabled)@', $x, $m) |
125 | preg_match('@Thread Safety.+(enabled|disabled)@', $x, $m) |
126 | ? ($m[1] == 'enabled' ? 'ts' : 'nts') : '*'; |
126 | ? ($m[1] == 'enabled' ? 'ts' : 'nts') : '*'; |
127 | 127 | ||
128 | $m = explode('.',phpversion()); |
128 | $m = explode('.',phpversion()); |
129 | $version = $m[0].$m[1]; |
129 | $version = $m[0].$m[1]; |
130 | 130 | ||
131 | // e.g. php_sqlsrv_82_ts_x64.dll |
131 | // e.g. php_sqlsrv_82_ts_x64.dll |
132 | return "php_sqlsrv_${version}_${threadsafety}_${architecture}.dll"; |
132 | return "php_sqlsrv_{$version}_{$threadsafety}_{$architecture}.dll"; |
133 | } |
133 | } |
134 | 134 | ||
135 | /** |
135 | /** |
136 | * @return void |
136 | * @return void |
137 | * @throws OIDplusConfigInitializationException |
137 | * @throws OIDplusConfigInitializationException |
138 | * @throws OIDplusException |
138 | * @throws OIDplusException |
139 | */ |
139 | */ |
140 | protected function doConnect()/*: void*/ { |
140 | protected function doConnect()/*: void*/ { |
141 | // Download here: https://learn.microsoft.com/en-us/sql/connect/php/download-drivers-php-sql-server?view=sql-server-ver16 |
141 | // Download here: https://learn.microsoft.com/en-us/sql/connect/php/download-drivers-php-sql-server?view=sql-server-ver16 |
142 | if (!function_exists('sqlsrv_connect')) throw new OIDplusException(_L('PHP extension "%1" not installed',self::get_sqlsrv_dll_name())); |
142 | if (!function_exists('sqlsrv_connect')) throw new OIDplusException(_L('PHP extension "%1" not installed',self::get_sqlsrv_dll_name())); |
143 | 143 | ||
144 | // Try connecting to the database |
144 | // Try connecting to the database |
145 | $servername = OIDplus::baseConfig()->getValue('SQLSRV_SERVER', 'localhost\oidplus'); |
145 | $servername = OIDplus::baseConfig()->getValue('SQLSRV_SERVER', 'localhost\oidplus'); |
146 | $username = OIDplus::baseConfig()->getValue('SQLSRV_USERNAME', ''); |
146 | $username = OIDplus::baseConfig()->getValue('SQLSRV_USERNAME', ''); |
147 | $password = OIDplus::baseConfig()->getValue('SQLSRV_PASSWORD', ''); |
147 | $password = OIDplus::baseConfig()->getValue('SQLSRV_PASSWORD', ''); |
148 | $database = OIDplus::baseConfig()->getValue('SQLSRV_DATABASE', 'oidplus'); |
148 | $database = OIDplus::baseConfig()->getValue('SQLSRV_DATABASE', 'oidplus'); |
149 | $options = OIDplus::baseConfig()->getValue('SQLSRV_OPTIONS', array()); |
149 | $options = OIDplus::baseConfig()->getValue('SQLSRV_OPTIONS', array()); |
150 | 150 | ||
151 | if (!isset($options['Database'])) $options['Database'] = $database; |
151 | if (!isset($options['Database'])) $options['Database'] = $database; |
152 | if (!isset($options['CharacterSet'])) $options['CharacterSet'] = 'UTF-8'; |
152 | if (!isset($options['CharacterSet'])) $options['CharacterSet'] = 'UTF-8'; |
153 | if ($username != '') { |
153 | if ($username != '') { |
154 | if (!isset($options['UID'])) $options['UID'] = $username; |
154 | if (!isset($options['UID'])) $options['UID'] = $username; |
155 | if (!isset($options['PWD'])) $options['PWD'] = $password; |
155 | if (!isset($options['PWD'])) $options['PWD'] = $password; |
156 | } |
156 | } |
157 | 157 | ||
158 | $this->conn = @sqlsrv_connect($servername, $options); |
158 | $this->conn = @sqlsrv_connect($servername, $options); |
159 | 159 | ||
160 | if (!$this->conn) { |
160 | if (!$this->conn) { |
161 | $message = print_r(sqlsrv_errors(), true); |
161 | $message = print_r(sqlsrv_errors(), true); |
162 | throw new OIDplusConfigInitializationException(trim(_L('Connection to the database failed!').' '.$message)); |
162 | throw new OIDplusConfigInitializationException(trim(_L('Connection to the database failed!').' '.$message)); |
163 | } |
163 | } |
164 | 164 | ||
165 | $this->last_error = null; |
165 | $this->last_error = null; |
166 | } |
166 | } |
167 | 167 | ||
168 | /** |
168 | /** |
169 | * @return void |
169 | * @return void |
170 | */ |
170 | */ |
171 | protected function doDisconnect()/*: void*/ { |
171 | protected function doDisconnect()/*: void*/ { |
172 | if (!is_null($this->conn)) { |
172 | if (!is_null($this->conn)) { |
173 | sqlsrv_close($this->conn); |
173 | sqlsrv_close($this->conn); |
174 | $this->conn = null; |
174 | $this->conn = null; |
175 | } |
175 | } |
176 | } |
176 | } |
177 | 177 | ||
178 | /** |
178 | /** |
179 | * @var bool |
179 | * @var bool |
180 | */ |
180 | */ |
181 | private $intransaction = false; |
181 | private $intransaction = false; |
182 | 182 | ||
183 | /** |
183 | /** |
184 | * @return bool |
184 | * @return bool |
185 | */ |
185 | */ |
186 | public function transaction_supported(): bool { |
186 | public function transaction_supported(): bool { |
187 | return true; |
187 | return true; |
188 | } |
188 | } |
189 | 189 | ||
190 | /** |
190 | /** |
191 | * @return int |
191 | * @return int |
192 | */ |
192 | */ |
193 | public function transaction_level(): int { |
193 | public function transaction_level(): int { |
194 | return $this->intransaction ? 1 : 0; |
194 | return $this->intransaction ? 1 : 0; |
195 | } |
195 | } |
196 | 196 | ||
197 | /** |
197 | /** |
198 | * @return void |
198 | * @return void |
199 | * @throws OIDplusException |
199 | * @throws OIDplusException |
200 | */ |
200 | */ |
201 | public function transaction_begin()/*: void*/ { |
201 | public function transaction_begin()/*: void*/ { |
202 | if ($this->intransaction) throw new OIDplusException(_L('Nested transactions are not supported by this database plugin.')); |
202 | if ($this->intransaction) throw new OIDplusException(_L('Nested transactions are not supported by this database plugin.')); |
203 | if (sqlsrv_begin_transaction($this->conn)) $this->intransaction = true; |
203 | if (sqlsrv_begin_transaction($this->conn)) $this->intransaction = true; |
204 | } |
204 | } |
205 | 205 | ||
206 | /** |
206 | /** |
207 | * @return void |
207 | * @return void |
208 | */ |
208 | */ |
209 | public function transaction_commit()/*: void*/ { |
209 | public function transaction_commit()/*: void*/ { |
210 | if (sqlsrv_commit($this->conn)) $this->intransaction = false; |
210 | if (sqlsrv_commit($this->conn)) $this->intransaction = false; |
211 | } |
211 | } |
212 | 212 | ||
213 | /** |
213 | /** |
214 | * @return void |
214 | * @return void |
215 | */ |
215 | */ |
216 | public function transaction_rollback()/*: void*/ { |
216 | public function transaction_rollback()/*: void*/ { |
217 | if (sqlsrv_rollback($this->conn)) $this->intransaction = false; |
217 | if (sqlsrv_rollback($this->conn)) $this->intransaction = false; |
218 | } |
218 | } |
219 | 219 | ||
220 | /** |
220 | /** |
221 | * @param bool $mustExist |
221 | * @param bool $mustExist |
222 | * @return OIDplusSqlSlangPlugin|null |
222 | * @return OIDplusSqlSlangPlugin|null |
223 | * @throws OIDplusConfigInitializationException |
223 | * @throws OIDplusConfigInitializationException |
224 | */ |
224 | */ |
225 | protected function doGetSlang(bool $mustExist=true)/*: ?OIDplusSqlSlangPlugin*/ { |
225 | protected function doGetSlang(bool $mustExist=true)/*: ?OIDplusSqlSlangPlugin*/ { |
226 | $slang = OIDplus::getSqlSlangPlugin('mssql'); |
226 | $slang = OIDplus::getSqlSlangPlugin('mssql'); |
227 | if (is_null($slang)) { |
227 | if (is_null($slang)) { |
228 | throw new OIDplusConfigInitializationException(_L('SQL-Slang plugin "%1" is missing. Please check if it exists in the directory "plugin/sqlSlang". If it is not existing, please recover it from an SVN snapshot or OIDplus TAR.GZ file.','mssql')); |
228 | throw new OIDplusConfigInitializationException(_L('SQL-Slang plugin "%1" is missing. Please check if it exists in the directory "plugin/sqlSlang". If it is not existing, please recover it from an SVN snapshot or OIDplus TAR.GZ file.','mssql')); |
229 | } |
229 | } |
230 | return $slang; |
230 | return $slang; |
231 | } |
231 | } |
232 | 232 | ||
233 | /** |
233 | /** |
234 | * @return array |
234 | * @return array |
235 | */ |
235 | */ |
236 | public function getExtendedInfo(): array { |
236 | public function getExtendedInfo(): array { |
237 | $servername = OIDplus::baseConfig()->getValue('SQLSRV_SERVER', 'localhost\oidplus'); |
237 | $servername = OIDplus::baseConfig()->getValue('SQLSRV_SERVER', 'localhost\oidplus'); |
238 | $username = OIDplus::baseConfig()->getValue('SQLSRV_USERNAME', ''); |
238 | $username = OIDplus::baseConfig()->getValue('SQLSRV_USERNAME', ''); |
239 | $password = OIDplus::baseConfig()->getValue('SQLSRV_PASSWORD', ''); |
239 | $password = OIDplus::baseConfig()->getValue('SQLSRV_PASSWORD', ''); |
240 | $database = OIDplus::baseConfig()->getValue('SQLSRV_DATABASE', 'oidplus'); |
240 | $database = OIDplus::baseConfig()->getValue('SQLSRV_DATABASE', 'oidplus'); |
241 | $options = OIDplus::baseConfig()->getValue('SQLSRV_OPTIONS', array()); |
241 | $options = OIDplus::baseConfig()->getValue('SQLSRV_OPTIONS', array()); |
242 | 242 | ||
243 | $ary_info = array( |
243 | $ary_info = array( |
244 | _L('Hostname') => $servername, |
244 | _L('Hostname') => $servername, |
245 | _L('Username') => $username, |
245 | _L('Username') => $username, |
246 | _L('Password') => $password != '' ? '('._L('redacted').')' : '', |
246 | _L('Password') => $password != '' ? '('._L('redacted').')' : '', |
247 | _L('Database') => $database |
247 | _L('Database') => $database |
248 | ); |
248 | ); |
249 | foreach ($options as $name => $val) { |
249 | foreach ($options as $name => $val) { |
250 | $ary_info[_L('Option %1',$name)] = '"'.$val.'"'; |
250 | $ary_info[_L('Option %1',$name)] = '"'.$val.'"'; |
251 | } |
251 | } |
252 | return $ary_info; |
252 | return $ary_info; |
253 | } |
253 | } |
254 | } |
254 | } |
255 | 255 |