connection = NULL; $this->lastError = 'Unknown Error'; $this->connectionData = $connectionData; $this->lastInsertId = 0; } public function isConnected(): bool { return !is_null($this->connection); } public function getLastError(): string { return trim($this->lastError); } public function createTable($tableName): CreateTable { return new CreateTable($this, $tableName); } public function insert($tableName, $columns=array()): Insert { return new Insert($this, $tableName, $columns); } public function select(...$columNames): Select { return new Select($this, $columNames); } public function truncate($table): Truncate { return new Truncate($this, $table); } public function delete($table): Delete { return new Delete($this, $table); } public function update($table): Update { return new Update($this, $table); } public function drop(string $table): Drop { return new Drop($this, $table); } public function alterTable($tableName): AlterTable { return new AlterTable($this, $tableName); } public function createTrigger($triggerName): CreateTrigger { return new CreateTrigger($this, $triggerName); } public function createProcedure(string $procName): CreateProcedure { return new CreateProcedure($this, $procName); } // #################### // ### ABSTRACT METHODS // #################### // Misc public abstract function checkRequirements(); public abstract function getDriverName(); // Connection Management public abstract function connect(); public abstract function disconnect(); public function executeQuery(Query $query, bool $fetchResult = false) { $parameters = []; $queryStr = $query->build($parameters); if($query->dump) { var_dump($queryStr); var_dump($parameters); } if ($queryStr === null) { return false; } $res = $this->execute($queryStr, $parameters, $fetchResult); $success = ($res !== FALSE); // fetch generated serial ids for Insert statements $generatedColumn = ($query instanceof Insert ? $query->getReturning() : null); if($success && $generatedColumn) { $this->fetchReturning($res, $generatedColumn); } return $fetchResult ? $res : $success; } public function getWhereClause($conditions, &$params): string { if (!$conditions) { return ""; } else { return " WHERE " . $this->buildCondition($conditions, $params); } } public function getConstraintDefinition(Constraint $constraint): ?string { $columnName = $this->columnName($constraint->getColumnNames()); if ($constraint instanceof PrimaryKey) { return "PRIMARY KEY ($columnName)"; } else if ($constraint instanceof Unique) { return "UNIQUE ($columnName)"; } else if ($constraint instanceof ForeignKey) { $refTable = $this->tableName($constraint->getReferencedTable()); $refColumn = $this->columnName($constraint->getReferencedColumn()); $strategy = $constraint->onDelete(); $code = "FOREIGN KEY ($columnName) REFERENCES $refTable ($refColumn)"; if ($strategy instanceof SetDefaultStrategy) { $code .= " ON DELETE SET DEFAULT"; } else if($strategy instanceof SetNullStrategy) { $code .= " ON DELETE SET NULL"; } else if($strategy instanceof CascadeStrategy) { $code .= " ON DELETE CASCADE"; } return $code; } else { $this->lastError = "Unsupported constraint type: " . get_class($constraint); return null; } } protected abstract function fetchReturning($res, string $returningCol); public abstract function getColumnDefinition(Column $column): ?string; public abstract function getOnDuplicateStrategy(?Strategy $strategy, &$params): ?string; public abstract function createTriggerBody(CreateTrigger $trigger): ?string; public abstract function getProcedureHead(CreateProcedure $procedure): ?string; public abstract function getColumnType(Column $column): ?string; public function getProcedureTail(): string { return ""; } public function getReturning(?string $columns): string { return ""; } public function getProcedureBody(CreateProcedure $procedure): string { $statements = ""; foreach ($procedure->getStatements() as $statement) { $statements .= $this->buildUnsafe($statement) . ";"; } return $statements; } protected function getUnsafeValue($value): ?string { if (is_string($value)) { return "'" . addslashes("$value") . "'"; // unsafe operation here... } else if (is_numeric($value) || is_bool($value)) { return $value; } else if ($value instanceof Column) { return $this->columnName($value); } else if ($value === null) { return "NULL"; } else { $this->lastError = "Cannot create unsafe value of type: " . gettype($value); return null; } } protected abstract function getValueDefinition($val); public abstract function addValue($val, &$params = NULL, bool $unsafe = false); protected abstract function buildUnsafe(Query $statement): string; public abstract function tableName($table): string; public abstract function columnName($col): string; // Special Keywords and functions public function now(): CurrentTimeStamp { return new CurrentTimeStamp(); } public function currentTimestamp(): CurrentTimeStamp { return new CurrentTimeStamp(); } public function count($col = NULL): Keyword { if (is_null($col)) { return new Keyword("COUNT(*) AS count"); } else if($col instanceof Keyword) { return new Keyword("COUNT(" . $col->getValue() . ") AS count"); } else { $countCol = strtolower(str_replace(".","_", $col)) . "_count"; $col = $this->columnName($col); return new Keyword("COUNT($col) AS $countCol"); } } public function distinct($col): Keyword { $col = $this->columnName($col); return new Keyword("DISTINCT($col)"); } // Statements protected abstract function execute($query, $values=NULL, $returnValues=false); protected function buildCondition($condition, &$params) { if ($condition instanceof CondOr) { $conditions = array(); foreach($condition->getConditions() as $cond) { $conditions[] = $this->buildCondition($cond, $params); } return "(" . implode(" OR ", $conditions) . ")"; } else if ($condition instanceof Compare) { $column = $this->columnName($condition->getColumn()); $value = $condition->getValue(); $operator = $condition->getOperator(); if ($value === null) { if ($operator === "=") { return "$column IS NULL"; } else if ($operator === "!=") { return "$column IS NOT NULL"; } } return $column . $operator . $this->addValue($value, $params); } else if ($condition instanceof CondBool) { return $this->columnName($condition->getValue()); } else if (is_array($condition)) { if (count($condition) === 1) { return $this->buildCondition($condition[0], $params); } else { $conditions = array(); foreach ($condition as $cond) { $conditions[] = $this->buildCondition($cond, $params); } return implode(" AND ", $conditions); } } else if($condition instanceof CondIn) { $expression = $condition->getExpression(); if (is_array($expression)) { $values = array(); foreach ($expression as $value) { $values[] = $this->addValue($value, $params); } $values = implode(",", $values); } else if($expression instanceof Select) { $values = $expression->build($params); } else { $this->lastError = "Unsupported in-expression value: " . get_class($condition); return false; } return $this->columnName($condition->getColumn()) . " IN ($values)"; } else if($condition instanceof CondKeyword) { $left = $condition->getLeftExp(); $right = $condition->getRightExp(); $keyword = $condition->getKeyword(); $left = ($left instanceof Column) ? $this->columnName($left->getName()) : $this->addValue($left, $params); $right = ($right instanceof Column) ? $this->columnName($right->getName()) : $this->addValue($right, $params); return "$left $keyword $right "; } else if($condition instanceof CondNot) { $expression = $condition->getExpression(); if ($expression instanceof Condition) { $expression = $this->buildCondition($expression, $params); } else { $expression = $this->columnName($expression); } return "NOT $expression"; } else if($condition instanceof CondNull) { return $this->columnName($condition->getColumn()) . " IS NULL"; } else { $this->lastError = "Unsupported condition type: " . get_class($condition); return null; } } protected function createExpression(Expression $exp, array &$params): ?string { if ($exp instanceof Column) { return $this->columnName($exp); } else if ($exp instanceof Query) { return "(" . $exp->build($params) . ")"; } else if ($exp instanceof CaseWhen) { $condition = $this->buildCondition($exp->getCondition(), $params); // psql requires constant values here $trueCase = $this->addValue($exp->getTrueCase(), $params, true); $falseCase = $this->addValue($exp->getFalseCase(), $params, true); return "CASE WHEN $condition THEN $trueCase ELSE $falseCase END"; } else if ($exp instanceof Sum) { $value = $this->addValue($exp->getValue(), $params); $alias = $exp->getAlias(); return "SUM($value) AS $alias"; } else { $this->lastError = "Unsupported expression type: " . get_class($exp); return null; } } public function setLastError($str) { $this->lastError = $str; } public function getLastInsertId(): int { return $this->lastInsertId; } public function close() { $this->disconnect(); $this->connection = NULL; } public static function createConnection(ConnectionData $connectionData) { $type = $connectionData->getProperty("type"); if ($type === "mysql") { $sql = new MySQL($connectionData); } else if ($type === "postgres") { $sql = new PostgreSQL($connectionData); } else { return "Unknown database type"; } if ($sql->checkRequirements()) { $sql->connect(); } return $sql; } public abstract function getStatus(); public function parseBool($val) : bool { return in_array($val, array(true, 1, '1', 't', 'true', 'TRUE'), true); } }