SQLAnywhereStatement.php 9.99 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 13
use PDO;
use ReflectionClass;
use ReflectionObject;
use stdClass;
14 15 16 17 18 19
use const SASQL_BOTH;
use function array_key_exists;
use function func_get_args;
use function func_num_args;
use function gettype;
use function is_array;
Sergei Morozov's avatar
Sergei Morozov committed
20
use function is_int;
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
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;
38 39 40 41 42 43

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

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

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

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

56
    /** @var resource The result set resource to fetch. */
57
    private $result;
Steve Müller's avatar
Steve Müller committed
58

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

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

65 66 67 68 69 70 71 72 73 74
    /**
     * 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)
    {
75
        if (! is_resource($conn)) {
76 77 78 79 80 81
            throw new SQLAnywhereException('Invalid SQL Anywhere connection resource: ' . $conn);
        }

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

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

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

            case ParameterType::LARGE_OBJECT:
101 102
                $type = 'b';
                break;
103 104 105

            case ParameterType::NULL:
            case ParameterType::STRING:
106
            case ParameterType::BINARY:
107 108
                $type = 's';
                break;
109

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

114 115
        $this->boundValues[$column] =& $variable;

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

        return true;
    }

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

    /**
     * {@inheritdoc}
     *
     * @throws SQLAnywhereException
     */
    public function closeCursor()
    {
138
        if (! sasql_stmt_reset($this->stmt)) {
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 174 175
            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
176
        if (is_array($params)) {
177
            $hasZeroIndex = array_key_exists(0, $params);
Steve Müller's avatar
Steve Müller committed
178

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

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

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

        return true;
    }

    /**
     * {@inheritdoc}
     *
     * @throws SQLAnywhereException
     */
202
    public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
203
    {
204
        if (! is_resource($this->result)) {
205 206 207 208 209 210
            return false;
        }

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

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

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

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

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

                if (func_num_args() >= 2) {
Steve Müller's avatar
Steve Müller committed
225
                    $args      = func_get_args();
226
                    $className = $args[1];
227
                    $ctorArgs  = $args[2] ?? [];
228 229 230 231
                }

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

232
                if ($result instanceof stdClass) {
233 234 235 236
                    $result = $this->castObject($result, $className, $ctorArgs);
                }

                return $result;
237 238

            case FetchMode::NUMERIC:
239
                return sasql_fetch_row($this->result);
240 241

            case FetchMode::STANDARD_OBJECT:
242
                return sasql_fetch_object($this->result);
243

244 245 246 247 248 249 250 251
            default:
                throw new SQLAnywhereException('Fetch mode is not supported: ' . $fetchMode);
        }
    }

    /**
     * {@inheritdoc}
     */
252
    public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null)
253
    {
254
        $rows = [];
255 256

        switch ($fetchMode) {
257
            case FetchMode::CUSTOM_OBJECT:
258
                while (($row = $this->fetch(...func_get_args())) !== false) {
259 260 261
                    $rows[] = $row;
                }
                break;
262 263

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

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

        return $rows;
    }

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

285
        if ($row === false) {
286
            return false;
287 288
        }

289
        return $row[$columnIndex] ?? null;
290 291 292 293 294 295 296
    }

    /**
     * {@inheritdoc}
     */
    public function getIterator()
    {
297
        return new StatementIterator($this);
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
    }

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

    /**
     * {@inheritdoc}
     */
    public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null)
    {
Steve Müller's avatar
Steve Müller committed
313
        $this->defaultFetchMode          = $fetchMode;
314
        $this->defaultFetchClass         = $arg2 ?: $this->defaultFetchClass;
315
        $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs;
316 317

        return true;
318 319 320 321 322
    }

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

345 346
        $sourceReflection           = new ReflectionObject($sourceObject);
        $destinationClassReflection = new ReflectionObject($destinationClass);
347 348 349

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

            $name  = $sourceProperty->getName();
352 353 354 355
            $value = $sourceProperty->getValue($sourceObject);

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

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

        return $destinationClass;
    }
}