SQLAnywhereStatement.php 9.82 KB
Newer Older
1 2 3 4
<?php

namespace Doctrine\DBAL\Driver\SQLAnywhere;

5
use Doctrine\DBAL\Driver\Statement;
6
use Doctrine\DBAL\Driver\StatementIterator;
7 8
use Doctrine\DBAL\FetchMode;
use Doctrine\DBAL\ParameterType;
9
use IteratorAggregate;
10 11 12
use ReflectionClass;
use ReflectionObject;
use stdClass;
13 14
use const SASQL_BOTH;
use function array_key_exists;
15
use function count;
16 17
use function gettype;
use function is_array;
Sergei Morozov's avatar
Sergei Morozov committed
18
use function is_int;
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
use function is_object;
use function is_resource;
use function is_string;
use function sasql_fetch_array;
use function sasql_fetch_assoc;
use function sasql_fetch_object;
use function sasql_fetch_row;
use function sasql_prepare;
use function sasql_stmt_affected_rows;
use function sasql_stmt_bind_param_ex;
use function sasql_stmt_errno;
use function sasql_stmt_error;
use function sasql_stmt_execute;
use function sasql_stmt_field_count;
use function sasql_stmt_reset;
use function sasql_stmt_result_metadata;
use function sprintf;
36 37 38 39 40 41

/**
 * SAP SQL Anywhere implementation of the Statement interface.
 */
class SQLAnywhereStatement implements IteratorAggregate, Statement
{
42
    /** @var resource The connection resource. */
43
    private $conn;
Steve Müller's avatar
Steve Müller committed
44

45
    /** @var string Name of the default class to instantiate when fetching class instances. */
46
    private $defaultFetchClass = '\stdClass';
Steve Müller's avatar
Steve Müller committed
47

48
    /** @var mixed[] Constructor arguments for the default class to instantiate when fetching class instances. */
49
    private $defaultFetchClassCtorArgs = [];
Steve Müller's avatar
Steve Müller committed
50

51
    /** @var int Default fetch mode to use. */
52
    private $defaultFetchMode = FetchMode::MIXED;
Steve Müller's avatar
Steve Müller committed
53

54
    /** @var resource|null The result set resource to fetch. */
55
    private $result;
Steve Müller's avatar
Steve Müller committed
56

57
    /** @var resource The prepared SQL statement to execute. */
58 59
    private $stmt;

60 61 62
    /** @var mixed[] The references to bound parameter values. */
    private $boundValues = [];

63 64 65 66 67 68 69 70 71 72
    /**
     * Prepares given statement for given connection.
     *
     * @param resource $conn The connection resource to use.
     * @param string   $sql  The SQL statement to prepare.
     *
     * @throws SQLAnywhereException
     */
    public function __construct($conn, $sql)
    {
73
        if (! is_resource($conn)) {
74 75 76 77 78 79
            throw new SQLAnywhereException('Invalid SQL Anywhere connection resource: ' . $conn);
        }

        $this->conn = $conn;
        $this->stmt = sasql_prepare($conn, $sql);

80
        if (! is_resource($this->stmt)) {
81 82 83 84 85 86 87 88 89
            throw SQLAnywhereException::fromSQLAnywhereError($conn);
        }
    }

    /**
     * {@inheritdoc}
     *
     * @throws SQLAnywhereException
     */
90
    public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
91 92
    {
        switch ($type) {
93 94
            case ParameterType::INTEGER:
            case ParameterType::BOOLEAN:
95 96
                $type = 'i';
                break;
97 98

            case ParameterType::LARGE_OBJECT:
99 100
                $type = 'b';
                break;
101 102 103

            case ParameterType::NULL:
            case ParameterType::STRING:
104
            case ParameterType::BINARY:
105 106
                $type = 's';
                break;
107

108 109 110 111
            default:
                throw new SQLAnywhereException('Unknown type: ' . $type);
        }

112 113
        $this->boundValues[$column] =& $variable;

114
        if (! sasql_stmt_bind_param_ex($this->stmt, $column - 1, $variable, $type, $variable === null)) {
115 116 117 118 119 120 121 122 123
            throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
124
    public function bindValue($param, $value, $type = ParameterType::STRING)
125 126 127 128 129 130 131 132 133 134 135
    {
        return $this->bindParam($param, $value, $type);
    }

    /**
     * {@inheritdoc}
     *
     * @throws SQLAnywhereException
     */
    public function closeCursor()
    {
136
        if (! sasql_stmt_reset($this->stmt)) {
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
            throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function columnCount()
    {
        return sasql_stmt_field_count($this->stmt);
    }

    /**
     * {@inheritdoc}
     */
    public function errorCode()
    {
        return sasql_stmt_errno($this->stmt);
    }

    /**
     * {@inheritdoc}
     */
    public function errorInfo()
    {
        return sasql_stmt_error($this->stmt);
    }

    /**
     * {@inheritdoc}
     *
     * @throws SQLAnywhereException
     */
    public function execute($params = null)
    {
Steve Müller's avatar
Steve Müller committed
174
        if (is_array($params)) {
175
            $hasZeroIndex = array_key_exists(0, $params);
Steve Müller's avatar
Steve Müller committed
176

177
            foreach ($params as $key => $val) {
Sergei Morozov's avatar
Sergei Morozov committed
178 179 180 181 182
                if ($hasZeroIndex && is_int($key)) {
                    $this->bindValue($key + 1, $val);
                } else {
                    $this->bindValue($key, $val);
                }
183 184 185
            }
        }

186
        if (! sasql_stmt_execute($this->stmt)) {
187 188 189 190 191 192 193 194 195 196 197 198 199
            throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
        }

        $this->result = sasql_stmt_result_metadata($this->stmt);

        return true;
    }

    /**
     * {@inheritdoc}
     *
     * @throws SQLAnywhereException
     */
200
    public function fetch($fetchMode = null, ...$args)
201
    {
202
        if (! is_resource($this->result)) {
203 204 205 206 207 208
            return false;
        }

        $fetchMode = $fetchMode ?: $this->defaultFetchMode;

        switch ($fetchMode) {
209
            case FetchMode::COLUMN:
210 211
                return $this->fetchColumn();

212
            case FetchMode::ASSOCIATIVE:
213
                return sasql_fetch_assoc($this->result);
214 215

            case FetchMode::MIXED:
216
                return sasql_fetch_array($this->result, SASQL_BOTH);
217 218

            case FetchMode::CUSTOM_OBJECT:
219
                $className = $this->defaultFetchClass;
Steve Müller's avatar
Steve Müller committed
220
                $ctorArgs  = $this->defaultFetchClassCtorArgs;
221

222 223 224
                if (count($args) > 0) {
                    $className = $args[0];
                    $ctorArgs  = $args[1] ?? [];
225 226 227 228
                }

                $result = sasql_fetch_object($this->result);

229
                if ($result instanceof stdClass) {
230 231 232 233
                    $result = $this->castObject($result, $className, $ctorArgs);
                }

                return $result;
234 235

            case FetchMode::NUMERIC:
236
                return sasql_fetch_row($this->result);
237 238

            case FetchMode::STANDARD_OBJECT:
239
                return sasql_fetch_object($this->result);
240

241 242 243 244 245 246 247 248
            default:
                throw new SQLAnywhereException('Fetch mode is not supported: ' . $fetchMode);
        }
    }

    /**
     * {@inheritdoc}
     */
249
    public function fetchAll($fetchMode = null, ...$args)
250
    {
251
        $rows = [];
252 253

        switch ($fetchMode) {
254
            case FetchMode::CUSTOM_OBJECT:
255
                while (($row = $this->fetch($fetchMode, ...$args)) !== false) {
256 257 258
                    $rows[] = $row;
                }
                break;
259 260

            case FetchMode::COLUMN:
261
                while (($row = $this->fetchColumn()) !== false) {
262 263 264
                    $rows[] = $row;
                }
                break;
265

266
            default:
267
                while (($row = $this->fetch($fetchMode)) !== false) {
268 269 270 271 272 273 274 275 276 277 278 279
                    $rows[] = $row;
                }
        }

        return $rows;
    }

    /**
     * {@inheritdoc}
     */
    public function fetchColumn($columnIndex = 0)
    {
280
        $row = $this->fetch(FetchMode::NUMERIC);
281

282
        if ($row === false) {
283
            return false;
284 285
        }

286
        return $row[$columnIndex] ?? null;
287 288 289 290 291 292 293
    }

    /**
     * {@inheritdoc}
     */
    public function getIterator()
    {
294
        return new StatementIterator($this);
295 296 297 298 299
    }

    /**
     * {@inheritdoc}
     */
300
    public function rowCount() : int
301 302 303 304 305 306 307
    {
        return sasql_stmt_affected_rows($this->stmt);
    }

    /**
     * {@inheritdoc}
     */
308
    public function setFetchMode($fetchMode, ...$args)
309
    {
310 311 312 313 314 315 316 317 318 319 320
        $this->defaultFetchMode = $fetchMode;

        if (isset($args[0])) {
            $this->defaultFetchClass = $args[0];
        }

        if (isset($args[1])) {
            $this->defaultFetchClassCtorArgs = (array) $args[1];
        }

        return true;
321 322 323 324 325
    }

    /**
     * Casts a stdClass object to the given class name mapping its' properties.
     *
326
     * @param stdClass      $sourceObject     Object to cast from.
327
     * @param string|object $destinationClass Name of the class or class instance to cast to.
328
     * @param mixed[]       $ctorArgs         Arguments to use for constructing the destination class instance.
329 330 331 332 333
     *
     * @return object
     *
     * @throws SQLAnywhereException
     */
334
    private function castObject(stdClass $sourceObject, $destinationClass, array $ctorArgs = [])
335
    {
336 337
        if (! is_string($destinationClass)) {
            if (! is_object($destinationClass)) {
338
                throw new SQLAnywhereException(sprintf(
339 340
                    'Destination class has to be of type string or object, %s given.',
                    gettype($destinationClass)
341 342 343
                ));
            }
        } else {
344
            $destinationClass = new ReflectionClass($destinationClass);
345 346 347
            $destinationClass = $destinationClass->newInstanceArgs($ctorArgs);
        }

348 349
        $sourceReflection           = new ReflectionObject($sourceObject);
        $destinationClassReflection = new ReflectionObject($destinationClass);
350 351 352

        foreach ($sourceReflection->getProperties() as $sourceProperty) {
            $sourceProperty->setAccessible(true);
Steve Müller's avatar
Steve Müller committed
353 354

            $name  = $sourceProperty->getName();
355 356 357 358
            $value = $sourceProperty->getValue($sourceObject);

            if ($destinationClassReflection->hasProperty($name)) {
                $destinationProperty = $destinationClassReflection->getProperty($name);
Steve Müller's avatar
Steve Müller committed
359

360 361 362 363 364 365 366 367 368 369
                $destinationProperty->setAccessible(true);
                $destinationProperty->setValue($destinationClass, $value);
            } else {
                $destinationClass->$name = $value;
            }
        }

        return $destinationClass;
    }
}