Przeglądaj źródła

U Pdo core functions and add tryHandleException

Piotr Labudda 6 lat temu
rodzic
commit
672013b484
4 zmienionych plików z 145 dodań i 46 usunięć
  1. 66 46
      SE/se-lib/Core/Pdo.php
  2. 13 0
      SE/se-lib/Core/TypeBase.php
  3. 45 0
      SE/se-lib/Core/TypeFactory.php
  4. 21 0
      SE/se-lib/DB.php

+ 66 - 46
SE/se-lib/Core/Pdo.php

@@ -1,6 +1,7 @@
 <?php
 
 Lib::loadClass('DBG');
+Lib::loadClass('Core_TypeFactory'); // Core_TypeFactory::make, Core_iPdoSqlValue::toSqlQuote
 
 class Core_Pdo extends PDO {
 
@@ -36,26 +37,13 @@ class Core_Pdo extends PDO {
 	}
 
 	public function identifierQuote($identifier) {
-		switch (strtolower($this->_type)) {
-			// case 'pgsql': return $identifier;
-			case 'pgsql': return $this->pgsqlIdentifierQuote($identifier);
-			case 'mysql': return "`{$identifier}`";
-		}
-		return $identifier;
-	}
-	function pgsqlIdentifierQuote($identifier) {
-		if (false !== strpos($identifier, '.')) {
-			return implode('.', array_map(function ($token) {
-				return "\"{$token}\"";
-			}, explode('.', $identifier)));
-		}
-		return "\"{$identifier}\""; // https://www.postgresql.org/docs/9.1/sql-syntax-lexical.html
+		return DB::identifierQuote($this->_type, $identifier);
 	}
 
 	public function tableNameQuote($tableName) {
 		switch (strtolower($this->_type)) {
-			case 'pgsql': return ($this->_schema) ? "{$this->_schema}.{$tableName}" : $tableName; // "'{$identifier}'";
-			case 'mysql': return "`{$tableName}`";
+			case 'pgsql': return DB::identifierQuote($this->_type, ($this->_schema) ? "{$this->_schema}.{$tableName}" : $tableName);
+			case 'mysql': return DB::identifierQuote($this->_type, $tableName);
 		}
 		return $tableName;
 	}
@@ -465,6 +453,12 @@ EOF_STRUCT_MYSQL;
 		}, $this->fetchAllByKey($sql, $key, $values));
 	}
 
+	public function fetchKeyValueList($sql, $keyField, $valueField, $values = []) { // for sql like `select key, val from ...` @returns assoc array key => val
+		return array_map(function ($row) use ($valueField) {
+			return V::get($valueField, '', $row);
+		}, $this->fetchAllByKey($sql, $keyField, $values));
+	}
+
 	public function fetchFirst($sql, $values = []) { // fetch only first row
 		$sth = $this->prepare($sql);
 		if (!empty($values)) {
@@ -565,16 +559,16 @@ EOF_STRUCT_MYSQL;
 	public function insert($tableName, $item, $sqlSchema = []) {// @returns int last inserted id
 		if (empty($tableName)) throw new Exception("Missing table name");
 		if (!is_array($item)) throw new Exception("Missing item");
-		$sqlFields = [];
-		$sqlValues = [];
+		$sqlListFields = [];
+		$sqlListValues = [];
 		foreach ($item as $field => $val) {
-			$sqlFields[] = $this->identifierQuote($field);
-			$sqlValues[] = $this->convertValueToSqlSafe($val, V::get($field, null, $sqlSchema));
+			$sqlListFields[] = $this->identifierQuote($field);
+			$sqlListValues[] = $this->convertValueToSqlSafe($val, V::get($field, null, $sqlSchema));
 		}
 		$sqlTableName = $this->tableNameQuote($tableName);
 		$sql = "
-			insert into {$sqlTableName} (" . implode(", ", $sqlFields) . ")
-				values (" . implode(", ", $sqlValues) . ")
+			insert into {$sqlTableName} (" . implode(", ", $sqlListFields) . ")
+				values (" . implode(", ", $sqlListValues) . ")
 		";
 		$this->execSql($sql);
 		return $this->lastInsertId();
@@ -583,16 +577,16 @@ EOF_STRUCT_MYSQL;
 	public function insertIgnore($tableName, $item, $sqlSchema = []) {// @returns int last inserted id
 		if (empty($tableName)) throw new Exception("Missing table name");
 		if (!is_array($item)) throw new Exception("Missing item");
-		$sqlFields = [];
-		$sqlValues = [];
+		$sqlListFields = [];
+		$sqlListValues = [];
 		foreach ($item as $field => $val) {
-			$sqlFields[] = $this->identifierQuote($field);
-			$sqlValues[] = $this->convertValueToSqlSafe($val, V::get($field, null, $sqlSchema));
+			$sqlListFields[] = $this->identifierQuote($field);
+			$sqlListValues[] = $this->convertValueToSqlSafe($val, V::get($field, null, $sqlSchema));
 		}
 		$sqlTableName = $this->tableNameQuote($tableName);
 		$sql = "
-			insert ignore into {$sqlTableName} (" . implode(", ", $sqlFields) . ")
-				values (" . implode(", ", $sqlValues) . ")
+			insert ignore into {$sqlTableName} (" . implode(", ", $sqlListFields) . ")
+				values (" . implode(", ", $sqlListValues) . ")
 		";
 		$this->execSql($sql);
 		return $this->lastInsertId();
@@ -603,21 +597,24 @@ EOF_STRUCT_MYSQL;
 		if (empty($primaryKeyName)) throw new Exception("Missing primaryKey name");
 		if (empty($primaryKey)) throw new Exception("Missing primaryKey");
 		if (empty($item) || !is_array($item)) throw new Exception("Missing item");
-		$sqlPrimaryKey = $this->quote($primaryKey, PDO::PARAM_STR);
 		$sqlUpdateSet = [];
 		foreach ($item as $field => $val) {
-			$sqlUpdateSet[] = $this->identifierQuote($field) . " = " . $this->convertValueToSqlSafe($val, V::get($field, null, $sqlSchema));
+			$sqlFieldName = $this->identifierQuote($field);
+			$sqlValue = $this->convertValueToSqlSafe($val, V::get($field, null, $sqlSchema));
+			$sqlUpdateSet[] = "{$sqlFieldName} = {$sqlValue}";
 		}
-		$sqlTableName = $this->identifierQuote($tableName);
+		$sqlTableName = $this->tableNameQuote($tableName);
 		$sqlPkName = $this->identifierQuote($primaryKeyName);
+		$sqlPkValue = $this->quote($primaryKey, PDO::PARAM_STR);
 		$sql = "
 			update {$sqlTableName}
 			set " . implode("\n , ", $sqlUpdateSet) . "
-			where {$sqlPkName} = {$sqlPrimaryKey}
+			where {$sqlPkName} = {$sqlPkValue}
 		";
 		return $this->execSql($sql);
 	}
 
+	// fieldName => fieldValue, // will update if duplicate key (sql: `on duplicate key update`)
 	// '@insert' => [
 	// 	'A_RECORD_CREATE_AUTHOR' => User::getLogin(),
 	// 	'A_RECORD_CREATE_DATE' => 'NOW()',
@@ -629,31 +626,37 @@ EOF_STRUCT_MYSQL;
 	public function insertOrUpdate($tableName, $item, $sqlSchema = []) {
 		if (empty($tableName)) throw new Exception("Missing table name");
 		if (empty($item) || !is_array($item)) throw new Exception("Missing item");
-		$sqlFields = [];
-		$sqlValues = [];
+		$sqlListFields = [];
+		$sqlListValues = [];
 		$sqlUpdateSet = [];
 		foreach ($item as $field => $val) {
 			if ('@insert' == $field) continue;
 			if ('@update' == $field) continue;
-			$sqlVal = $this->convertValueToSqlSafe($val, V::get($field, null, $sqlSchema));
-			$sqlFields[] = "`{$field}`";
-			$sqlValues[] = $sqlVal;
-			$sqlUpdateSet[] = "`{$field}` = {$sqlVal}";
+			$sqlFieldName = $this->identifierQuote($field);
+			$sqlValue = $this->convertValueToSqlSafe($val, V::get($field, null, $sqlSchema));
+			$sqlListFields[] = $sqlFieldName;
+			$sqlListValues[] = $sqlValue;
+			$sqlUpdateSet[] = "{$sqlFieldName} = {$sqlValue}";
 		}
 		if (!empty($item['@insert'])) {
 			foreach ($item['@insert'] as $field => $val) {
-				$sqlFields[] = "`{$field}`";
-				$sqlValues[] = $this->convertValueToSqlSafe($val, V::get($field, null, $sqlSchema));
+				$sqlFieldName = $this->identifierQuote($field);
+				$sqlValue = $this->convertValueToSqlSafe($val, V::get($field, null, $sqlSchema));
+				$sqlListFields[] = $sqlFieldName;
+				$sqlListValues[] = $sqlValue;
 			}
 		}
 		if (!empty($item['@update'])) {
 			foreach ($item['@update'] as $field => $val) {
-				$sqlUpdateSet[] = "`{$field}` = " . $this->convertValueToSqlSafe($val, V::get($field, null, $sqlSchema));
+				$sqlFieldName = $this->identifierQuote($field);
+				$sqlValue = $this->convertValueToSqlSafe($val, V::get($field, null, $sqlSchema));
+				$sqlUpdateSet[] = "{$sqlFieldName} = {$sqlValue}";
 			}
 		}
+		$sqlTableName = $this->tableNameQuote($tableName);
 		$sql = "
-			insert into `{$tableName}` (" . implode(", ", $sqlFields) . ")
-				values (" . implode(", ", $sqlValues) . ")
+			insert into {$sqlTableName} (" . implode(", ", $sqlListFields) . ")
+				values (" . implode(", ", $sqlListValues) . ")
 		";
 		if (!empty($sqlUpdateSet)) $sql .= " on duplicate key update " . implode(", ", $sqlUpdateSet);
 		$affected = $this->execSql($sql);
@@ -661,10 +664,15 @@ EOF_STRUCT_MYSQL;
 	}
 
 	public function convertValueToSqlSafe($value, $xsdType = null) {
+		if (is_object($value)) {
+			if ($value instanceof Core_iPdoSqlValue) return $value->toSqlQuote($this); // TODO: use P5Type to convert to sql value
+			throw new Exception("DB Error: convertValueToSqlSafe value not implementing iPdoSqlValue '" . get_class($value) . "'");
+		}
 		if ('NOW()' === $value) return 'NOW()';
-		else if (NULL === $value) return 'NULL';
-		else if ('GeomFromText' == substr($value, 0, strlen('GeomFromText'))) return $value;
-		else return $this->quote($value, PDO::PARAM_STR);// TODO: use $sqlSchema if set
+		if (NULL === $value) return 'NULL';
+		if ('GeomFromText' == substr($value, 0, strlen('GeomFromText'))) return $value; // TODO: convert to Core_iPdoSqlValue, security
+		if ('FROM_UNIXTIME' == substr($value, 0, strlen('FROM_UNIXTIME'))) return $value; // TODO: convert to Core_iPdoSqlValue, security
+		return $this->quote($value, PDO::PARAM_STR); // TODO: use $sqlSchema if set
 	}
 
 	public function execSql($sql, $values = []) {
@@ -729,6 +737,18 @@ EOF_STRUCT_MYSQL;
 		return $return;
 	}
 
+
+	public function tryHandleException($handler, $methodName, $args) { // try again on exception
+		try {
+			return call_user_func_array([ $this, $methodName ], $args);
+		} catch (Exception $e) {
+			DBG::log("DBG:PDO->tryHandleException Exception trying to fix using handler ...");
+			DBG::log($e);
+			$handler($e);
+			return call_user_func_array([ $this, $methodName ], $args);
+		}
+	}
+
 }
 
 

+ 13 - 0
SE/se-lib/Core/TypeBase.php

@@ -0,0 +1,13 @@
+<?php
+
+class Core_TypeBase {
+
+	public $value;
+	public $params;
+
+	function __construct($value, $params = []) {
+		$this->value = $value;
+		$this->params = $params;
+	}
+
+}

+ 45 - 0
SE/se-lib/Core/TypeFactory.php

@@ -0,0 +1,45 @@
+<?php
+
+Lib::loadClass('Core_TypeBase');
+
+class Core_TypeFactory {
+
+	static function make($type, $value, $params = []) {
+		$lowerTypeName = strtolower($type);
+		$typeClassName = "Core_Type_{$lowerTypeName}";
+		if (!class_exists($typeClassName)) Lib::loadClass($typeClassName);
+		return new $typeClassName($value, $params);
+	}
+
+}
+
+interface Core_iPdoSqlValue {
+    public function toSqlQuote(PDO $pdo);  // $pdo: Core_Pdo ( Core_Pdo::getType(): mysql | mssql | psql )
+}
+
+
+class Core_Type_geomfromtext extends Core_TypeBase implements Core_iPdoSqlValue { // GeomFromText($wkt)
+
+	public function toSqlQuote(PDO $pdo) { // $pdo: Core_Pdo ( Core_Pdo::getType(): mysql | mssql | psql )
+		// TODO: if ($this->value instanceof Core_iPdoSqlValue)
+		switch ($pdo->getType()) {
+			case 'mysql': return "GeomFromText(" . $pdo->quote($this->value) . ")";
+			// case 'psql': return "GeomFromText(" . $pdo->quote($this->value) . ")";
+			default: throw new Exception("Not Implemented database type in Core_Type_geomfromtext::toSqlQuote '{$databaseType}' ");
+		}
+	}
+
+}
+
+class Core_Type_from_unixtime extends Core_TypeBase implements Core_iPdoSqlValue { // FROM_UNIXTIME(timestamp): '2000-01-01 00:00:00'
+
+	public function toSqlQuote(PDO $pdo) { // $pdo: Core_Pdo ( Core_Pdo::getType(): mysql | mssql | psql )
+		// TODO: if ($this->value instanceof Core_iPdoSqlValue)
+		switch ($pdo->getType()) {
+			case 'mysql': return "FROM_UNIXTIME(" . $pdo->quote($this->value) . ")";
+			// case 'psql': return "FROM_UNIXTIME(" . $pdo->quote($this->value) . ")";
+			default: throw new Exception("Not Implemented database type in Core_Type_from_unixtime::toSqlQuote '{$databaseType}' ");
+		}
+	}
+
+}

+ 21 - 0
SE/se-lib/DB.php

@@ -29,6 +29,7 @@ try {
 Lib::loadClass('Config');
 Lib::loadClass('DataSourceException');
 Lib::loadClass('Core_Pdo');
+Lib::loadClass('Core_TypeFactory');
 
 class DB {
 
@@ -171,6 +172,26 @@ class DB {
 		return $_instance[$dbConfName];
 	}
 
+	public static function identifierQuote($type, $identifier) { // $type: mysql | mssql | pgsql
+		switch (strtolower($type)) {
+			case 'pgsql': return self::pgsqlIdentifierQuote($identifier);
+			case 'mysql': return "`{$identifier}`";
+			default: throw new Exception("Not Implemented database type in identifierQuote '{$type}'");
+		}
+	}
+	public static function pgsqlIdentifierQuote($identifier) {
+		if (false !== strpos($identifier, '.')) {
+			return implode('.', array_map(function ($token) {
+				return "\"{$token}\"";
+			}, explode('.', $identifier)));
+		}
+		return "\"{$identifier}\""; // https://www.postgresql.org/docs/9.1/sql-syntax-lexical.html
+	}
+
+	public static function makeValue($type, $value, $params = []) {
+		return Core_TypeFactory::make($type, $value, $params);
+	}
+
 
 public static function connect() {
 	static $conn;