DB2Statement.php 14 KB

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