<?php declare(strict_types=1); namespace JKingWeb\Arsse\Db; use JKingWeb\DrUUID\UUID as UUID; abstract class AbstractDriver implements Driver { protected $locked = false; protected $transDepth = 0; protected $transStatus = []; public abstract function prepareArray(string $query, array $paramTypes): Statement; protected abstract function lock(): bool; protected abstract function unlock(bool $rollback = false) : bool; /** @codeCoverageIgnore */ public function schemaVersion(): int { // FIXME: generic schemaVersion() will need to be covered for database engines other than SQLite try { return (int) $this->query("SELECT value from arsse_meta where key is schema_version")->getValue(); } catch(Exception $e) { return 0; } } public function begin(bool $lock = false): Transaction { return new Transaction($this, $lock); } public function savepointCreate(bool $lock = false): int { if($lock && !$this->transDepth) { $this->lock(); $this->locked = true; } $this->exec("SAVEPOINT arsse_".(++$this->transDepth)); $this->transStatus[$this->transDepth] = self::TR_PEND; return $this->transDepth; } public function savepointRelease(int $index = null): bool { if(is_null($index)) { $index = $this->transDepth; } if(array_key_exists($index, $this->transStatus)) { switch($this->transStatus[$index]) { case self::TR_PEND: $this->exec("RELEASE SAVEPOINT arsse_".$index); $this->transStatus[$index] = self::TR_COMMIT; $a = $index; while(++$a && $a <= $this->transDepth) { if($this->transStatus[$a] <= self::TR_PEND) { $this->transStatus[$a] = self::TR_PEND_COMMIT; } } $out = true; break; case self::TR_PEND_COMMIT: $this->transStatus[$index] = self::TR_COMMIT; $out = true; break; case self::TR_PEND_ROLLBACK: $this->transStatus[$index] = self::TR_COMMIT; $out = false; break; case self::TR_COMMIT: case self::TR_ROLLBACK: throw new ExceptionSavepoint("stale", ['action' => "commit", 'index' => $index]); default: throw new Exception("unknownSavepointStatus", $this->transStatus[$index]); } if($index==$this->transDepth) { while($this->transDepth > 0 && $this->transStatus[$this->transDepth] > self::TR_PEND) { array_pop($this->transStatus); $this->transDepth--; } } if(!$this->transDepth && $this->locked) { $this->unlock(); $this->locked = false; } return $out; } else { throw new ExceptionSavepoint("invalid", ['action' => "commit", 'index' => $index]); } } public function savepointUndo(int $index = null): bool { if(is_null($index)) { $index = $this->transDepth; } if(array_key_exists($index, $this->transStatus)) { switch($this->transStatus[$index]) { case self::TR_PEND: $this->exec("ROLLBACK TRANSACTION TO SAVEPOINT arsse_".$index); $this->exec("RELEASE SAVEPOINT arsse_".$index); $this->transStatus[$index] = self::TR_ROLLBACK; $a = $index; while(++$a && $a <= $this->transDepth) { if($this->transStatus[$a] <= self::TR_PEND) { $this->transStatus[$a] = self::TR_PEND_ROLLBACK; } } $out = true; break; case self::TR_PEND_COMMIT: $this->transStatus[$index] = self::TR_ROLLBACK; $out = false; break; case self::TR_PEND_ROLLBACK: $this->transStatus[$index] = self::TR_ROLLBACK; $out = true; break; case self::TR_COMMIT: case self::TR_ROLLBACK: throw new ExceptionSavepoint("stale", ['action' => "rollback", 'index' => $index]); default: throw new Exception("unknownSavepointStatus", $this->transStatus[$index]); } if($index==$this->transDepth) { while($this->transDepth > 0 && $this->transStatus[$this->transDepth] > self::TR_PEND) { array_pop($this->transStatus); $this->transDepth--; } } if(!$this->transDepth && $this->locked) { $this->unlock(true); $this->locked = false; } return $out; } else { throw new ExceptionSavepoint("invalid", ['action' => "rollback", 'index' => $index]); } } public function prepare(string $query, ...$paramType): Statement { return $this->prepareArray($query, $paramType); } }