Passed
Push — master ( f797a9...4201fb )
by Christoph
15:48 queued 10s
created
lib/private/DB/Connection.php 1 patch
Indentation   +472 added lines, -472 removed lines patch added patch discarded remove patch
@@ -54,476 +54,476 @@
 block discarded – undo
54 54
 use OCP\PreConditionNotMetException;
55 55
 
56 56
 class Connection extends \Doctrine\DBAL\Connection {
57
-	/** @var string */
58
-	protected $tablePrefix;
59
-
60
-	/** @var \OC\DB\Adapter $adapter */
61
-	protected $adapter;
62
-
63
-	/** @var SystemConfig */
64
-	private $systemConfig;
65
-
66
-	/** @var ILogger */
67
-	private $logger;
68
-
69
-	protected $lockedTable = null;
70
-
71
-	/** @var int */
72
-	protected $queriesBuilt = 0;
73
-
74
-	/** @var int */
75
-	protected $queriesExecuted = 0;
76
-
77
-	/**
78
-	 * @throws Exception
79
-	 */
80
-	public function connect() {
81
-		try {
82
-			return parent::connect();
83
-		} catch (Exception $e) {
84
-			// throw a new exception to prevent leaking info from the stacktrace
85
-			throw new Exception('Failed to connect to the database: ' . $e->getMessage(), $e->getCode());
86
-		}
87
-	}
88
-
89
-	public function getStats(): array {
90
-		return [
91
-			'built' => $this->queriesBuilt,
92
-			'executed' => $this->queriesExecuted,
93
-		];
94
-	}
95
-
96
-	/**
97
-	 * Returns a QueryBuilder for the connection.
98
-	 */
99
-	public function getQueryBuilder(): IQueryBuilder {
100
-		$this->queriesBuilt++;
101
-		return new QueryBuilder(
102
-			new ConnectionAdapter($this),
103
-			$this->systemConfig,
104
-			$this->logger
105
-		);
106
-	}
107
-
108
-	/**
109
-	 * Gets the QueryBuilder for the connection.
110
-	 *
111
-	 * @return \Doctrine\DBAL\Query\QueryBuilder
112
-	 * @deprecated please use $this->getQueryBuilder() instead
113
-	 */
114
-	public function createQueryBuilder() {
115
-		$backtrace = $this->getCallerBacktrace();
116
-		\OC::$server->getLogger()->debug('Doctrine QueryBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]);
117
-		$this->queriesBuilt++;
118
-		return parent::createQueryBuilder();
119
-	}
120
-
121
-	/**
122
-	 * Gets the ExpressionBuilder for the connection.
123
-	 *
124
-	 * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder
125
-	 * @deprecated please use $this->getQueryBuilder()->expr() instead
126
-	 */
127
-	public function getExpressionBuilder() {
128
-		$backtrace = $this->getCallerBacktrace();
129
-		\OC::$server->getLogger()->debug('Doctrine ExpressionBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]);
130
-		$this->queriesBuilt++;
131
-		return parent::getExpressionBuilder();
132
-	}
133
-
134
-	/**
135
-	 * Get the file and line that called the method where `getCallerBacktrace()` was used
136
-	 *
137
-	 * @return string
138
-	 */
139
-	protected function getCallerBacktrace() {
140
-		$traces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
141
-
142
-		// 0 is the method where we use `getCallerBacktrace`
143
-		// 1 is the target method which uses the method we want to log
144
-		if (isset($traces[1])) {
145
-			return $traces[1]['file'] . ':' . $traces[1]['line'];
146
-		}
147
-
148
-		return '';
149
-	}
150
-
151
-	/**
152
-	 * @return string
153
-	 */
154
-	public function getPrefix() {
155
-		return $this->tablePrefix;
156
-	}
157
-
158
-	/**
159
-	 * Initializes a new instance of the Connection class.
160
-	 *
161
-	 * @param array $params  The connection parameters.
162
-	 * @param \Doctrine\DBAL\Driver $driver
163
-	 * @param \Doctrine\DBAL\Configuration $config
164
-	 * @param \Doctrine\Common\EventManager $eventManager
165
-	 * @throws \Exception
166
-	 */
167
-	public function __construct(array $params, Driver $driver, Configuration $config = null,
168
-		EventManager $eventManager = null) {
169
-		if (!isset($params['adapter'])) {
170
-			throw new \Exception('adapter not set');
171
-		}
172
-		if (!isset($params['tablePrefix'])) {
173
-			throw new \Exception('tablePrefix not set');
174
-		}
175
-		/**
176
-		 * @psalm-suppress InternalMethod
177
-		 */
178
-		parent::__construct($params, $driver, $config, $eventManager);
179
-		$this->adapter = new $params['adapter']($this);
180
-		$this->tablePrefix = $params['tablePrefix'];
181
-
182
-		$this->systemConfig = \OC::$server->getSystemConfig();
183
-		$this->logger = \OC::$server->getLogger();
184
-	}
185
-
186
-	/**
187
-	 * Prepares an SQL statement.
188
-	 *
189
-	 * @param string $statement The SQL statement to prepare.
190
-	 * @param int $limit
191
-	 * @param int $offset
192
-	 *
193
-	 * @return Statement The prepared statement.
194
-	 * @throws Exception
195
-	 */
196
-	public function prepare($statement, $limit = null, $offset = null): Statement {
197
-		if ($limit === -1) {
198
-			$limit = null;
199
-		}
200
-		if (!is_null($limit)) {
201
-			$platform = $this->getDatabasePlatform();
202
-			$statement = $platform->modifyLimitQuery($statement, $limit, $offset);
203
-		}
204
-		$statement = $this->replaceTablePrefix($statement);
205
-		$statement = $this->adapter->fixupStatement($statement);
206
-
207
-		return parent::prepare($statement);
208
-	}
209
-
210
-	/**
211
-	 * Executes an, optionally parametrized, SQL query.
212
-	 *
213
-	 * If the query is parametrized, a prepared statement is used.
214
-	 * If an SQLLogger is configured, the execution is logged.
215
-	 *
216
-	 * @param string                                      $sql  The SQL query to execute.
217
-	 * @param array                                       $params The parameters to bind to the query, if any.
218
-	 * @param array                                       $types  The types the previous parameters are in.
219
-	 * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp    The query cache profile, optional.
220
-	 *
221
-	 * @return Result The executed statement.
222
-	 *
223
-	 * @throws \Doctrine\DBAL\Exception
224
-	 */
225
-	public function executeQuery(string $sql, array $params = [], $types = [], QueryCacheProfile $qcp = null): Result {
226
-		$sql = $this->replaceTablePrefix($sql);
227
-		$sql = $this->adapter->fixupStatement($sql);
228
-		$this->queriesExecuted++;
229
-		return parent::executeQuery($sql, $params, $types, $qcp);
230
-	}
231
-
232
-	/**
233
-	 * @throws Exception
234
-	 */
235
-	public function executeUpdate(string $sql, array $params = [], array $types = []): int {
236
-		$sql = $this->replaceTablePrefix($sql);
237
-		$sql = $this->adapter->fixupStatement($sql);
238
-		$this->queriesExecuted++;
239
-		return parent::executeUpdate($sql, $params, $types);
240
-	}
241
-
242
-	/**
243
-	 * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
244
-	 * and returns the number of affected rows.
245
-	 *
246
-	 * This method supports PDO binding types as well as DBAL mapping types.
247
-	 *
248
-	 * @param string $sql  The SQL query.
249
-	 * @param array  $params The query parameters.
250
-	 * @param array  $types  The parameter types.
251
-	 *
252
-	 * @return int The number of affected rows.
253
-	 *
254
-	 * @throws \Doctrine\DBAL\Exception
255
-	 */
256
-	public function executeStatement($sql, array $params = [], array $types = []): int {
257
-		$sql = $this->replaceTablePrefix($sql);
258
-		$sql = $this->adapter->fixupStatement($sql);
259
-		$this->queriesExecuted++;
260
-		return parent::executeStatement($sql, $params, $types);
261
-	}
262
-
263
-	/**
264
-	 * Returns the ID of the last inserted row, or the last value from a sequence object,
265
-	 * depending on the underlying driver.
266
-	 *
267
-	 * Note: This method may not return a meaningful or consistent result across different drivers,
268
-	 * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
269
-	 * columns or sequences.
270
-	 *
271
-	 * @param string $seqName Name of the sequence object from which the ID should be returned.
272
-	 *
273
-	 * @return string the last inserted ID.
274
-	 * @throws Exception
275
-	 */
276
-	public function lastInsertId($seqName = null) {
277
-		if ($seqName) {
278
-			$seqName = $this->replaceTablePrefix($seqName);
279
-		}
280
-		return $this->adapter->lastInsertId($seqName);
281
-	}
282
-
283
-	/**
284
-	 * @internal
285
-	 * @throws Exception
286
-	 */
287
-	public function realLastInsertId($seqName = null) {
288
-		return parent::lastInsertId($seqName);
289
-	}
290
-
291
-	/**
292
-	 * Insert a row if the matching row does not exists. To accomplish proper race condition avoidance
293
-	 * it is needed that there is also a unique constraint on the values. Then this method will
294
-	 * catch the exception and return 0.
295
-	 *
296
-	 * @param string $table The table name (will replace *PREFIX* with the actual prefix)
297
-	 * @param array $input data that should be inserted into the table  (column name => value)
298
-	 * @param array|null $compare List of values that should be checked for "if not exists"
299
-	 *				If this is null or an empty array, all keys of $input will be compared
300
-	 *				Please note: text fields (clob) must not be used in the compare array
301
-	 * @return int number of inserted rows
302
-	 * @throws \Doctrine\DBAL\Exception
303
-	 * @deprecated 15.0.0 - use unique index and "try { $db->insert() } catch (UniqueConstraintViolationException $e) {}" instead, because it is more reliable and does not have the risk for deadlocks - see https://github.com/nextcloud/server/pull/12371
304
-	 */
305
-	public function insertIfNotExist($table, $input, array $compare = null) {
306
-		return $this->adapter->insertIfNotExist($table, $input, $compare);
307
-	}
308
-
309
-	public function insertIgnoreConflict(string $table, array $values) : int {
310
-		return $this->adapter->insertIgnoreConflict($table, $values);
311
-	}
312
-
313
-	private function getType($value) {
314
-		if (is_bool($value)) {
315
-			return IQueryBuilder::PARAM_BOOL;
316
-		} elseif (is_int($value)) {
317
-			return IQueryBuilder::PARAM_INT;
318
-		} else {
319
-			return IQueryBuilder::PARAM_STR;
320
-		}
321
-	}
322
-
323
-	/**
324
-	 * Insert or update a row value
325
-	 *
326
-	 * @param string $table
327
-	 * @param array $keys (column name => value)
328
-	 * @param array $values (column name => value)
329
-	 * @param array $updatePreconditionValues ensure values match preconditions (column name => value)
330
-	 * @return int number of new rows
331
-	 * @throws \Doctrine\DBAL\Exception
332
-	 * @throws PreConditionNotMetException
333
-	 */
334
-	public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []) {
335
-		try {
336
-			$insertQb = $this->getQueryBuilder();
337
-			$insertQb->insert($table)
338
-				->values(
339
-					array_map(function ($value) use ($insertQb) {
340
-						return $insertQb->createNamedParameter($value, $this->getType($value));
341
-					}, array_merge($keys, $values))
342
-				);
343
-			return $insertQb->execute();
344
-		} catch (NotNullConstraintViolationException $e) {
345
-			throw $e;
346
-		} catch (ConstraintViolationException $e) {
347
-			// value already exists, try update
348
-			$updateQb = $this->getQueryBuilder();
349
-			$updateQb->update($table);
350
-			foreach ($values as $name => $value) {
351
-				$updateQb->set($name, $updateQb->createNamedParameter($value, $this->getType($value)));
352
-			}
353
-			$where = $updateQb->expr()->andX();
354
-			$whereValues = array_merge($keys, $updatePreconditionValues);
355
-			foreach ($whereValues as $name => $value) {
356
-				if ($value === '') {
357
-					$where->add($updateQb->expr()->emptyString(
358
-						$name
359
-					));
360
-				} else {
361
-					$where->add($updateQb->expr()->eq(
362
-						$name,
363
-						$updateQb->createNamedParameter($value, $this->getType($value)),
364
-						$this->getType($value)
365
-					));
366
-				}
367
-			}
368
-			$updateQb->where($where);
369
-			$affected = $updateQb->execute();
370
-
371
-			if ($affected === 0 && !empty($updatePreconditionValues)) {
372
-				throw new PreConditionNotMetException();
373
-			}
374
-
375
-			return 0;
376
-		}
377
-	}
378
-
379
-	/**
380
-	 * Create an exclusive read+write lock on a table
381
-	 *
382
-	 * @param string $tableName
383
-	 *
384
-	 * @throws \BadMethodCallException When trying to acquire a second lock
385
-	 * @throws Exception
386
-	 * @since 9.1.0
387
-	 */
388
-	public function lockTable($tableName) {
389
-		if ($this->lockedTable !== null) {
390
-			throw new \BadMethodCallException('Can not lock a new table until the previous lock is released.');
391
-		}
392
-
393
-		$tableName = $this->tablePrefix . $tableName;
394
-		$this->lockedTable = $tableName;
395
-		$this->adapter->lockTable($tableName);
396
-	}
397
-
398
-	/**
399
-	 * Release a previous acquired lock again
400
-	 *
401
-	 * @throws Exception
402
-	 * @since 9.1.0
403
-	 */
404
-	public function unlockTable() {
405
-		$this->adapter->unlockTable();
406
-		$this->lockedTable = null;
407
-	}
408
-
409
-	/**
410
-	 * returns the error code and message as a string for logging
411
-	 * works with DoctrineException
412
-	 * @return string
413
-	 */
414
-	public function getError() {
415
-		$msg = $this->errorCode() . ': ';
416
-		$errorInfo = $this->errorInfo();
417
-		if (!empty($errorInfo)) {
418
-			$msg .= 'SQLSTATE = '.$errorInfo[0] . ', ';
419
-			$msg .= 'Driver Code = '.$errorInfo[1] . ', ';
420
-			$msg .= 'Driver Message = '.$errorInfo[2];
421
-		}
422
-		return $msg;
423
-	}
424
-
425
-	public function errorCode() {
426
-		return -1;
427
-	}
428
-
429
-	public function errorInfo() {
430
-		return [];
431
-	}
432
-
433
-	/**
434
-	 * Drop a table from the database if it exists
435
-	 *
436
-	 * @param string $table table name without the prefix
437
-	 *
438
-	 * @throws Exception
439
-	 */
440
-	public function dropTable($table) {
441
-		$table = $this->tablePrefix . trim($table);
442
-		$schema = $this->getSchemaManager();
443
-		if ($schema->tablesExist([$table])) {
444
-			$schema->dropTable($table);
445
-		}
446
-	}
447
-
448
-	/**
449
-	 * Check if a table exists
450
-	 *
451
-	 * @param string $table table name without the prefix
452
-	 *
453
-	 * @return bool
454
-	 * @throws Exception
455
-	 */
456
-	public function tableExists($table) {
457
-		$table = $this->tablePrefix . trim($table);
458
-		$schema = $this->getSchemaManager();
459
-		return $schema->tablesExist([$table]);
460
-	}
461
-
462
-	// internal use
463
-	/**
464
-	 * @param string $statement
465
-	 * @return string
466
-	 */
467
-	protected function replaceTablePrefix($statement) {
468
-		return str_replace('*PREFIX*', $this->tablePrefix, $statement);
469
-	}
470
-
471
-	/**
472
-	 * Check if a transaction is active
473
-	 *
474
-	 * @return bool
475
-	 * @since 8.2.0
476
-	 */
477
-	public function inTransaction() {
478
-		return $this->getTransactionNestingLevel() > 0;
479
-	}
480
-
481
-	/**
482
-	 * Escape a parameter to be used in a LIKE query
483
-	 *
484
-	 * @param string $param
485
-	 * @return string
486
-	 */
487
-	public function escapeLikeParameter($param) {
488
-		return addcslashes($param, '\\_%');
489
-	}
490
-
491
-	/**
492
-	 * Check whether or not the current database support 4byte wide unicode
493
-	 *
494
-	 * @return bool
495
-	 * @since 11.0.0
496
-	 */
497
-	public function supports4ByteText() {
498
-		if (!$this->getDatabasePlatform() instanceof MySQLPlatform) {
499
-			return true;
500
-		}
501
-		return $this->getParams()['charset'] === 'utf8mb4';
502
-	}
503
-
504
-
505
-	/**
506
-	 * Create the schema of the connected database
507
-	 *
508
-	 * @return Schema
509
-	 * @throws Exception
510
-	 */
511
-	public function createSchema() {
512
-		$schemaManager = new MDB2SchemaManager($this);
513
-		$migrator = $schemaManager->getMigrator();
514
-		return $migrator->createSchema();
515
-	}
516
-
517
-	/**
518
-	 * Migrate the database to the given schema
519
-	 *
520
-	 * @param Schema $toSchema
521
-	 *
522
-	 * @throws Exception
523
-	 */
524
-	public function migrateToSchema(Schema $toSchema) {
525
-		$schemaManager = new MDB2SchemaManager($this);
526
-		$migrator = $schemaManager->getMigrator();
527
-		$migrator->migrate($toSchema);
528
-	}
57
+    /** @var string */
58
+    protected $tablePrefix;
59
+
60
+    /** @var \OC\DB\Adapter $adapter */
61
+    protected $adapter;
62
+
63
+    /** @var SystemConfig */
64
+    private $systemConfig;
65
+
66
+    /** @var ILogger */
67
+    private $logger;
68
+
69
+    protected $lockedTable = null;
70
+
71
+    /** @var int */
72
+    protected $queriesBuilt = 0;
73
+
74
+    /** @var int */
75
+    protected $queriesExecuted = 0;
76
+
77
+    /**
78
+     * @throws Exception
79
+     */
80
+    public function connect() {
81
+        try {
82
+            return parent::connect();
83
+        } catch (Exception $e) {
84
+            // throw a new exception to prevent leaking info from the stacktrace
85
+            throw new Exception('Failed to connect to the database: ' . $e->getMessage(), $e->getCode());
86
+        }
87
+    }
88
+
89
+    public function getStats(): array {
90
+        return [
91
+            'built' => $this->queriesBuilt,
92
+            'executed' => $this->queriesExecuted,
93
+        ];
94
+    }
95
+
96
+    /**
97
+     * Returns a QueryBuilder for the connection.
98
+     */
99
+    public function getQueryBuilder(): IQueryBuilder {
100
+        $this->queriesBuilt++;
101
+        return new QueryBuilder(
102
+            new ConnectionAdapter($this),
103
+            $this->systemConfig,
104
+            $this->logger
105
+        );
106
+    }
107
+
108
+    /**
109
+     * Gets the QueryBuilder for the connection.
110
+     *
111
+     * @return \Doctrine\DBAL\Query\QueryBuilder
112
+     * @deprecated please use $this->getQueryBuilder() instead
113
+     */
114
+    public function createQueryBuilder() {
115
+        $backtrace = $this->getCallerBacktrace();
116
+        \OC::$server->getLogger()->debug('Doctrine QueryBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]);
117
+        $this->queriesBuilt++;
118
+        return parent::createQueryBuilder();
119
+    }
120
+
121
+    /**
122
+     * Gets the ExpressionBuilder for the connection.
123
+     *
124
+     * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder
125
+     * @deprecated please use $this->getQueryBuilder()->expr() instead
126
+     */
127
+    public function getExpressionBuilder() {
128
+        $backtrace = $this->getCallerBacktrace();
129
+        \OC::$server->getLogger()->debug('Doctrine ExpressionBuilder retrieved in {backtrace}', ['app' => 'core', 'backtrace' => $backtrace]);
130
+        $this->queriesBuilt++;
131
+        return parent::getExpressionBuilder();
132
+    }
133
+
134
+    /**
135
+     * Get the file and line that called the method where `getCallerBacktrace()` was used
136
+     *
137
+     * @return string
138
+     */
139
+    protected function getCallerBacktrace() {
140
+        $traces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
141
+
142
+        // 0 is the method where we use `getCallerBacktrace`
143
+        // 1 is the target method which uses the method we want to log
144
+        if (isset($traces[1])) {
145
+            return $traces[1]['file'] . ':' . $traces[1]['line'];
146
+        }
147
+
148
+        return '';
149
+    }
150
+
151
+    /**
152
+     * @return string
153
+     */
154
+    public function getPrefix() {
155
+        return $this->tablePrefix;
156
+    }
157
+
158
+    /**
159
+     * Initializes a new instance of the Connection class.
160
+     *
161
+     * @param array $params  The connection parameters.
162
+     * @param \Doctrine\DBAL\Driver $driver
163
+     * @param \Doctrine\DBAL\Configuration $config
164
+     * @param \Doctrine\Common\EventManager $eventManager
165
+     * @throws \Exception
166
+     */
167
+    public function __construct(array $params, Driver $driver, Configuration $config = null,
168
+        EventManager $eventManager = null) {
169
+        if (!isset($params['adapter'])) {
170
+            throw new \Exception('adapter not set');
171
+        }
172
+        if (!isset($params['tablePrefix'])) {
173
+            throw new \Exception('tablePrefix not set');
174
+        }
175
+        /**
176
+         * @psalm-suppress InternalMethod
177
+         */
178
+        parent::__construct($params, $driver, $config, $eventManager);
179
+        $this->adapter = new $params['adapter']($this);
180
+        $this->tablePrefix = $params['tablePrefix'];
181
+
182
+        $this->systemConfig = \OC::$server->getSystemConfig();
183
+        $this->logger = \OC::$server->getLogger();
184
+    }
185
+
186
+    /**
187
+     * Prepares an SQL statement.
188
+     *
189
+     * @param string $statement The SQL statement to prepare.
190
+     * @param int $limit
191
+     * @param int $offset
192
+     *
193
+     * @return Statement The prepared statement.
194
+     * @throws Exception
195
+     */
196
+    public function prepare($statement, $limit = null, $offset = null): Statement {
197
+        if ($limit === -1) {
198
+            $limit = null;
199
+        }
200
+        if (!is_null($limit)) {
201
+            $platform = $this->getDatabasePlatform();
202
+            $statement = $platform->modifyLimitQuery($statement, $limit, $offset);
203
+        }
204
+        $statement = $this->replaceTablePrefix($statement);
205
+        $statement = $this->adapter->fixupStatement($statement);
206
+
207
+        return parent::prepare($statement);
208
+    }
209
+
210
+    /**
211
+     * Executes an, optionally parametrized, SQL query.
212
+     *
213
+     * If the query is parametrized, a prepared statement is used.
214
+     * If an SQLLogger is configured, the execution is logged.
215
+     *
216
+     * @param string                                      $sql  The SQL query to execute.
217
+     * @param array                                       $params The parameters to bind to the query, if any.
218
+     * @param array                                       $types  The types the previous parameters are in.
219
+     * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp    The query cache profile, optional.
220
+     *
221
+     * @return Result The executed statement.
222
+     *
223
+     * @throws \Doctrine\DBAL\Exception
224
+     */
225
+    public function executeQuery(string $sql, array $params = [], $types = [], QueryCacheProfile $qcp = null): Result {
226
+        $sql = $this->replaceTablePrefix($sql);
227
+        $sql = $this->adapter->fixupStatement($sql);
228
+        $this->queriesExecuted++;
229
+        return parent::executeQuery($sql, $params, $types, $qcp);
230
+    }
231
+
232
+    /**
233
+     * @throws Exception
234
+     */
235
+    public function executeUpdate(string $sql, array $params = [], array $types = []): int {
236
+        $sql = $this->replaceTablePrefix($sql);
237
+        $sql = $this->adapter->fixupStatement($sql);
238
+        $this->queriesExecuted++;
239
+        return parent::executeUpdate($sql, $params, $types);
240
+    }
241
+
242
+    /**
243
+     * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
244
+     * and returns the number of affected rows.
245
+     *
246
+     * This method supports PDO binding types as well as DBAL mapping types.
247
+     *
248
+     * @param string $sql  The SQL query.
249
+     * @param array  $params The query parameters.
250
+     * @param array  $types  The parameter types.
251
+     *
252
+     * @return int The number of affected rows.
253
+     *
254
+     * @throws \Doctrine\DBAL\Exception
255
+     */
256
+    public function executeStatement($sql, array $params = [], array $types = []): int {
257
+        $sql = $this->replaceTablePrefix($sql);
258
+        $sql = $this->adapter->fixupStatement($sql);
259
+        $this->queriesExecuted++;
260
+        return parent::executeStatement($sql, $params, $types);
261
+    }
262
+
263
+    /**
264
+     * Returns the ID of the last inserted row, or the last value from a sequence object,
265
+     * depending on the underlying driver.
266
+     *
267
+     * Note: This method may not return a meaningful or consistent result across different drivers,
268
+     * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
269
+     * columns or sequences.
270
+     *
271
+     * @param string $seqName Name of the sequence object from which the ID should be returned.
272
+     *
273
+     * @return string the last inserted ID.
274
+     * @throws Exception
275
+     */
276
+    public function lastInsertId($seqName = null) {
277
+        if ($seqName) {
278
+            $seqName = $this->replaceTablePrefix($seqName);
279
+        }
280
+        return $this->adapter->lastInsertId($seqName);
281
+    }
282
+
283
+    /**
284
+     * @internal
285
+     * @throws Exception
286
+     */
287
+    public function realLastInsertId($seqName = null) {
288
+        return parent::lastInsertId($seqName);
289
+    }
290
+
291
+    /**
292
+     * Insert a row if the matching row does not exists. To accomplish proper race condition avoidance
293
+     * it is needed that there is also a unique constraint on the values. Then this method will
294
+     * catch the exception and return 0.
295
+     *
296
+     * @param string $table The table name (will replace *PREFIX* with the actual prefix)
297
+     * @param array $input data that should be inserted into the table  (column name => value)
298
+     * @param array|null $compare List of values that should be checked for "if not exists"
299
+     *				If this is null or an empty array, all keys of $input will be compared
300
+     *				Please note: text fields (clob) must not be used in the compare array
301
+     * @return int number of inserted rows
302
+     * @throws \Doctrine\DBAL\Exception
303
+     * @deprecated 15.0.0 - use unique index and "try { $db->insert() } catch (UniqueConstraintViolationException $e) {}" instead, because it is more reliable and does not have the risk for deadlocks - see https://github.com/nextcloud/server/pull/12371
304
+     */
305
+    public function insertIfNotExist($table, $input, array $compare = null) {
306
+        return $this->adapter->insertIfNotExist($table, $input, $compare);
307
+    }
308
+
309
+    public function insertIgnoreConflict(string $table, array $values) : int {
310
+        return $this->adapter->insertIgnoreConflict($table, $values);
311
+    }
312
+
313
+    private function getType($value) {
314
+        if (is_bool($value)) {
315
+            return IQueryBuilder::PARAM_BOOL;
316
+        } elseif (is_int($value)) {
317
+            return IQueryBuilder::PARAM_INT;
318
+        } else {
319
+            return IQueryBuilder::PARAM_STR;
320
+        }
321
+    }
322
+
323
+    /**
324
+     * Insert or update a row value
325
+     *
326
+     * @param string $table
327
+     * @param array $keys (column name => value)
328
+     * @param array $values (column name => value)
329
+     * @param array $updatePreconditionValues ensure values match preconditions (column name => value)
330
+     * @return int number of new rows
331
+     * @throws \Doctrine\DBAL\Exception
332
+     * @throws PreConditionNotMetException
333
+     */
334
+    public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []) {
335
+        try {
336
+            $insertQb = $this->getQueryBuilder();
337
+            $insertQb->insert($table)
338
+                ->values(
339
+                    array_map(function ($value) use ($insertQb) {
340
+                        return $insertQb->createNamedParameter($value, $this->getType($value));
341
+                    }, array_merge($keys, $values))
342
+                );
343
+            return $insertQb->execute();
344
+        } catch (NotNullConstraintViolationException $e) {
345
+            throw $e;
346
+        } catch (ConstraintViolationException $e) {
347
+            // value already exists, try update
348
+            $updateQb = $this->getQueryBuilder();
349
+            $updateQb->update($table);
350
+            foreach ($values as $name => $value) {
351
+                $updateQb->set($name, $updateQb->createNamedParameter($value, $this->getType($value)));
352
+            }
353
+            $where = $updateQb->expr()->andX();
354
+            $whereValues = array_merge($keys, $updatePreconditionValues);
355
+            foreach ($whereValues as $name => $value) {
356
+                if ($value === '') {
357
+                    $where->add($updateQb->expr()->emptyString(
358
+                        $name
359
+                    ));
360
+                } else {
361
+                    $where->add($updateQb->expr()->eq(
362
+                        $name,
363
+                        $updateQb->createNamedParameter($value, $this->getType($value)),
364
+                        $this->getType($value)
365
+                    ));
366
+                }
367
+            }
368
+            $updateQb->where($where);
369
+            $affected = $updateQb->execute();
370
+
371
+            if ($affected === 0 && !empty($updatePreconditionValues)) {
372
+                throw new PreConditionNotMetException();
373
+            }
374
+
375
+            return 0;
376
+        }
377
+    }
378
+
379
+    /**
380
+     * Create an exclusive read+write lock on a table
381
+     *
382
+     * @param string $tableName
383
+     *
384
+     * @throws \BadMethodCallException When trying to acquire a second lock
385
+     * @throws Exception
386
+     * @since 9.1.0
387
+     */
388
+    public function lockTable($tableName) {
389
+        if ($this->lockedTable !== null) {
390
+            throw new \BadMethodCallException('Can not lock a new table until the previous lock is released.');
391
+        }
392
+
393
+        $tableName = $this->tablePrefix . $tableName;
394
+        $this->lockedTable = $tableName;
395
+        $this->adapter->lockTable($tableName);
396
+    }
397
+
398
+    /**
399
+     * Release a previous acquired lock again
400
+     *
401
+     * @throws Exception
402
+     * @since 9.1.0
403
+     */
404
+    public function unlockTable() {
405
+        $this->adapter->unlockTable();
406
+        $this->lockedTable = null;
407
+    }
408
+
409
+    /**
410
+     * returns the error code and message as a string for logging
411
+     * works with DoctrineException
412
+     * @return string
413
+     */
414
+    public function getError() {
415
+        $msg = $this->errorCode() . ': ';
416
+        $errorInfo = $this->errorInfo();
417
+        if (!empty($errorInfo)) {
418
+            $msg .= 'SQLSTATE = '.$errorInfo[0] . ', ';
419
+            $msg .= 'Driver Code = '.$errorInfo[1] . ', ';
420
+            $msg .= 'Driver Message = '.$errorInfo[2];
421
+        }
422
+        return $msg;
423
+    }
424
+
425
+    public function errorCode() {
426
+        return -1;
427
+    }
428
+
429
+    public function errorInfo() {
430
+        return [];
431
+    }
432
+
433
+    /**
434
+     * Drop a table from the database if it exists
435
+     *
436
+     * @param string $table table name without the prefix
437
+     *
438
+     * @throws Exception
439
+     */
440
+    public function dropTable($table) {
441
+        $table = $this->tablePrefix . trim($table);
442
+        $schema = $this->getSchemaManager();
443
+        if ($schema->tablesExist([$table])) {
444
+            $schema->dropTable($table);
445
+        }
446
+    }
447
+
448
+    /**
449
+     * Check if a table exists
450
+     *
451
+     * @param string $table table name without the prefix
452
+     *
453
+     * @return bool
454
+     * @throws Exception
455
+     */
456
+    public function tableExists($table) {
457
+        $table = $this->tablePrefix . trim($table);
458
+        $schema = $this->getSchemaManager();
459
+        return $schema->tablesExist([$table]);
460
+    }
461
+
462
+    // internal use
463
+    /**
464
+     * @param string $statement
465
+     * @return string
466
+     */
467
+    protected function replaceTablePrefix($statement) {
468
+        return str_replace('*PREFIX*', $this->tablePrefix, $statement);
469
+    }
470
+
471
+    /**
472
+     * Check if a transaction is active
473
+     *
474
+     * @return bool
475
+     * @since 8.2.0
476
+     */
477
+    public function inTransaction() {
478
+        return $this->getTransactionNestingLevel() > 0;
479
+    }
480
+
481
+    /**
482
+     * Escape a parameter to be used in a LIKE query
483
+     *
484
+     * @param string $param
485
+     * @return string
486
+     */
487
+    public function escapeLikeParameter($param) {
488
+        return addcslashes($param, '\\_%');
489
+    }
490
+
491
+    /**
492
+     * Check whether or not the current database support 4byte wide unicode
493
+     *
494
+     * @return bool
495
+     * @since 11.0.0
496
+     */
497
+    public function supports4ByteText() {
498
+        if (!$this->getDatabasePlatform() instanceof MySQLPlatform) {
499
+            return true;
500
+        }
501
+        return $this->getParams()['charset'] === 'utf8mb4';
502
+    }
503
+
504
+
505
+    /**
506
+     * Create the schema of the connected database
507
+     *
508
+     * @return Schema
509
+     * @throws Exception
510
+     */
511
+    public function createSchema() {
512
+        $schemaManager = new MDB2SchemaManager($this);
513
+        $migrator = $schemaManager->getMigrator();
514
+        return $migrator->createSchema();
515
+    }
516
+
517
+    /**
518
+     * Migrate the database to the given schema
519
+     *
520
+     * @param Schema $toSchema
521
+     *
522
+     * @throws Exception
523
+     */
524
+    public function migrateToSchema(Schema $toSchema) {
525
+        $schemaManager = new MDB2SchemaManager($this);
526
+        $migrator = $schemaManager->getMigrator();
527
+        $migrator->migrate($toSchema);
528
+    }
529 529
 }
Please login to merge, or discard this patch.