Rev 501 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 501 | Rev 502 | ||
---|---|---|---|
Line 18... | Line 18... | ||
18 | */ |
18 | */ |
19 | 19 | ||
20 | class OIDplusDatabaseConnectionPDO extends OIDplusDatabaseConnection { |
20 | class OIDplusDatabaseConnectionPDO extends OIDplusDatabaseConnection { |
21 | private $conn = null; |
21 | private $conn = null; |
22 | private $last_error = null; // we need that because PDO divides prepared statement errors and normal query errors, but we have only one "error()" method |
22 | private $last_error = null; // we need that because PDO divides prepared statement errors and normal query errors, but we have only one "error()" method |
- | 23 | private $transactions_supported = false; |
|
23 | 24 | ||
24 | public static function getPlugin(): OIDplusDatabasePlugin { |
25 | public static function getPlugin(): OIDplusDatabasePlugin { |
25 | return new OIDplusDatabasePluginPDO(); |
26 | return new OIDplusDatabasePluginPDO(); |
26 | } |
27 | } |
27 | 28 | ||
Line 35... | Line 36... | ||
35 | throw new OIDplusSQLException($sql, $this->error()); |
36 | throw new OIDplusSQLException($sql, $this->error()); |
36 | } else { |
37 | } else { |
37 | return new OIDplusQueryResultPDO($res); |
38 | return new OIDplusQueryResultPDO($res); |
38 | } |
39 | } |
39 | } else { |
40 | } else { |
40 | // TEST: Emulate the prepared statement |
41 | if (!is_array($prepared_args)) { |
- | 42 | throw new OIDplusException(_L('"prepared_args" must be either NULL or an ARRAY.')); |
|
- | 43 | } |
|
- | 44 | ||
41 | /* |
45 | /* |
- | 46 | // Not required because PDO can emulate prepared statements by itself |
|
- | 47 | if ($this->forcePrepareEmulation()) { |
|
- | 48 | $sql = str_replace('?', chr(1), $sql); |
|
42 | foreach ($prepared_args as $arg) { |
49 | foreach ($prepared_args as $arg) { |
43 | $needle = '?'; |
50 | $needle = chr(1); |
- | 51 | if (is_bool($arg)) { |
|
- | 52 | if ($this->slangDetectionDone) { |
|
- | 53 | $replace = $this->getSlang()->getSQLBool($arg); |
|
- | 54 | } else { |
|
- | 55 | $replace = $arg ? '1' : '0'; |
|
- | 56 | } |
|
- | 57 | } else { |
|
44 | $replace = "'$arg'"; // TODO: types |
58 | $replace = "'$arg'"; // TODO: types |
- | 59 | } |
|
45 | $pos = strpos($sql, $needle); |
60 | $pos = strpos($sql, $needle); |
46 | if ($pos !== false) { |
61 | if ($pos !== false) { |
47 | $sql = substr_replace($sql, $replace, $pos, strlen($needle)); |
62 | $sql = substr_replace($sql, $replace, $pos, strlen($needle)); |
48 | } |
63 | } |
49 | } |
64 | } |
- | 65 | $sql = str_replace(chr(1), '?', $sql); |
|
50 | return new OIDplusQueryResultPDO($this->conn->query($sql)); |
66 | $ps = $this->conn->query($sql); |
51 | */ |
67 | if (!$ps) { |
52 | - | ||
53 | if (!is_array($prepared_args)) { |
68 | $this->last_error = $this->error(); |
54 | throw new OIDplusException(_L('"prepared_args" must be either NULL or an ARRAY.')); |
69 | throw new OIDplusSQLException($sql, _L('Cannot prepare statement').': '.$this->error()); |
55 | } |
70 | } |
- | 71 | return new OIDplusQueryResultPDO($ps); |
|
- | 72 | } |
|
- | 73 | */ |
|
56 | 74 | ||
57 | foreach ($prepared_args as &$value) { |
75 | foreach ($prepared_args as &$value) { |
58 | // We need to manually convert booleans into strings, because there is a |
76 | // We need to manually convert booleans into strings, because there is a |
59 | // 14 year old bug that hasn't been adressed by the PDO developers: |
77 | // 14 year old bug that hasn't been adressed by the PDO developers: |
60 | // https://bugs.php.net/bug.php?id=57157 |
78 | // https://bugs.php.net/bug.php?id=57157 |
- | 79 | if (is_bool($value)) { |
|
- | 80 | if ($this->slangDetectionDone) { |
|
- | 81 | $value = $this->getSlang()->getSQLBool($value); |
|
- | 82 | } else { |
|
- | 83 | // This works for everything except Microsoft Access (which needs -1 and 0) |
|
61 | // Note: We are using '1' and '0' instead of 'true' and 'false' because MySQL converts boolean to tinyint(1) |
84 | // Note: We are using '1' and '0' instead of 'true' and 'false' because MySQL converts boolean to tinyint(1) |
62 | if (is_bool($value)) $value = $value ? '1' : '0'; |
85 | $value = $value ? '1' : '0'; |
- | 86 | } |
|
- | 87 | } |
|
63 | } |
88 | } |
64 | 89 | ||
65 | $ps = $this->conn->prepare($sql); |
90 | $ps = $this->conn->prepare($sql); |
66 | if (!$ps) { |
91 | if (!$ps) { |
67 | $this->last_error = $ps->errorInfo()[2]; |
92 | $this->last_error = $this->conn->errorInfo()[2]; |
68 | throw new OIDplusSQLException($sql, _L('Cannot prepare statement').': '.$this->error()); |
93 | throw new OIDplusSQLException($sql, _L('Cannot prepare statement').': '.$this->error()); |
69 | } |
94 | } |
70 | 95 | ||
71 | if (!$ps->execute($prepared_args)) { |
96 | if (!$ps->execute($prepared_args)) { |
72 | $this->last_error = $ps->errorInfo()[2]; |
97 | $this->last_error = $ps->errorInfo()[2]; |
Line 75... | Line 100... | ||
75 | return new OIDplusQueryResultPDO($ps); |
100 | return new OIDplusQueryResultPDO($ps); |
76 | } |
101 | } |
77 | } |
102 | } |
78 | 103 | ||
79 | public function insert_id(): int { |
104 | public function insert_id(): int { |
- | 105 | try { |
|
80 | return $this->conn->lastInsertId(); |
106 | $out = @($this->conn->lastInsertId()); |
- | 107 | if ($out === false) return parent::insert_id(); // fallback method that uses the SQL slang |
|
- | 108 | return $out; |
|
- | 109 | } catch (Exception $e) { |
|
- | 110 | return parent::insert_id(); // fallback method that uses the SQL slang |
|
- | 111 | } |
|
81 | } |
112 | } |
82 | 113 | ||
83 | public function error(): string { |
114 | public function error(): string { |
84 | $err = $this->last_error; |
115 | $err = $this->last_error; |
85 | if ($err == null) $err = ''; |
116 | if ($err == null) $err = ''; |
Line 106... | Line 137... | ||
106 | throw new OIDplusConfigInitializationException(_L('Connection to the database failed!').' '.$message); |
137 | throw new OIDplusConfigInitializationException(_L('Connection to the database failed!').' '.$message); |
107 | } |
138 | } |
108 | 139 | ||
109 | $this->last_error = null; |
140 | $this->last_error = null; |
110 | 141 | ||
- | 142 | try { |
|
111 | $this->query("SET NAMES 'utf8'"); |
143 | @$this->conn->exec("SET NAMES 'utf8'"); |
- | 144 | } catch (Exception $e) { |
|
- | 145 | } |
|
- | 146 | ||
- | 147 | // We check if the DBMS supports autocommit. |
|
- | 148 | // Attention: Check it after you have sent a query already, because Microsoft Access doesn't seem to allow |
|
- | 149 | // changing auto commit once a query was executed ("Attribute cannot be set now SQLState: S1011") |
|
- | 150 | // Note: For some weird reason we *DO* need to redirect the output to "$dummy", otherwise it won't work! |
|
- | 151 | $dummy = $this->conn->query("select name from config where 1=0"); |
|
- | 152 | try { |
|
- | 153 | $this->conn->beginTransaction(); |
|
- | 154 | $this->conn->rollBack(); |
|
- | 155 | $this->transactions_supported = true; |
|
- | 156 | } catch (Exception $e) { |
|
- | 157 | $this->transactions_supported = false; |
|
- | 158 | } |
|
112 | } |
159 | } |
113 | 160 | ||
114 | protected function doDisconnect()/*: void*/ { |
161 | protected function doDisconnect()/*: void*/ { |
115 | $this->conn = null; // the connection will be closed by removing the reference |
162 | $this->conn = null; // the connection will be closed by removing the reference |
116 | } |
163 | } |
117 | 164 | ||
118 | private $intransaction = false; |
165 | private $intransaction = false; |
119 | 166 | ||
120 | public function transaction_supported(): bool { |
167 | public function transaction_supported(): bool { |
121 | return true; |
168 | return $this->transactions_supported; |
122 | } |
169 | } |
123 | 170 | ||
124 | public function transaction_level(): int { |
171 | public function transaction_level(): int { |
- | 172 | if (!$this->transaction_supported()) { |
|
- | 173 | // TODO? |
|
- | 174 | return 0; |
|
- | 175 | } |
|
125 | return $this->intransaction ? 1 : 0; |
176 | return $this->intransaction ? 1 : 0; |
126 | } |
177 | } |
127 | 178 | ||
128 | public function transaction_begin()/*: void*/ { |
179 | public function transaction_begin()/*: void*/ { |
- | 180 | if (!$this->transaction_supported()) { |
|
- | 181 | // TODO? |
|
- | 182 | return; |
|
- | 183 | } |
|
129 | if ($this->intransaction) throw new OIDplusException(_L('Nested transactions are not supported by this database plugin.')); |
184 | if ($this->intransaction) throw new OIDplusException(_L('Nested transactions are not supported by this database plugin.')); |
130 | $this->conn->beginTransaction(); |
185 | $this->conn->beginTransaction(); |
131 | $this->intransaction = true; |
186 | $this->intransaction = true; |
132 | } |
187 | } |
133 | 188 | ||
134 | public function transaction_commit()/*: void*/ { |
189 | public function transaction_commit()/*: void*/ { |
- | 190 | if (!$this->transaction_supported()) { |
|
- | 191 | // TODO? |
|
- | 192 | return; |
|
- | 193 | } |
|
135 | $this->conn->commit(); |
194 | $this->conn->commit(); |
136 | $this->intransaction = false; |
195 | $this->intransaction = false; |
137 | } |
196 | } |
138 | 197 | ||
139 | public function transaction_rollback()/*: void*/ { |
198 | public function transaction_rollback()/*: void*/ { |
- | 199 | if (!$this->transaction_supported()) { |
|
- | 200 | // TODO? |
|
- | 201 | return; |
|
- | 202 | } |
|
140 | $this->conn->rollBack(); |
203 | $this->conn->rollBack(); |
141 | $this->intransaction = false; |
204 | $this->intransaction = false; |
142 | } |
205 | } |
143 | } |
206 | } |