DB2Statement.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. <?php
  2. namespace Doctrine\DBAL\Driver\IBMDB2;
  3. use Doctrine\DBAL\Driver\Statement;
  4. use Doctrine\DBAL\Driver\StatementIterator;
  5. use Doctrine\DBAL\FetchMode;
  6. use Doctrine\DBAL\ParameterType;
  7. use IteratorAggregate;
  8. use PDO;
  9. use ReflectionClass;
  10. use ReflectionObject;
  11. use ReflectionProperty;
  12. use stdClass;
  13. use const CASE_LOWER;
  14. use const DB2_BINARY;
  15. use const DB2_CHAR;
  16. use const DB2_LONG;
  17. use const DB2_PARAM_FILE;
  18. use const DB2_PARAM_IN;
  19. use function array_change_key_case;
  20. use function db2_bind_param;
  21. use function db2_execute;
  22. use function db2_fetch_array;
  23. use function db2_fetch_assoc;
  24. use function db2_fetch_both;
  25. use function db2_fetch_object;
  26. use function db2_free_result;
  27. use function db2_num_fields;
  28. use function db2_num_rows;
  29. use function db2_stmt_error;
  30. use function db2_stmt_errormsg;
  31. use function error_get_last;
  32. use function fclose;
  33. use function func_get_args;
  34. use function func_num_args;
  35. use function fwrite;
  36. use function gettype;
  37. use function is_object;
  38. use function is_resource;
  39. use function is_string;
  40. use function ksort;
  41. use function sprintf;
  42. use function stream_copy_to_stream;
  43. use function stream_get_meta_data;
  44. use function strtolower;
  45. use function tmpfile;
  46. class DB2Statement implements IteratorAggregate, Statement
  47. {
  48. /** @var resource */
  49. private $stmt;
  50. /** @var mixed[] */
  51. private $bindParam = [];
  52. /**
  53. * Map of LOB parameter positions to the tuples containing reference to the variable bound to the driver statement
  54. * and the temporary file handle bound to the underlying statement
  55. *
  56. * @var mixed[][]
  57. */
  58. private $lobs = [];
  59. /** @var string Name of the default class to instantiate when fetching class instances. */
  60. private $defaultFetchClass = '\stdClass';
  61. /** @var mixed[] Constructor arguments for the default class to instantiate when fetching class instances. */
  62. private $defaultFetchClassCtorArgs = [];
  63. /** @var int */
  64. private $defaultFetchMode = FetchMode::MIXED;
  65. /**
  66. * Indicates whether the statement is in the state when fetching results is possible
  67. *
  68. * @var bool
  69. */
  70. private $result = false;
  71. /**
  72. * @param resource $stmt
  73. */
  74. public function __construct($stmt)
  75. {
  76. $this->stmt = $stmt;
  77. }
  78. /**
  79. * {@inheritdoc}
  80. */
  81. public function bindValue($param, $value, $type = ParameterType::STRING)
  82. {
  83. return $this->bindParam($param, $value, $type);
  84. }
  85. /**
  86. * {@inheritdoc}
  87. */
  88. public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
  89. {
  90. switch ($type) {
  91. case ParameterType::INTEGER:
  92. $this->bind($column, $variable, DB2_PARAM_IN, DB2_LONG);
  93. break;
  94. case ParameterType::LARGE_OBJECT:
  95. if (isset($this->lobs[$column])) {
  96. [, $handle] = $this->lobs[$column];
  97. fclose($handle);
  98. }
  99. $handle = $this->createTemporaryFile();
  100. $path = stream_get_meta_data($handle)['uri'];
  101. $this->bind($column, $path, DB2_PARAM_FILE, DB2_BINARY);
  102. $this->lobs[$column] = [&$variable, $handle];
  103. break;
  104. default:
  105. $this->bind($column, $variable, DB2_PARAM_IN, DB2_CHAR);
  106. break;
  107. }
  108. return true;
  109. }
  110. /**
  111. * @param int|string $parameter Parameter position or name
  112. * @param mixed $variable
  113. *
  114. * @throws DB2Exception
  115. */
  116. private function bind($parameter, &$variable, int $parameterType, int $dataType) : void
  117. {
  118. $this->bindParam[$parameter] =& $variable;
  119. if (! db2_bind_param($this->stmt, $parameter, 'variable', $parameterType, $dataType)) {
  120. throw new DB2Exception(db2_stmt_errormsg());
  121. }
  122. }
  123. /**
  124. * {@inheritdoc}
  125. */
  126. public function closeCursor()
  127. {
  128. if (! $this->stmt) {
  129. return false;
  130. }
  131. $this->bindParam = [];
  132. if (! db2_free_result($this->stmt)) {
  133. return false;
  134. }
  135. $this->result = false;
  136. return true;
  137. }
  138. /**
  139. * {@inheritdoc}
  140. */
  141. public function columnCount()
  142. {
  143. if (! $this->stmt) {
  144. return false;
  145. }
  146. return db2_num_fields($this->stmt);
  147. }
  148. /**
  149. * {@inheritdoc}
  150. */
  151. public function errorCode()
  152. {
  153. return db2_stmt_error();
  154. }
  155. /**
  156. * {@inheritdoc}
  157. */
  158. public function errorInfo()
  159. {
  160. return [
  161. db2_stmt_errormsg(),
  162. db2_stmt_error(),
  163. ];
  164. }
  165. /**
  166. * {@inheritdoc}
  167. */
  168. public function execute($params = null)
  169. {
  170. if (! $this->stmt) {
  171. return false;
  172. }
  173. if ($params === null) {
  174. ksort($this->bindParam);
  175. $params = [];
  176. foreach ($this->bindParam as $column => $value) {
  177. $params[] = $value;
  178. }
  179. }
  180. foreach ($this->lobs as [$source, $target]) {
  181. if (is_resource($source)) {
  182. $this->copyStreamToStream($source, $target);
  183. continue;
  184. }
  185. $this->writeStringToStream($source, $target);
  186. }
  187. $retval = db2_execute($this->stmt, $params);
  188. foreach ($this->lobs as [, $handle]) {
  189. fclose($handle);
  190. }
  191. $this->lobs = [];
  192. if ($retval === false) {
  193. throw new DB2Exception(db2_stmt_errormsg());
  194. }
  195. $this->result = true;
  196. return $retval;
  197. }
  198. /**
  199. * {@inheritdoc}
  200. */
  201. public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
  202. {
  203. $this->defaultFetchMode = $fetchMode;
  204. $this->defaultFetchClass = $arg2 ?: $this->defaultFetchClass;
  205. $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs;
  206. return true;
  207. }
  208. /**
  209. * {@inheritdoc}
  210. */
  211. public function getIterator()
  212. {
  213. return new StatementIterator($this);
  214. }
  215. /**
  216. * {@inheritdoc}
  217. */
  218. public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
  219. {
  220. // do not try fetching from the statement if it's not expected to contain result
  221. // in order to prevent exceptional situation
  222. if (! $this->result) {
  223. return false;
  224. }
  225. $fetchMode = $fetchMode ?: $this->defaultFetchMode;
  226. switch ($fetchMode) {
  227. case FetchMode::COLUMN:
  228. return $this->fetchColumn();
  229. case FetchMode::MIXED:
  230. return db2_fetch_both($this->stmt);
  231. case FetchMode::ASSOCIATIVE:
  232. return db2_fetch_assoc($this->stmt);
  233. case FetchMode::CUSTOM_OBJECT:
  234. $className = $this->defaultFetchClass;
  235. $ctorArgs = $this->defaultFetchClassCtorArgs;
  236. if (func_num_args() >= 2) {
  237. $args = func_get_args();
  238. $className = $args[1];
  239. $ctorArgs = $args[2] ?? [];
  240. }
  241. $result = db2_fetch_object($this->stmt);
  242. if ($result instanceof stdClass) {
  243. $result = $this->castObject($result, $className, $ctorArgs);
  244. }
  245. return $result;
  246. case FetchMode::NUMERIC:
  247. return db2_fetch_array($this->stmt);
  248. case FetchMode::STANDARD_OBJECT:
  249. return db2_fetch_object($this->stmt);
  250. default:
  251. throw new DB2Exception('Given Fetch-Style ' . $fetchMode . ' is not supported.');
  252. }
  253. }
  254. /**
  255. * {@inheritdoc}
  256. */
  257. public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
  258. {
  259. $rows = [];
  260. switch ($fetchMode) {
  261. case FetchMode::CUSTOM_OBJECT:
  262. while (($row = $this->fetch(...func_get_args())) !== false) {
  263. $rows[] = $row;
  264. }
  265. break;
  266. case FetchMode::COLUMN:
  267. while (($row = $this->fetchColumn()) !== false) {
  268. $rows[] = $row;
  269. }
  270. break;
  271. default:
  272. while (($row = $this->fetch($fetchMode)) !== false) {
  273. $rows[] = $row;
  274. }
  275. }
  276. return $rows;
  277. }
  278. /**
  279. * {@inheritdoc}
  280. */
  281. public function fetchColumn($columnIndex = 0)
  282. {
  283. $row = $this->fetch(FetchMode::NUMERIC);
  284. if ($row === false) {
  285. return false;
  286. }
  287. return $row[$columnIndex] ?? null;
  288. }
  289. /**
  290. * {@inheritdoc}
  291. */
  292. public function rowCount()
  293. {
  294. return @db2_num_rows($this->stmt) ? : 0;
  295. }
  296. /**
  297. * Casts a stdClass object to the given class name mapping its' properties.
  298. *
  299. * @param stdClass $sourceObject Object to cast from.
  300. * @param string|object $destinationClass Name of the class or class instance to cast to.
  301. * @param mixed[] $ctorArgs Arguments to use for constructing the destination class instance.
  302. *
  303. * @return object
  304. *
  305. * @throws DB2Exception
  306. */
  307. private function castObject(stdClass $sourceObject, $destinationClass, array $ctorArgs = [])
  308. {
  309. if (! is_string($destinationClass)) {
  310. if (! is_object($destinationClass)) {
  311. throw new DB2Exception(sprintf(
  312. 'Destination class has to be of type string or object, %s given.',
  313. gettype($destinationClass)
  314. ));
  315. }
  316. } else {
  317. $destinationClass = new ReflectionClass($destinationClass);
  318. $destinationClass = $destinationClass->newInstanceArgs($ctorArgs);
  319. }
  320. $sourceReflection = new ReflectionObject($sourceObject);
  321. $destinationClassReflection = new ReflectionObject($destinationClass);
  322. /** @var ReflectionProperty[] $destinationProperties */
  323. $destinationProperties = array_change_key_case($destinationClassReflection->getProperties(), CASE_LOWER);
  324. foreach ($sourceReflection->getProperties() as $sourceProperty) {
  325. $sourceProperty->setAccessible(true);
  326. $name = $sourceProperty->getName();
  327. $value = $sourceProperty->getValue($sourceObject);
  328. // Try to find a case-matching property.
  329. if ($destinationClassReflection->hasProperty($name)) {
  330. $destinationProperty = $destinationClassReflection->getProperty($name);
  331. $destinationProperty->setAccessible(true);
  332. $destinationProperty->setValue($destinationClass, $value);
  333. continue;
  334. }
  335. $name = strtolower($name);
  336. // Try to find a property without matching case.
  337. // Fallback for the driver returning either all uppercase or all lowercase column names.
  338. if (isset($destinationProperties[$name])) {
  339. $destinationProperty = $destinationProperties[$name];
  340. $destinationProperty->setAccessible(true);
  341. $destinationProperty->setValue($destinationClass, $value);
  342. continue;
  343. }
  344. $destinationClass->$name = $value;
  345. }
  346. return $destinationClass;
  347. }
  348. /**
  349. * @return resource
  350. *
  351. * @throws DB2Exception
  352. */
  353. private function createTemporaryFile()
  354. {
  355. $handle = @tmpfile();
  356. if ($handle === false) {
  357. throw new DB2Exception('Could not create temporary file: ' . error_get_last()['message']);
  358. }
  359. return $handle;
  360. }
  361. /**
  362. * @param resource $source
  363. * @param resource $target
  364. *
  365. * @throws DB2Exception
  366. */
  367. private function copyStreamToStream($source, $target) : void
  368. {
  369. if (@stream_copy_to_stream($source, $target) === false) {
  370. throw new DB2Exception('Could not copy source stream to temporary file: ' . error_get_last()['message']);
  371. }
  372. }
  373. /**
  374. * @param resource $target
  375. *
  376. * @throws DB2Exception
  377. */
  378. private function writeStringToStream(string $string, $target) : void
  379. {
  380. if (@fwrite($target, $string) === false) {
  381. throw new DB2Exception('Could not write string to temporary file: ' . error_get_last()['message']);
  382. }
  383. }
  384. }