├── .editorconfig ├── .github └── workflows │ └── tests.yml ├── .gitignore ├── composer.json ├── phpunit.xml ├── readme.md ├── scripts ├── oci82.sh └── oci83.sh ├── src ├── Jfelder │ └── OracleDB │ │ ├── Connectors │ │ └── OracleConnector.php │ │ ├── OCI_PDO │ │ ├── OCI.php │ │ ├── OCIException.php │ │ └── OCIStatement.php │ │ ├── OracleConnection.php │ │ ├── OracleDBServiceProvider.php │ │ ├── Query │ │ ├── Grammars │ │ │ └── OracleGrammar.php │ │ ├── OracleBuilder.php │ │ └── Processors │ │ │ └── OracleProcessor.php │ │ └── Schema │ │ ├── Grammars │ │ └── OracleGrammar.php │ │ └── OracleBuilder.php └── config │ └── oracledb.php └── tests ├── OracleDBConnectionTest.php ├── OracleDBConnectorTest.php ├── OracleDBOCIProcessorTest.php ├── OracleDBOCIStatementTest.php ├── OracleDBOCITest.php ├── OracleDBPDOProcessorTest.php ├── OracleDBQueryBuilderTest.php ├── OracleDBQueryGrammarTest.php ├── OracleDBSchemaGrammarTest.php └── mocks ├── OCIFunctions.php ├── OCIMocks.php └── PDOMocks.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - '*.x' 8 | pull_request: 9 | schedule: 10 | - cron: '0 0 * * *' 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | tests: 17 | runs-on: ubuntu-latest 18 | services: 19 | oracle: 20 | image: deepdiver/docker-oracle-xe-11g:2.0 21 | ports: 22 | - 49160:22 23 | - 1521:1521 24 | 25 | strategy: 26 | fail-fast: true 27 | matrix: 28 | php: [8.2, 8.3] 29 | stability: [prefer-lowest, prefer-stable] 30 | 31 | name: PHP ${{ matrix.php }} - ${{ matrix.stability }} 32 | 33 | steps: 34 | - name: Checkout code 35 | uses: actions/checkout@v4 36 | 37 | - name: Setup PHP 38 | uses: shivammathur/setup-php@v2 39 | with: 40 | php-version: ${{ matrix.php }} 41 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite 42 | ini-values: error_reporting=E_ALL 43 | tools: composer:v2, pecl 44 | coverage: none 45 | 46 | - name: Setup OCI8 for PHP 8.2 47 | run: ./scripts/oci82.sh 48 | if: matrix.php == 8.2 49 | 50 | - name: Setup OCI8 for PHP 8.3 51 | run: ./scripts/oci83.sh 52 | if: matrix.php == 8.3 53 | 54 | - name: Install Composer dependencies 55 | run: composer install --prefer-dist --no-interaction --no-progress 56 | 57 | - name: Execute tests 58 | run: vendor/bin/phpunit --display-deprecation 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | /report 5 | /.idea 6 | atlassian-ide-plugin.xml 7 | /vagrant 8 | Vagrantfile 9 | .phpunit.result.cache 10 | /.phpunit.cache 11 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jfelder/oracledb", 3 | "description": "Oracle DB driver for Laravel", 4 | "keywords": [ 5 | "oracle", 6 | "laravel", 7 | "laravel 11", 8 | "pdo_oci", 9 | "oci8" 10 | ], 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Jimmy Felder", 15 | "email": "jimmyfelder@gmail.com" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.2", 20 | "illuminate/database": "^11.0|^12.0", 21 | "illuminate/pagination": "^11.0|^12.0" 22 | }, 23 | "require-dev": { 24 | "mockery/mockery": "^1.6", 25 | "phpunit/phpunit": "^11.0.1", 26 | "laravel/pint": "^1.13" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Jfelder\\": "src/Jfelder" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "Jfelder\\OracleDB\\Tests\\": "tests/" 36 | } 37 | }, 38 | "extra": { 39 | "laravel": { 40 | "providers": [ 41 | "Jfelder\\OracleDB\\OracleDBServiceProvider" 42 | ] 43 | } 44 | }, 45 | "minimum-stability": "stable" 46 | } 47 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | ./tests 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Laravel Oracle Database Package 2 | 3 | ### OracleDB (updated for Laravel 11) 4 | 5 | Build Status 6 | Total Downloads 7 | Latest Stable Version 8 | License 9 | 10 | OracleDB is an Oracle Database Driver package for [Laravel Framework](https://laravel.com) - thanks [@taylorotwell](https://github.com/taylorotwell). OracleDB is an extension of [Illuminate/Database](https://github.com/illuminate/database) that uses the [OCI8 Functions](https://www.php.net/manual/en/ref.oci8.php) wrapped into the PDO namespace. 11 | 12 | **Please report any bugs you may find.** 13 | 14 | - [Installation](#installation) 15 | - [Basic Usage](#basic-usage) 16 | - [Unimplemented Features](#unimplemented-features) 17 | - [License](#license) 18 | 19 | ### Installation 20 | 21 | With [Composer](https://getcomposer.org): 22 | 23 | ```sh 24 | composer require jfelder/oracledb 25 | ``` 26 | 27 | During this command, Laravel's "Auto-Discovery" feature should automatically register OracleDB's service 28 | provider. 29 | 30 | Next, publish OracleDB's configuration file using the vendor:publish Artisan command. This will copy OracleDB's 31 | configuration file to `config/oracledb.php` in your project. 32 | 33 | ```sh 34 | php artisan vendor:publish --tag=oracledb-config 35 | ``` 36 | 37 | To finish the installation, set your environment variables (typically in your .env file) to the corresponding 38 | env variables used in `config/oracledb.php`: such as `DB_HOST`, `DB_USERNAME`, etc. 39 | 40 | **Double Check Your Date Format Config** 41 | The `date_format` config powers all date column casting to/from Carbon and defaults to `Y-m-d H:i:s`, so it is imperative 42 | to set the DB_DATE_FORMAT env var if your db stringifies dates a different way, ie `d-M-y H:i:s`. This affects all 43 | read/write operations of any Eloquent model with date fields and any Query Builder queries that utilize a Carbon instance. 44 | 45 | ### Basic Usage 46 | The configuration file for this package is located at `config/oracledb.php`. 47 | In this file, you define all of your oracle database connections. If you need to make more than one connection, just 48 | copy the example one. If you want to make one of these connections the default connection, enter the name you gave the 49 | connection into the "Default Database Connection Name" section in `config/database.php`. 50 | 51 | Once you have configured the OracleDB database connection(s), you may run queries using the `DB` facade as normal. 52 | 53 | > **Note:** The default driver, `'oci8'`, makes OracleDB use the 54 | [OCI8 Functions](https://www.php.net/manual/en/ref.oci8.php) under the hood. If you want to use 55 | [PDO_OCI](https://www.php.net/manual/en/ref.pdo-oci.php) instead, change the `driver` value to `'pdo'` in the 56 | `config/oracledb.php` file. 57 | 58 | ```php 59 | $results = DB::select('select * from users where id = ?', [1]); 60 | ``` 61 | 62 | The above statement assumes you have set the default connection to be the oracle connection you setup in 63 | config/database.php file and will always return an `array` of results. 64 | 65 | ```php 66 | $results = DB::connection('oracle')->select('select * from users where id = ?', [1]); 67 | ``` 68 | 69 | Just like the built-in database drivers, you can use the connection method to access the oracle database(s) you setup 70 | in config/oracledb.php file. 71 | 72 | #### Inserting Records Into A Table With An Auto-Incrementing ID 73 | 74 | ```php 75 | $id = DB::connection('oracle')->table('users')->insertGetId( 76 | ['email' => 'john@example.com', 'votes' => 0], 'userid' 77 | ); 78 | ``` 79 | 80 | > **Note:** When using the insertGetId method, you can specify the auto-incrementing column name as the second 81 | parameter in insertGetId function. It will default to "id" if not specified. 82 | 83 | See [Laravel Database Basic Docs](https://laravel.com/docs/9.x/database) for more information. 84 | 85 | ### Unimplemented Features 86 | 87 | Some of the features available in the first-party Laravel database drivers are not implemented in this package. Pull 88 | requests are welcome for implementing any of these features, or for expanding this list if you find any unimplemented 89 | features not already listed. 90 | 91 | #### Query Builder 92 | 93 | - group limiting via a groupLimit clause `$query->groupLimit($value, $column);` note: this was only added to Laravel so Eloquent can limit the number of eagerly loaded results per parent 94 | - insertOrIgnore `DB::from('users')->insertOrIgnore(['email' => 'foo']);` 95 | - insertGetId with empty values `DB::from('users')->insertGetId([]);` (but calling with non-empty values is supported) 96 | - upserts `DB::from('users')->upsert([['email' => 'foo', 'name' => 'bar'], ['name' => 'bar2', 'email' => 'foo2']], 'email');` 97 | - deleting with a join `DB::from('users')->join('contacts', 'users.id', '=', 'contacts.id')->where('users.email', '=', 'foo')->delete();` 98 | - deleting with a limit `DB::from('users')->where('email', '=', 'foo')->orderBy('id')->take(1)->delete();` 99 | - json operations `DB::from('users')->where('items->sku', '=', 'foo-bar')->get();` 100 | - whereFulltext `DB::table('users')->whereFulltext('description', 'Hello World');` 101 | 102 | #### Eloquent 103 | 104 | - setting $guarded on an Eloquent model as anything other than an empty array. your models must either not define $guarded at all, or set it to an empty array. If not, Eloquent may attempt to run a column listing sql query resulting in an exception. 105 | - limiting the number of eagerly loaded results per parent, ie get only 3 posts per user `User::with(['posts' => fn ($query) => $query->limit(3)])->paginate();` 106 | 107 | #### Schema Builder 108 | 109 | - drop a table if it exists `Schema::dropIfExists('some_table');` 110 | - drop all tables, views, or types `Schema::dropAllTables()`, `Schema::dropAllViews()`, and `Schema::dropAllTypes()` 111 | - set collation on a table `$blueprint->collation('BINARY_CI')` 112 | - set collation on a column `$blueprint->string('some_column')->collation('BINARY_CI')` 113 | - set comments on a table `$blueprint->comment("This table is great.")` 114 | - set comments on a column `$blueprint->string('foo')->comment("Some helpful info about the foo column")` 115 | - set the starting value of an auto-incrementing column `$blueprint->increments('id')->startingValue(1000)` 116 | - create a private temporary table `$blueprint->temporary()` 117 | - rename an index `$blueprint->renameIndex('foo', 'bar')` 118 | - specify an algorithm when creating an index via the third argument `$blueprint->index(['foo', 'bar'], 'baz', 'hash')` 119 | - create a spatial index `$blueprint->spatialIndex('coordinates')` 120 | - create a spatial index fluently `$blueprint->point('coordinates')->spatialIndex()` 121 | - create a generated column, like the mysql driver has `virtualAs` and `storedAs` and postgres has `generatedAs`; ie, assuming an integer type column named price exists on the table, `$blueprint->integer('discounted_virtual')->virtualAs('price - 5')` 122 | - create a json column `$blueprint->json('foo')` or jsonb column `$blueprint->jsonb('foo')` (oracle recommends storing json in VARCHAR2, CLOB, or BLOB columns) 123 | - create a datetime with timezone column without precision `$blueprint->dateTimeTz('created_at')`, or with precision `$blueprint->timestampTz('created_at', 1)` 124 | - create Laravel-style timestamp columns having a timezone component `$blueprint->timestampsTz()` 125 | - create a uuid column `$blueprint->uuid('foo')` (oracle recommends a column of data type 16 byte raw for storing uuids) 126 | - create a foreign uuid column `$blueprint->foreignUuid('foo')` 127 | - create a column to hold IP addresses `$blueprint->ipAddress('foo')` (would be implemented as varchar2 45) 128 | - create a column to hold MAC addresses `$blueprint->macAddress('foo')` (would be implemented as varchar2 17) 129 | - create a geometry column `$blueprint->geometry('coordinates')` 130 | - create a geography column `$blueprint->geography('coordinates')` 131 | - create a timestamp column with `useCurrent` modifier `$blueprint->timestamp('created_at')->useCurrent()` 132 | 133 | ### License 134 | 135 | Licensed under the [MIT License](https://cheeaun.mit-license.org). 136 | -------------------------------------------------------------------------------- /scripts/oci82.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # install deps 4 | sudo apt-get update -qq 5 | sudo apt-get -y install -qq build-essential unzip wget libaio1 6 | 7 | # install oci8 libs & extension 8 | sudo mkdir -p /opt/oracle 9 | 10 | wget https://github.com/bumpx/oracle-instantclient/raw/master/instantclient-basic-linux.x64-12.1.0.2.0.zip 11 | wget https://github.com/bumpx/oracle-instantclient/raw/master/instantclient-sdk-linux.x64-12.1.0.2.0.zip 12 | 13 | sudo unzip -o ./instantclient-basic-linux.x64-12.1.0.2.0.zip -d /opt/oracle 14 | sudo unzip -o ./instantclient-sdk-linux.x64-12.1.0.2.0.zip -d /opt/oracle 15 | 16 | sudo ln -s /opt/oracle/instantclient/sqlplus /usr/bin/sqlplus 17 | sudo ln -s /opt/oracle/instantclient_12_1 /opt/oracle/instantclient 18 | sudo ln -s /opt/oracle/instantclient/libclntsh.so.12.1 /opt/oracle/instantclient/libclntsh.so 19 | sudo ln -s /opt/oracle/instantclient/libocci.so.12.1 /opt/oracle/instantclient/libocci.so 20 | 21 | sudo sh -c "echo 'instantclient,/opt/oracle/instantclient' | pecl install oci8" 22 | 23 | # setup ld library path 24 | sudo sh -c "echo '/opt/oracle/instantclient' >> /etc/ld.so.conf" 25 | sudo ldconfig 26 | 27 | # enable oci8 extension 28 | sudo sh -c "echo 'extension=oci8.so' >> /etc/php/8.2/cli/php.ini" -------------------------------------------------------------------------------- /scripts/oci83.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # install deps 4 | sudo apt-get update -qq 5 | sudo apt-get -y install -qq build-essential unzip wget libaio1 6 | 7 | # install oci8 libs & extension 8 | sudo mkdir -p /opt/oracle 9 | 10 | wget https://github.com/bumpx/oracle-instantclient/raw/master/instantclient-basic-linux.x64-12.1.0.2.0.zip 11 | wget https://github.com/bumpx/oracle-instantclient/raw/master/instantclient-sdk-linux.x64-12.1.0.2.0.zip 12 | 13 | sudo unzip -o ./instantclient-basic-linux.x64-12.1.0.2.0.zip -d /opt/oracle 14 | sudo unzip -o ./instantclient-sdk-linux.x64-12.1.0.2.0.zip -d /opt/oracle 15 | 16 | sudo ln -s /opt/oracle/instantclient/sqlplus /usr/bin/sqlplus 17 | sudo ln -s /opt/oracle/instantclient_12_1 /opt/oracle/instantclient 18 | sudo ln -s /opt/oracle/instantclient/libclntsh.so.12.1 /opt/oracle/instantclient/libclntsh.so 19 | sudo ln -s /opt/oracle/instantclient/libocci.so.12.1 /opt/oracle/instantclient/libocci.so 20 | 21 | sudo sh -c "echo 'instantclient,/opt/oracle/instantclient' | pecl install oci8" 22 | 23 | # setup ld library path 24 | sudo sh -c "echo '/opt/oracle/instantclient' >> /etc/ld.so.conf" 25 | sudo ldconfig 26 | 27 | # enable oci8 extension 28 | sudo sh -c "echo 'extension=oci8.so' >> /etc/php/8.3/cli/php.ini" -------------------------------------------------------------------------------- /src/Jfelder/OracleDB/Connectors/OracleConnector.php: -------------------------------------------------------------------------------- 1 | PDO::CASE_LOWER, 20 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, 21 | PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, 22 | ]; 23 | 24 | /** 25 | * Create a new PDO connection. 26 | * 27 | * @param string $dsn 28 | * @return PDO 29 | * 30 | * @throws InvalidArgumentException 31 | */ 32 | public function createConnection($dsn, array $config, array $options) 33 | { 34 | if ($config['driver'] === 'pdo') { 35 | return parent::createConnection($dsn, $config, $options); 36 | } elseif ($config['driver'] === 'oci8') { 37 | return new OCI($dsn, $config['username'], $config['password'], $options, $config['charset']); 38 | } 39 | 40 | throw new InvalidArgumentException('Unsupported driver ['.$config['driver'].'].'); 41 | } 42 | 43 | /** 44 | * Establish a database connection. 45 | * 46 | * @return PDO 47 | */ 48 | public function connect(array $config) 49 | { 50 | $dsn = $this->getDsn($config); 51 | 52 | $options = $this->getOptions($config); 53 | 54 | $connection = $this->createConnection($dsn, $config, $options); 55 | 56 | return $connection; 57 | } 58 | 59 | /** 60 | * Create a DSN string from a configuration. 61 | * 62 | * @return string 63 | */ 64 | protected function getDsn(array $config) 65 | { 66 | if (empty($config['tns'])) { 67 | $config['tns'] = "(DESCRIPTION =(ADDRESS = (PROTOCOL = TCP)(HOST = {$config['host']})(PORT = {$config['port']}))(CONNECT_DATA =(SID = {$config['database']})))"; 68 | } 69 | 70 | $rv = $config['tns']; 71 | 72 | if ($config['driver'] != 'oci8') { 73 | $rv = 'oci:dbname='.$rv.(empty($config['charset']) ? '' : ';charset='.$config['charset']); 74 | } 75 | 76 | return $rv; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Jfelder/OracleDB/OCI_PDO/OCI.php: -------------------------------------------------------------------------------- 1 | 1, 42 | PDO::ATTR_ERRMODE => 0, 43 | PDO::ATTR_CASE => 0, 44 | PDO::ATTR_ORACLE_NULLS => 0, 45 | ]; 46 | 47 | /** 48 | * @var bool Tracks if currently in a transaction 49 | */ 50 | protected $transaction = false; 51 | 52 | /** 53 | * @var int Mode for executing on Database Connection 54 | */ 55 | protected $mode = \OCI_COMMIT_ON_SUCCESS; 56 | 57 | /** 58 | * @var array PDO errorInfo array 59 | */ 60 | protected $error = [0 => '', 1 => null, 2 => null]; 61 | 62 | /** 63 | * @var string SQL statement to be run 64 | */ 65 | public $queryString = ''; 66 | 67 | /** 68 | * @var OCIStatement Statement object 69 | */ 70 | protected $stmt = null; 71 | 72 | /** 73 | * @var bool Set this to FALSE to turn debug output off or TRUE to turn it on. 74 | */ 75 | protected $internalDebug = false; 76 | 77 | /** 78 | * Constructor. 79 | * 80 | * @param string $dsn DSN string to connect to database 81 | * @param string $username Username of creditial to login to database 82 | * @param string $password Password of creditial to login to database 83 | * @param array $driver_options Options for the connection handle 84 | * @param string $charset Character set to specify to the database when connecting 85 | * 86 | * @throws OCIException if connection fails 87 | */ 88 | public function __construct($dsn, $username, $password, $driver_options = [], $charset = '') 89 | { 90 | $this->dsn = $dsn; 91 | $this->username = $username; 92 | $this->password = $password; 93 | $this->attributes = $driver_options + $this->attributes; 94 | $this->charset = $charset; 95 | 96 | if ($this->getAttribute(PDO::ATTR_PERSISTENT)) { 97 | $this->conn = oci_pconnect($username, $password, $dsn, $charset); 98 | } else { 99 | $this->conn = oci_connect($username, $password, $dsn, $charset); 100 | } 101 | 102 | // Check if connection was successful 103 | if (! $this->conn) { 104 | throw new OCIException($this->setErrorInfo('08006')); 105 | } 106 | } 107 | 108 | /** 109 | * Destructor - Checks for an oci resource and frees the resource if needed. 110 | */ 111 | public function __destruct() 112 | { 113 | if (strtolower(get_resource_type($this->conn)) === 'oci8') { 114 | oci_close($this->conn); 115 | } 116 | } 117 | 118 | /** 119 | * Initiates a transaction. 120 | * 121 | * @return bool Returns TRUE on success 122 | * 123 | * @throws OCIException If already in a transaction 124 | */ 125 | public function beginTransaction(): bool 126 | { 127 | if ($this->inTransaction()) { 128 | throw new OCIException($this->setErrorInfo('25000', '9999', 'Already in a transaction')); 129 | } 130 | 131 | $this->transaction = $this->setExecuteMode(\OCI_NO_AUTO_COMMIT); 132 | 133 | return true; 134 | } 135 | 136 | /** 137 | * Commits a transaction. 138 | * 139 | * @return bool Returns TRUE on success or FALSE on failure 140 | * 141 | * @throws OCIException If oci_commit fails 142 | */ 143 | public function commit(): bool 144 | { 145 | if ($this->inTransaction()) { 146 | $r = oci_commit($this->conn); 147 | if (! $r) { 148 | throw new OCIException($this->setErrorInfo('08007')); 149 | } 150 | $this->transaction = ! $this->flipExecuteMode(); 151 | 152 | return true; 153 | } 154 | 155 | return false; 156 | } 157 | 158 | /** 159 | * Fetch the SQLSTATE associated with the last operation on the database handle. 160 | * 161 | * @return mixed Returns SQLSTATE if available, otherwise null 162 | */ 163 | public function errorCode(): ?string 164 | { 165 | return empty($this->error[0]) ? null : $this->error[0]; 166 | } 167 | 168 | /** 169 | * Fetch extended error information associated with the last operation on the database handle. 170 | * 171 | * @return array Array of error information about the last operation performed 172 | */ 173 | public function errorInfo(): array 174 | { 175 | return $this->error; 176 | } 177 | 178 | /** 179 | * Execute an SQL statement and return the number of affected rows. 180 | * 181 | * @param string $statement The SQL statement to prepare and execute. 182 | * @return int Returns the number of rows that were modified or deleted by the statement 183 | */ 184 | public function exec(string $statement): int|false 185 | { 186 | $this->prepare($statement); 187 | 188 | $result = $this->stmt->execute(); 189 | 190 | return $result ? $this->stmt->rowCount() : false; 191 | } 192 | 193 | /** 194 | * Retrieve a database connection attribute. 195 | * 196 | * @param int $attribute One of the PDO::ATTR_* constants. 197 | * @return mixed The value of the requested PDO attribute or null if it does not exist. 198 | */ 199 | public function getAttribute(int $attribute): mixed 200 | { 201 | return $this->attributes[$attribute] ?? null; 202 | } 203 | 204 | /** 205 | * Checks if inside a transaction. 206 | * 207 | * @return bool Returns TRUE if a transaction is currently active, and FALSE if not. 208 | */ 209 | public function inTransaction(): bool 210 | { 211 | return $this->transaction; 212 | } 213 | 214 | /** 215 | * Returns the ID of the last inserted row or sequence value. 216 | * 217 | * @throws OCIException This feature is not supported 218 | */ 219 | public function lastInsertId(?string $name = null): string|false 220 | { 221 | throw new OCIException($this->setErrorInfo('IM001', '0000', 'Driver does not support this function')); 222 | } 223 | 224 | /** 225 | * Prepares a statement for execution and returns a Jfelder\OracleDB\OCI_PDO\OCIStatement object. 226 | * 227 | * @param string $query Valid SQL statement for the target database server. 228 | * @param array $options Attribute values for the OCIStatement object 229 | * @return mixed Returns a OCIStatement on success, false otherwise 230 | */ 231 | public function prepare(string $query, array $options = []): OCIStatement|false 232 | { 233 | $tokens = explode('?', $query); 234 | 235 | $count = count($tokens) - 1; 236 | if ($count) { 237 | $query = ''; 238 | for ($i = 0; $i < $count; $i++) { 239 | $query .= trim($tokens[$i])." :{$i} "; 240 | } 241 | $query .= trim($tokens[$i]); 242 | } 243 | 244 | $this->queryString = $query; 245 | $stmt = oci_parse($this->conn, $this->queryString); 246 | $this->stmt = new OCIStatement($stmt, $this, $this->queryString, $options); 247 | 248 | return $this->stmt; 249 | } 250 | 251 | /** 252 | * Executes an SQL statement, returning a result set as a Jfelder\OracleDB\OCI_PDO\OCIStatement object 253 | * on success or false on failure. 254 | * 255 | * @param string $query Valid SQL statement for the target database server. 256 | * @param int $fetchMode The fetch mode must be one of the PDO::FETCH_* constants. 257 | * @param mixed ...$fetchModeArgs Has no effect; was only included to extend parent. 258 | * @return mixed Returns a OCIStatement on success, false otherwise 259 | */ 260 | public function query(string $query, ?int $fetchMode = null, mixed ...$fetchModeArgs): OCIStatement|false 261 | { 262 | $this->prepare($query); 263 | 264 | if ($fetchMode) { 265 | $this->stmt->setFetchMode($fetchMode, $fetchModeArgs); 266 | } 267 | 268 | $result = $this->stmt->execute(); 269 | 270 | return $result ? $this->stmt : false; 271 | } 272 | 273 | /** 274 | * Quotes a string for use in a query. 275 | * 276 | * @param string $string The string to be quoted. 277 | * @param int $type Provides a data type hint for drivers that have alternate quoting styles. 278 | * @return string Returns false 279 | */ 280 | public function quote(string $string, int $type = PDO::PARAM_STR): string|false 281 | { 282 | return false; 283 | } 284 | 285 | /** 286 | * Rolls back a transaction. 287 | * 288 | * @return bool Returns TRUE on success or FALSE on failure. 289 | * 290 | * @throws OCIException If oci_rollback returns an error. 291 | */ 292 | public function rollBack(): bool 293 | { 294 | if ($this->inTransaction()) { 295 | $r = oci_rollback($this->conn); 296 | if (! $r) { 297 | throw new OCIException($this->setErrorInfo('40003')); 298 | } 299 | $this->transaction = ! $this->flipExecuteMode(); 300 | 301 | return true; 302 | } 303 | 304 | return false; 305 | } 306 | 307 | /** 308 | * Set an attribute. 309 | * 310 | * @param int $attribute PDO::ATTR_* attribute identifier 311 | * @param mixed $value Value of PDO::ATTR_* attribute 312 | * @return true 313 | */ 314 | public function setAttribute(int $attribute, mixed $value): bool 315 | { 316 | $this->attributes[$attribute] = $value; 317 | 318 | return true; 319 | } 320 | 321 | /** 322 | * CUSTOM CODE FROM HERE DOWN. 323 | * 324 | * All code above this is overriding the PDO base code 325 | * All code below this are custom helpers or other functionality provided by the oci_* functions 326 | */ 327 | 328 | /** 329 | * Flip the execute mode. 330 | * 331 | * @return int Returns true 332 | */ 333 | public function flipExecuteMode() 334 | { 335 | $this->setExecuteMode($this->getExecuteMode() == \OCI_COMMIT_ON_SUCCESS ? \OCI_NO_AUTO_COMMIT : \OCI_COMMIT_ON_SUCCESS); 336 | 337 | return true; 338 | } 339 | 340 | /** 341 | * Get the current Execute Mode for the conneciton. 342 | * 343 | * @return int Either \OCI_COMMIT_ON_SUCCESS or \OCI_NO_AUTO_COMMIT 344 | */ 345 | public function getExecuteMode() 346 | { 347 | return $this->mode; 348 | } 349 | 350 | /** 351 | * Returns the oci8 connection handle for use with other oci_ functions. 352 | * 353 | * @return bool|oci8|resource|string The oci8 connection handle 354 | */ 355 | public function getOCIResource() 356 | { 357 | return $this->conn; 358 | } 359 | 360 | /** 361 | * Set the PDO errorInfo array values. 362 | * 363 | * @param string $code SQLSTATE identifier 364 | * @param string $error Driver error code 365 | * @param string $message Driver error message 366 | * @return array Returns the PDO errorInfo array 367 | */ 368 | private function setErrorInfo($code = null, $error = null, $message = null) 369 | { 370 | if (is_null($code)) { 371 | $code = 'JF000'; 372 | } 373 | 374 | if (is_null($error)) { 375 | $e = oci_error($this->conn); 376 | $error = $e['code']; 377 | $message = $e['message'].(empty($e['sqltext']) ? '' : ' - SQL: '.$e['sqltext']); 378 | } 379 | 380 | $this->error[0] = $code; 381 | $this->error[1] = $error; 382 | $this->error[2] = $message; 383 | 384 | return $this->error; 385 | } 386 | 387 | /** 388 | * Set the execute mode for the connection. 389 | * 390 | * @param int $mode Either \OCI_COMMIT_ON_SUCCESS or \OCI_NO_AUTO_COMMIT 391 | * @return bool 392 | * 393 | * @throws OCIException If any value other than the above are passed in 394 | */ 395 | public function setExecuteMode($mode) 396 | { 397 | if ($mode === \OCI_COMMIT_ON_SUCCESS || $mode === \OCI_NO_AUTO_COMMIT) { 398 | $this->mode = $mode; 399 | 400 | return true; 401 | } 402 | 403 | throw new OCIException($this->setErrorInfo('0A000', '9999', "Invalid commit mode specified: {$mode}")); 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /src/Jfelder/OracleDB/OCI_PDO/OCIException.php: -------------------------------------------------------------------------------- 1 | errorInfo = $e; 17 | $this->code = $e[1]; 18 | $this->message = "SQLSTATE[$e[0]] ".$e[2]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Jfelder/OracleDB/OCI_PDO/OCIStatement.php: -------------------------------------------------------------------------------- 1 | OCI data types conversion var 37 | */ 38 | protected $datatypes = [ 39 | PDO::PARAM_BOOL => \SQLT_INT, 40 | // there is no SQLT_NULL, but oracle will insert a null value if it receives an empty string 41 | PDO::PARAM_NULL => \SQLT_CHR, 42 | PDO::PARAM_INT => \SQLT_INT, 43 | PDO::PARAM_STR => \SQLT_CHR, 44 | PDO::PARAM_INPUT_OUTPUT => \SQLT_CHR, 45 | PDO::PARAM_LOB => \SQLT_BLOB, 46 | ]; 47 | 48 | /** 49 | * @var array PDO errorInfo array 50 | */ 51 | protected $error = [ 52 | 0 => '', 53 | 1 => null, 54 | 2 => null, 55 | ]; 56 | 57 | /** 58 | * @var array Array to hold column bindings 59 | */ 60 | protected $bindings = []; 61 | 62 | /** 63 | * @var array Array to hold descriptors 64 | */ 65 | protected $descriptors = []; 66 | 67 | /** 68 | * Constructor. 69 | * 70 | * @param resource $stmt Statement handle created with oci_parse() 71 | * @param OCI $oci The OCI object for this statement 72 | * @param array $options Options for the statement handle 73 | * 74 | * @throws OCIException if $stmt is not a vaild oci8 statement resource 75 | */ 76 | public function __construct($stmt, OCI $oci, $sql = '', $options = []) 77 | { 78 | $resource_type = strtolower(get_resource_type($stmt)); 79 | 80 | if ($resource_type !== 'oci8 statement') { 81 | throw new OCIException($this->setErrorInfo('0A000', '9999', "Invalid resource received: {$resource_type}")); 82 | } 83 | 84 | $this->stmt = $stmt; 85 | $this->conn = $oci; 86 | $this->sql = $sql; 87 | $this->attributes = $options; 88 | } 89 | 90 | /** 91 | * Destructor - Checks for an oci statment resource and frees the resource if needed. 92 | */ 93 | public function __destruct() 94 | { 95 | if (strtolower(get_resource_type($this->stmt)) == 'oci8 statement') { 96 | oci_free_statement($this->stmt); 97 | } 98 | 99 | //Also test for descriptors 100 | } 101 | 102 | /** 103 | * Bind a column to a PHP variable. 104 | * 105 | * @param mixed $column Number of the column (1-indexed) in the result set 106 | * @param mixed $var Name of the PHP variable to which the column will be bound. 107 | * @param int $type Data type of the parameter, specified by the PDO::PARAM_* constants. 108 | * @param int $maxLength A hint for pre-allocation. 109 | * @param mixed $driverOptions Optional parameter(s) for the driver. 110 | * @return bool Returns TRUE on success or FALSE on failure. 111 | * 112 | * @throws \InvalidArgumentException If an unknown data type is passed in 113 | */ 114 | public function bindColumn(string|int $column, mixed &$var, ?int $type = null, int $maxLength = 0, mixed $driverOptions = null): bool 115 | { 116 | if (! is_numeric($column) || $column < 1) { 117 | throw new \InvalidArgumentException("Invalid column specified: {$column}"); 118 | } 119 | 120 | if (! isset($this->datatypes[$type])) { 121 | throw new \InvalidArgumentException("Unknown data type in oci_bind_by_name: {$type}"); 122 | } 123 | 124 | $this->bindings[$column] = [ 125 | 'var' => &$var, 126 | 'data_type' => $type, 127 | 'max_length' => $maxLength, 128 | 'driverdata' => $driverOptions, 129 | ]; 130 | 131 | return true; 132 | } 133 | 134 | /** 135 | * Binds a parameter to the specified variable name. 136 | * 137 | * @param mixed $param Parameter identifier 138 | * @param mixed $var Name of the PHP variable to bind to the SQL statement parameter 139 | * @param int $type Explicit data type for the parameter using the PDO::PARAM_* constants 140 | * @param int $maxLength Length of the data type 141 | * @return bool Returns TRUE on success or FALSE on failure 142 | * 143 | * @throws \InvalidArgumentException If an unknown data type is passed in 144 | */ 145 | public function bindParam(string|int $param, mixed &$var, int $type = PDO::PARAM_STR, int $maxLength = -1, mixed $driverOptions = null): bool 146 | { 147 | if (is_numeric($param)) { 148 | $param = ":{$param}"; 149 | } 150 | 151 | $this->addParameter($param, $var, $type, $maxLength, $driverOptions); 152 | 153 | if (! isset($this->datatypes[$type])) { 154 | if ($type === (PDO::PARAM_INT | PDO::PARAM_INPUT_OUTPUT)) { 155 | $type = PDO::PARAM_STR; 156 | $maxLength = $maxLength > 40 ? $maxLength : 40; 157 | } else { 158 | throw new \InvalidArgumentException("Unknown data type in oci_bind_by_name: {$type}"); 159 | } 160 | } 161 | 162 | $result = oci_bind_by_name($this->stmt, $param, $var, $maxLength, $this->datatypes[$type]); 163 | 164 | return $result; 165 | } 166 | 167 | /** 168 | * Binds a value to a parameter. 169 | * 170 | * @param mixed $param Parameter identifier. 171 | * @param mixed $value The value to bind to the parameter 172 | * @param int $type Explicit data type for the parameter using the PDO::PARAM_* constants 173 | * @return bool Returns TRUE on success or FALSE on failure. 174 | * 175 | * @throws \InvalidArgumentException If an unknown data type is passed in 176 | */ 177 | public function bindValue(string|int $param, mixed $value, int $type = PDO::PARAM_STR): bool 178 | { 179 | if (is_numeric($param)) { 180 | $param = ":{$param}"; 181 | } 182 | 183 | $this->addParameter($param, $value, $type); 184 | 185 | if (! isset($this->datatypes[$type])) { 186 | throw new \InvalidArgumentException("Unknown data type in oci_bind_by_name: {$type}"); 187 | } 188 | 189 | $result = oci_bind_by_name($this->stmt, $param, $value, -1, $this->datatypes[$type]); 190 | 191 | return $result; 192 | } 193 | 194 | /** 195 | * Closes the cursor, enabling the statement to be executed again. 196 | * 197 | * Todo implement this method instead of always returning true 198 | * 199 | * @return bool Returns TRUE on success or FALSE on failure. 200 | */ 201 | public function closeCursor(): bool 202 | { 203 | return true; 204 | } 205 | 206 | /** 207 | * Returns the number of columns in the result set. 208 | * 209 | * @return int Returns the number of columns in the result set represented by the PDOStatement object. 210 | * If there is no result set, returns 0. 211 | */ 212 | public function columnCount(): int 213 | { 214 | return oci_num_fields($this->stmt); 215 | } 216 | 217 | /** 218 | * Dump an SQL prepared command directly to the normal output. 219 | * 220 | * @return bool Returns true. 221 | */ 222 | public function debugDumpParams(): ?bool 223 | { 224 | return print_r(['sql' => $this->sql, 'params' => $this->parameters]); 225 | } 226 | 227 | /** 228 | * Fetch the SQLSTATE associated with the last operation on the statement handle. 229 | * 230 | * @return mixed Returns an SQLSTATE or NULL if no operation has been run 231 | */ 232 | public function errorCode(): ?string 233 | { 234 | return empty($this->error[0]) ? null : $this->error[0]; 235 | } 236 | 237 | /** 238 | * Fetch extended error information associated with the last operation on the statement handle. 239 | * 240 | * @return array array of error information about the last operation performed 241 | */ 242 | public function errorInfo(): array 243 | { 244 | return $this->error; 245 | } 246 | 247 | /** 248 | * Executes a prepared statement. 249 | * 250 | * @param array $params An array of values with as many elements as there are bound parameters in the 251 | * SQL statement being executed 252 | * @return bool Returns TRUE on success or FALSE on failure. 253 | */ 254 | public function execute(?array $params = null): bool 255 | { 256 | if (is_array($params)) { 257 | foreach ($params as $k => $v) { 258 | $this->bindParam($k, $params[$k]); 259 | } 260 | } 261 | 262 | $result = oci_execute($this->stmt, $this->conn->getExecuteMode()); 263 | 264 | if (! $result) { 265 | $this->setErrorInfo('07000'); 266 | } 267 | 268 | $this->processBindings($result); 269 | 270 | return $result; 271 | } 272 | 273 | /** 274 | * Fetches the next row from a result set. 275 | * 276 | * @param int $mode Controls how the next row will be returned to the caller. This value must be one of 277 | * the PDO::FETCH_* constants 278 | * @param int $cursorOrientation Has no effect; was only included to extend parent. 279 | * @param int $cursorOffset Has no effect; was only included to extend parent. 280 | * @return mixed The return value of this function on success depends on the fetch type. 281 | * In all cases, FALSE is returned on failure. 282 | */ 283 | public function fetch(int $mode = PDO::FETCH_CLASS, int $cursorOrientation = PDO::FETCH_ORI_NEXT, int $cursorOffset = 0): mixed 284 | { 285 | // set global fetch_style 286 | $this->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, $mode); 287 | 288 | // init return value 289 | $rs = false; 290 | 291 | // determine what oci_fetch_* to run 292 | switch ($mode) { 293 | case PDO::FETCH_CLASS: 294 | case PDO::FETCH_ASSOC: 295 | $rs = oci_fetch_assoc($this->stmt); 296 | break; 297 | case PDO::FETCH_NUM: 298 | $rs = oci_fetch_row($this->stmt); 299 | break; 300 | default: 301 | $rs = oci_fetch_array($this->stmt); 302 | break; 303 | } 304 | 305 | if (! $rs) { 306 | $this->setErrorInfo('07000'); 307 | } 308 | 309 | $this->processBindings($rs); 310 | 311 | return $this->processFetchOptions($rs); 312 | } 313 | 314 | /** 315 | * Returns an array containing all of the result set rows. 316 | * 317 | * @param int|null $mode Controls how the next row will be returned to the caller. This value must be one 318 | * of the PDO::FETCH_* constants 319 | * @param mixed ...$args Has no effect; was only included to extend parent. 320 | */ 321 | public function fetchAll(int $mode = PDO::FETCH_CLASS, mixed ...$args): array 322 | { 323 | if ($mode != PDO::FETCH_CLASS && $mode != PDO::FETCH_ASSOC) { 324 | throw new \InvalidArgumentException( 325 | "Invalid fetch style requested: {$mode}. Only PDO::FETCH_CLASS and PDO::FETCH_ASSOC suported." 326 | ); 327 | } 328 | 329 | $this->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, $mode); 330 | 331 | oci_fetch_all($this->stmt, $results, 0, -1, \OCI_FETCHSTATEMENT_BY_ROW + \OCI_ASSOC); 332 | 333 | foreach ($results as $k => $v) { 334 | $results[$k] = $this->processFetchOptions($v); 335 | } 336 | 337 | return $results; 338 | } 339 | 340 | /** 341 | * Returns a single column from the next row of a result set. 342 | * 343 | * @param int $column 0-indexed number of the column you wish to retrieve from the row. 344 | * If no value is supplied, fetchColumn fetches the first column. 345 | * @return mixed single column in the next row of a result set 346 | */ 347 | public function fetchColumn(int $column = 0): mixed 348 | { 349 | $rs = $this->fetch(PDO::FETCH_NUM); 350 | 351 | return isset($rs[$column]) ? $rs[$column] : false; 352 | } 353 | 354 | /** 355 | * Fetches the next row and returns it as an object. 356 | * 357 | * @param string $class Name of the created class 358 | * @param array $constructorArgs Elements of this array are passed to the constructor 359 | * @return bool Returns an instance of the required class with property names that correspond to the column names 360 | * or FALSE on failure. 361 | */ 362 | public function fetchObject(?string $class = 'stdClass', array $constructorArgs = []): object|false 363 | { 364 | $this->setFetchMode(PDO::FETCH_CLASS, $class, $constructorArgs); 365 | 366 | return $this->fetch(PDO::FETCH_CLASS); 367 | } 368 | 369 | /** 370 | * Retrieve a statement attribute. 371 | * 372 | * @param int $name The attribute number 373 | * @return mixed Returns the value of the attribute on success or null on failure 374 | */ 375 | public function getAttribute(int $name): mixed 376 | { 377 | return $this->attributes[$name] ?? null; 378 | } 379 | 380 | /** 381 | * Returns metadata for a column in a result set. 382 | * 383 | * @param int $column The 0-indexed column in the result set. 384 | * @return array Returns an associative array representing the metadata for a single column 385 | */ 386 | public function getColumnMeta(int $column): array|false 387 | { 388 | $column++; 389 | 390 | return [ 391 | 'native_type' => oci_field_type($this->stmt, $column), 392 | 'driver:decl_type' => oci_field_type_raw($this->stmt, $column), 393 | 'name' => oci_field_name($this->stmt, $column), 394 | 'len' => oci_field_size($this->stmt, $column), 395 | 'precision' => oci_field_precision($this->stmt, $column), 396 | ]; 397 | } 398 | 399 | /** 400 | * Advances to the next rowset in a multi-rowset statement handle. 401 | * 402 | * @return bool Returns TRUE on success or FALSE on failure 403 | */ 404 | public function nextRowset(): bool 405 | { 406 | return true; 407 | } 408 | 409 | /** 410 | * Returns the number of rows affected by the last SQL statement. 411 | * 412 | * @return int Returns the number of rows affected as an integer, or FALSE on errors. 413 | */ 414 | public function rowCount(): int 415 | { 416 | return oci_num_rows($this->stmt); 417 | } 418 | 419 | /** 420 | * Set a statement attribute. 421 | * 422 | * @param int $attribute The attribute number 423 | * @param mixed $value Value of named attribute 424 | * @return bool Returns TRUE 425 | */ 426 | public function setAttribute(int $attribute, mixed $value): bool 427 | { 428 | $this->attributes[$attribute] = $value; 429 | 430 | return true; 431 | } 432 | 433 | /** 434 | * Set the default fetch mode for this statement. 435 | * 436 | * @param int $mode The fetch mode must be one of the PDO::FETCH_* constants. 437 | * @param mixed ...$args Has no effect; was only included to extend parent. 438 | * @return bool Returns TRUE on success or FALSE on failure 439 | */ 440 | public function setFetchMode(int $mode, mixed ...$args) 441 | { 442 | return true; 443 | } 444 | 445 | /** 446 | * CUSTOM CODE FROM HERE DOWN. 447 | * 448 | * All code above this is overriding the PDO base code 449 | * All code below this are custom helpers or other functionality provided by the oci_* functions 450 | */ 451 | 452 | /** 453 | * Stores query parameters for debugDumpParams output. 454 | */ 455 | private function addParameter($parameter, $variable, $data_type = PDO::PARAM_STR, $length = -1, $driver_options = null) 456 | { 457 | $param_count = count($this->parameters); 458 | 459 | $this->parameters[$param_count] = [ 460 | 'paramno' => $param_count, 461 | 'name' => $parameter, 462 | 'value' => $variable, 463 | 'is_param' => 1, 464 | 'param_type' => $data_type, 465 | ]; 466 | } 467 | 468 | /** 469 | * Returns the oci8 statement handle for use with other oci_ functions. 470 | * 471 | * @return oci8 statment The oci8 statment handle 472 | */ 473 | public function getOCIResource() 474 | { 475 | return $this->stmt; 476 | } 477 | 478 | /** 479 | * Single location to process all the bindings on a resultset. 480 | * 481 | * @param array $rs The fetched array to be modified 482 | */ 483 | private function processBindings($rs) 484 | { 485 | if ($rs !== false && ! empty($this->bindings)) { 486 | $i = 1; 487 | foreach ($rs as $col => $value) { 488 | if (isset($this->bindings[$i])) { 489 | $this->bindings[$i]['var'] = $value; 490 | } 491 | $i++; 492 | } 493 | } 494 | } 495 | 496 | /** 497 | * Single location to process all the fetch options on a resultset. 498 | * 499 | * @param array $rec The fetched array to be modified 500 | * @return mixed The modified resultset 501 | */ 502 | private function processFetchOptions($rec) 503 | { 504 | if ($rec !== false) { 505 | if ($this->conn->getAttribute(PDO::ATTR_CASE) == PDO::CASE_LOWER) { 506 | $rec = array_change_key_case($rec, \CASE_LOWER); 507 | } 508 | 509 | $rec = ($this->getAttribute(PDO::ATTR_DEFAULT_FETCH_MODE) != PDO::FETCH_CLASS) ? $rec : (object) $rec; 510 | } 511 | 512 | return $rec; 513 | } 514 | 515 | /** 516 | * Single location to process all errors and set necessary fields. 517 | * 518 | * @param string $code The SQLSTATE error code. defualts to custom 'JF000' 519 | * @param string $error The driver based error code. If null, oci_error is called 520 | * @param string $message The error message 521 | * @return array The local error array 522 | */ 523 | private function setErrorInfo($code = null, $error = null, $message = null) 524 | { 525 | if (is_null($code)) { 526 | $code = 'JF000'; 527 | } 528 | 529 | if (is_null($error)) { 530 | $e = oci_error($this->stmt); 531 | $error = $e['code']; 532 | $message = $e['message'].(empty($e['sqltext']) ? '' : ' - SQL: '.$e['sqltext']); 533 | } 534 | 535 | $this->error[0] = $code; 536 | $this->error[1] = $error; 537 | $this->error[2] = $message; 538 | 539 | return $this->error; 540 | } 541 | } 542 | -------------------------------------------------------------------------------- /src/Jfelder/OracleDB/OracleConnection.php: -------------------------------------------------------------------------------- 1 | schemaGrammar)) { 40 | $this->useDefaultSchemaGrammar(); 41 | } 42 | 43 | return new OracleSchemaBuilder($this); 44 | } 45 | 46 | /** 47 | * Get a new query builder instance. 48 | * 49 | * @return \Jfelder\OracleDB\Query\OracleBuilder 50 | */ 51 | public function query() 52 | { 53 | return new OracleQueryBuilder( 54 | $this, $this->getQueryGrammar(), $this->getPostProcessor() 55 | ); 56 | } 57 | 58 | /** 59 | * Get the default query grammar instance. 60 | * 61 | * @return \Jfelder\OracleDB\Query\Grammars\OracleGrammar 62 | */ 63 | protected function getDefaultQueryGrammar() 64 | { 65 | ($grammar = new QueryGrammar)->setConnection($this); 66 | 67 | return $this->withTablePrefix($grammar); 68 | } 69 | 70 | /** 71 | * Get the default schema grammar instance. 72 | * 73 | * @return \Jfelder\OracleDB\Schema\Grammars\OracleGrammar|null 74 | */ 75 | protected function getDefaultSchemaGrammar() 76 | { 77 | ($grammar = new SchemaGrammar)->setConnection($this); 78 | 79 | return $this->withTablePrefix($grammar); 80 | } 81 | 82 | /** 83 | * Get the default post processor instance. 84 | * 85 | * @return \Jfelder\OracleDB\Query\Processors\OracleProcessor 86 | */ 87 | protected function getDefaultPostProcessor() 88 | { 89 | return new OracleProcessor; 90 | } 91 | 92 | /** 93 | * Bind values to their parameters in the given statement. 94 | * 95 | * @param \PDOStatement $statement 96 | * @param array $bindings 97 | * @return void 98 | */ 99 | public function bindValues($statement, $bindings) 100 | { 101 | foreach ($bindings as $key => $value) { 102 | $statement->bindValue( 103 | $key, 104 | $value, 105 | match (true) { 106 | is_int($value) => PDO::PARAM_INT, 107 | is_bool($value) => PDO::PARAM_BOOL, 108 | is_null($value) => PDO::PARAM_NULL, 109 | is_resource($value) => PDO::PARAM_LOB, 110 | default => PDO::PARAM_STR 111 | }, 112 | ); 113 | } 114 | } 115 | 116 | /** 117 | * Run an "insert get ID" statement against an oracle database. 118 | * 119 | * @param string $query 120 | * @param array $bindings 121 | * @return int 122 | */ 123 | public function oracleInsertGetId($query, $bindings = []) 124 | { 125 | return $this->run($query, $bindings, function ($query, $bindings) { 126 | $last_insert_id = 0; 127 | 128 | $statement = $this->getPdo()->prepare($query); 129 | 130 | $this->bindValues($statement, $this->prepareBindings($bindings)); 131 | 132 | // bind final param to a var to capture the id obtained by the query's "returning id into" clause 133 | $statement->bindParam(count($bindings), $last_insert_id, PDO::PARAM_INT | PDO::PARAM_INPUT_OUTPUT, 8); 134 | 135 | $this->recordsHaveBeenModified(); 136 | 137 | $statement->execute(); 138 | 139 | return (int) $last_insert_id; 140 | }); 141 | } 142 | 143 | /** 144 | * Determine if the given database exception was caused by a unique constraint violation. 145 | * 146 | * @return bool 147 | */ 148 | protected function isUniqueConstraintError(Exception $exception) 149 | { 150 | return boolval(preg_match('#ORA-00001: unique constraint#i', $exception->getMessage())); 151 | } 152 | 153 | /** 154 | * Get the schema state for the connection. 155 | * 156 | * @throws \RuntimeException 157 | */ 158 | public function getSchemaState() 159 | { 160 | throw new RuntimeException('Schema dumping is not supported when using Oracle.'); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/Jfelder/OracleDB/OracleDBServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 18 | __DIR__.'/../../config/oracledb.php' => config_path('oracledb.php'), 19 | ], 'oracledb-config'); 20 | } 21 | 22 | /** 23 | * Register the service provider. 24 | * 25 | * @returns Jfelder\OrcaleDB\OracleConnection 26 | */ 27 | public function register() 28 | { 29 | // merge default config 30 | $this->mergeConfigFrom(__DIR__.'/../../config/oracledb.php', 'database.connections'); 31 | 32 | // load default configs 33 | $config = [ 34 | 'oracle' => config('database.connections.oracle'), 35 | ]; 36 | 37 | // override any default configs with user config and load those configs 38 | if (file_exists(config_path('oracledb.php'))) { 39 | $this->mergeConfigFrom(config_path('oracledb.php'), 'database.connections'); 40 | 41 | $config = $this->app['config']->get('oracledb'); 42 | } 43 | 44 | $connection_keys = array_keys($config); 45 | 46 | // loop thru oracle configs to extend DB 47 | if (is_array($connection_keys)) { 48 | foreach ($connection_keys as $key) { 49 | $this->app['db']->extend($key, function ($config) { 50 | $oConnector = new Connectors\OracleConnector; 51 | 52 | $connection = $oConnector->connect($config); 53 | 54 | return new OracleConnection($connection, $config['database'], $config['prefix'], $config); 55 | }); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Jfelder/OracleDB/Query/Grammars/OracleGrammar.php: -------------------------------------------------------------------------------- 1 | 'YYYY-MM-DD', 22 | 'day' => 'DD', 23 | 'month' => 'MM', 24 | 'year' => 'YYYY', 25 | 'time' => 'HH24:MI:SS', 26 | ]; 27 | $value = $this->parameter($where['value']); 28 | 29 | return 'TO_CHAR('.$this->wrap($where['column']).', \''.$date_parts[$type].'\') '.$where['operator'].' '.$value; 30 | } 31 | 32 | /** 33 | * Compile a select query into SQL. 34 | * 35 | * @param \Illuminate\Database\Query\Builder 36 | * @return string 37 | */ 38 | public function compileSelect(Builder $query) 39 | { 40 | if (($query->unions || $query->havings) && $query->aggregate) { 41 | return $this->compileUnionAggregate($query); 42 | } 43 | 44 | // If the query does not have any columns set, we'll set the columns to the 45 | // * character to just get all of the columns from the database. Then we 46 | // can build the query and concatenate all the pieces together as one. 47 | $original = $query->columns; 48 | 49 | if (is_null($query->columns)) { 50 | $query->columns = ['*']; 51 | } 52 | 53 | // To compile the query, we'll spin through each component of the query and 54 | // see if that component exists. If it does we'll just call the compiler 55 | // function for the component which is responsible for making the SQL. 56 | $components = $this->compileComponents($query); 57 | $sql = trim($this->concatenate($components)); 58 | 59 | if ($query->unions) { 60 | $sql = $this->wrapUnion($sql).' '.$this->compileUnions($query); 61 | } 62 | 63 | if (isset($query->limit) || isset($query->offset)) { 64 | $sql = $this->compileAnsiOffset($query, $components); 65 | } 66 | 67 | $query->columns = $original; 68 | 69 | return $sql; 70 | } 71 | 72 | /** 73 | * Compile a "where like" clause. 74 | * 75 | * @param array $where 76 | * @return string 77 | */ 78 | protected function whereLike(Builder $query, $where) 79 | { 80 | if (! $where['caseSensitive']) { 81 | throw new RuntimeException('This database engine does not support case insensitive like operations. The sql "UPPER(some_column) like ?" can accomplish insensitivity.'); 82 | } 83 | 84 | $where['operator'] = $where['not'] ? 'not like' : 'like'; 85 | 86 | return $this->whereBasic($query, $where); 87 | } 88 | 89 | /** 90 | * Compile an insert statement into SQL. 91 | * 92 | * @return string 93 | */ 94 | public function compileInsert(Builder $query, array $values) 95 | { 96 | // Essentially we will force every insert to be treated as a batch insert which 97 | // simply makes creating the SQL easier for us since we can utilize the same 98 | // basic routine regardless of an amount of records given to us to insert. 99 | $table = $this->wrapTable($query->from); 100 | 101 | if (! is_array(reset($values))) { 102 | $values = [$values]; 103 | } 104 | 105 | // If there is only one record being inserted, we will just use the usual query 106 | // grammar insert builder because no special syntax is needed for the single 107 | // row inserts in Oracle. However, if there are multiples, we'll continue. 108 | $count = count($values); 109 | 110 | if ($count == 1) { 111 | return parent::compileInsert($query, reset($values)); 112 | } 113 | 114 | $columns = $this->columnize(array_keys(reset($values))); 115 | 116 | $rows = []; 117 | 118 | // Oracle requires us to build the multi-row insert as multiple inserts with 119 | // a select statement at the end. So we'll build out this list of columns 120 | // and then join them all together with select to complete the queries. 121 | $parameters = $this->parameterize(reset($values)); 122 | 123 | for ($i = 0; $i < $count; $i++) { 124 | $rows[] = "into {$table} ({$columns}) values ({$parameters}) "; 125 | } 126 | 127 | return 'insert all '.implode($rows).' select 1 from dual'; 128 | } 129 | 130 | /** 131 | * Compile an insert and get ID statement into SQL. 132 | * 133 | * @param array $values 134 | * @param string $sequence 135 | * @return string 136 | */ 137 | public function compileInsertGetId(Builder $query, $values, $sequence) 138 | { 139 | if (empty($values)) { 140 | throw new RuntimeException('This database engine does not support calling the insertGetId method with empty values.'); 141 | } 142 | 143 | if (is_null($sequence)) { 144 | $sequence = 'id'; 145 | } 146 | 147 | return $this->compileInsert($query, $values).' returning '.$this->wrap($sequence).' into ?'; 148 | } 149 | 150 | /** 151 | * Compile the "union" queries attached to the main query. 152 | * 153 | * @return string 154 | */ 155 | protected function compileUnions(Builder $query) 156 | { 157 | $sql = ''; 158 | 159 | foreach ($query->unions as $union) { 160 | $sql .= $this->compileUnion($union); 161 | } 162 | 163 | if (isset($query->unionOrders)) { 164 | $sql .= ' '.$this->compileOrders($query, $query->unionOrders); 165 | } 166 | 167 | if (isset($query->unionLimit)) { 168 | $query->limit = $query->unionLimit; 169 | } 170 | 171 | if (isset($query->unionOffset)) { 172 | $query->offset = $query->unionOffset; 173 | } 174 | 175 | return ltrim($sql); 176 | } 177 | 178 | /** 179 | * Compile a delete statement into SQL. 180 | * 181 | * @return string 182 | */ 183 | public function compileDelete(Builder $query) 184 | { 185 | if (isset($query->joins) || isset($query->limit)) { 186 | return $this->compileDeleteWithJoinsOrLimit($query); 187 | } 188 | 189 | return parent::compileDelete($query); 190 | } 191 | 192 | /** 193 | * Compile a delete statement with joins or limit into SQL. 194 | * 195 | * @return string 196 | */ 197 | protected function compileDeleteWithJoinsOrLimit(Builder $query) 198 | { 199 | throw new RuntimeException('This database engine does not support delete statements that contain joins or limits.'); 200 | } 201 | 202 | /** 203 | * Compile an exists statement into SQL. 204 | * 205 | * @return string 206 | */ 207 | public function compileExists(Builder $query) 208 | { 209 | $select = $this->compileSelect($query); 210 | 211 | return "select t2.\"rn\" as {$this->wrap('exists')} from ( select rownum AS \"rn\", t1.* from ({$select}) t1 ) t2 where t2.\"rn\" between 1 and 1"; 212 | } 213 | 214 | /** 215 | * Compile the lock into SQL. 216 | * 217 | * @param bool|string $value 218 | * @return string 219 | */ 220 | protected function compileLock(Builder $query, $value) 221 | { 222 | if (is_string($value)) { 223 | return $value; 224 | } 225 | 226 | return $value ? 'for update' : 'lock in share mode'; 227 | } 228 | 229 | /** 230 | * Create a full ANSI offset clause for the query. 231 | * 232 | * @param array $components 233 | * @return string 234 | */ 235 | protected function compileAnsiOffset(Builder $query, $components) 236 | { 237 | $constraint = $this->compileRowConstraint($query); 238 | 239 | $sql = $this->concatenate($components); 240 | 241 | if ($query->unions) { 242 | $sql = $this->wrapUnion($sql).' '.$this->compileUnions($query); 243 | } 244 | 245 | // We are now ready to build the final SQL query so we'll create a common table 246 | // expression from the query and get the records with row numbers within our 247 | // given limit and offset value that we just put on as a query constraint. 248 | $temp = $this->compileTableExpression($sql, $constraint, $query); 249 | 250 | return $temp; 251 | } 252 | 253 | /** 254 | * Compile the limit / offset row constraint for a query. 255 | * 256 | * @param \Illuminate\Database\Query\Builder $query 257 | * @return string 258 | */ 259 | protected function compileRowConstraint($query) 260 | { 261 | if (isset($query->limit) && $query->limit < 1) { 262 | return '< 1'; 263 | } 264 | 265 | $start = $query->offset + 1; 266 | 267 | if ($query->limit > 0) { 268 | $finish = $query->offset + $query->limit; 269 | 270 | return "between {$start} and {$finish}"; 271 | } 272 | 273 | return ">= {$start}"; 274 | } 275 | 276 | /** 277 | * Compile a common table expression for a query. 278 | * 279 | * @param string $sql 280 | * @param string $constraint 281 | * @return string 282 | */ 283 | protected function compileTableExpression($sql, $constraint, $query) 284 | { 285 | if ($query->limit > 0) { 286 | return "select t2.* from ( select rownum AS \"rn\", t1.* from ({$sql}) t1 ) t2 where t2.\"rn\" {$constraint}"; 287 | } else { 288 | return "select * from ({$sql}) where rownum {$constraint}"; 289 | } 290 | } 291 | 292 | /** 293 | * Compile the "limit" portions of the query. 294 | * 295 | * @param int $limit 296 | * @return string 297 | */ 298 | protected function compileLimit(Builder $query, $limit) 299 | { 300 | return ''; 301 | } 302 | 303 | /** 304 | * Compile a group limit clause. 305 | * 306 | * @return string 307 | */ 308 | protected function compileGroupLimit(Builder $query) 309 | { 310 | throw new RuntimeException('This database engine does not support group limit operations.'); 311 | } 312 | 313 | /** 314 | * Compile a row number clause. 315 | * 316 | * @param string $partition 317 | * @param string $orders 318 | * @return string 319 | */ 320 | protected function compileRowNumber($partition, $orders) 321 | { 322 | throw new RuntimeException('This database engine does not support row number operations.'); 323 | } 324 | 325 | /** 326 | * Compile the "offset" portions of the query. 327 | * 328 | * @param int $offset 329 | * @return string 330 | */ 331 | protected function compileOffset(Builder $query, $offset) 332 | { 333 | return ''; 334 | } 335 | 336 | /** 337 | * Compile a query to get the number of open connections for a database. 338 | * 339 | * @throws RuntimeException 340 | */ 341 | public function compileThreadCount() 342 | { 343 | throw new RuntimeException('This database engine does not support getting the number of open connections.'); 344 | } 345 | 346 | /** 347 | * Get the format for database stored dates. 348 | * 349 | * @return string 350 | */ 351 | public function getDateFormat() 352 | { 353 | return $this->connection->getConfig('date_format') ?: 'Y-m-d H:i:s'; 354 | } 355 | 356 | /** 357 | * Wrap a single string in keyword identifiers. 358 | * 359 | * @param string $value 360 | * @return string 361 | */ 362 | protected function wrapValue($value) 363 | { 364 | if ($this->connection->getConfig('quoting') === true) { 365 | return parent::wrapValue($value); 366 | } 367 | 368 | return $value; 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /src/Jfelder/OracleDB/Query/OracleBuilder.php: -------------------------------------------------------------------------------- 1 | createSub($query); 20 | 21 | $expression = '('.$query.') '.$this->grammar->wrapTable($as); 22 | 23 | $this->addBinding($bindings, 'join'); 24 | 25 | $this->joins[] = $this->newJoinClause($this, 'cross', new Expression($expression)); 26 | 27 | return $this; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Jfelder/OracleDB/Query/Processors/OracleProcessor.php: -------------------------------------------------------------------------------- 1 | getConnection()->oracleInsertGetId($sql, $values); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Jfelder/OracleDB/Schema/Grammars/OracleGrammar.php: -------------------------------------------------------------------------------- 1 | getCommandByName($blueprint, 'primary'); 61 | 62 | if (! is_null($primary)) { 63 | $columns = $this->columnize($primary->columns); 64 | 65 | return ", constraint {$primary->index} primary key ( {$columns} )"; 66 | } 67 | } 68 | 69 | /** 70 | * Get the foreign key syntax for a table creation statement. 71 | * 72 | * @return string|null 73 | */ 74 | protected function addForeignKeys(Blueprint $blueprint) 75 | { 76 | $sql = ''; 77 | 78 | $foreigns = $this->getCommandsByName($blueprint, 'foreign'); 79 | 80 | // Once we have all the foreign key commands for the table creation statement 81 | // we'll loop through each of them and add them to the create table SQL we 82 | // are building 83 | foreach ($foreigns as $foreign) { 84 | $on = $this->wrapTable($foreign->on); 85 | 86 | $columns = $this->columnize($foreign->columns); 87 | 88 | $onColumns = $this->columnize((array) $foreign->references); 89 | 90 | $sql .= ", constraint {$foreign->index} foreign key ( {$columns} ) references {$on} ( {$onColumns} )"; 91 | 92 | // Once we have the basic foreign key creation statement constructed we can 93 | // build out the syntax for what should happen on an update or delete of 94 | // the affected columns, which will get something like "cascade", etc. 95 | if (! is_null($foreign->onDelete)) { 96 | $sql .= " on delete {$foreign->onDelete}"; 97 | } 98 | 99 | if (! is_null($foreign->onUpdate)) { 100 | $sql .= " on update {$foreign->onUpdate}"; 101 | } 102 | } 103 | 104 | return $sql; 105 | } 106 | 107 | /** 108 | * Compile a create table command. 109 | * 110 | * @param Illuminate\Database\Schema\Blueprint $blueprint 111 | * @param Illuminate\Support\Fluent $command 112 | * @return string 113 | */ 114 | public function compileCreate(Blueprint $blueprint, Fluent $command) 115 | { 116 | $columns = implode(', ', $this->getColumns($blueprint)); 117 | 118 | $sql = 'create table '.$this->wrapTable($blueprint)." ( $columns"; 119 | 120 | // To be able to name the primary/foreign keys when the table is 121 | // initially created we will need to check for a primary/foreign 122 | // key commands and add the columns to the table's declaration 123 | // here so they can be created on the tables. 124 | 125 | $sql .= (string) $this->addForeignKeys($blueprint); 126 | 127 | $sql .= (string) $this->addPrimaryKeys($blueprint); 128 | 129 | return $sql .= ' )'; 130 | } 131 | 132 | /** 133 | * Compile a column addition table command. 134 | * 135 | * @param Illuminate\Database\Schema\Blueprint $blueprint 136 | * @param Illuminate\Support\Fluent $command 137 | * @return string 138 | */ 139 | public function compileAdd(Blueprint $blueprint, Fluent $command) 140 | { 141 | $column = $this->getColumn($blueprint, $command->column); 142 | 143 | $sql = 'alter table '.$this->wrapTable($blueprint)." add ( $column"; 144 | 145 | $primary = $this->getCommandByName($blueprint, 'primary'); 146 | 147 | if (! is_null($primary) && in_array($command->column->name, $primary->columns)) { 148 | $sql .= ", constraint {$primary->index} primary key ( {$command->column->name} )"; 149 | } 150 | 151 | return $sql .= ' )'; 152 | } 153 | 154 | /** 155 | * Compile a primary key command. 156 | * 157 | * @param Illuminate\Database\Schema\Blueprint $blueprint 158 | * @param Illuminate\Support\Fluent $command 159 | * @return string 160 | */ 161 | public function compilePrimary(Blueprint $blueprint, Fluent $command) 162 | { 163 | $create = $this->getCommandByName($blueprint, 'create'); 164 | 165 | if (is_null($create)) { 166 | $columns = $this->columnize($command->columns); 167 | 168 | $table = $this->wrapTable($blueprint); 169 | 170 | return "alter table {$table} add constraint {$command->index} primary key ({$columns})"; 171 | } 172 | } 173 | 174 | /** 175 | * Compile a foreign key command. 176 | * 177 | * @return string 178 | */ 179 | public function compileForeign(Blueprint $blueprint, Fluent $command) 180 | { 181 | $create = $this->getCommandByName($blueprint, 'create'); 182 | 183 | if (is_null($create)) { 184 | $table = $this->wrapTable($blueprint); 185 | 186 | $on = $this->wrapTable($command->on); 187 | 188 | // We need to prepare several of the elements of the foreign key definition 189 | // before we can create the SQL, such as wrapping the tables and convert 190 | // an array of columns to comma-delimited strings for the SQL queries. 191 | $columns = $this->columnize($command->columns); 192 | 193 | $onColumns = $this->columnize((array) $command->references); 194 | 195 | $sql = "alter table {$table} add constraint {$command->index} "; 196 | 197 | $sql .= "foreign key ( {$columns} ) references {$on} ( {$onColumns} )"; 198 | 199 | // Once we have the basic foreign key creation statement constructed we can 200 | // build out the syntax for what should happen on an update or delete of 201 | // the affected columns, which will get something like "cascade", etc. 202 | if (! is_null($command->onDelete)) { 203 | $sql .= " on delete {$command->onDelete}"; 204 | } 205 | 206 | if (! is_null($command->onUpdate)) { 207 | $sql .= " on update {$command->onUpdate}"; 208 | } 209 | 210 | return $sql; 211 | } 212 | } 213 | 214 | /** 215 | * Compile a unique key command. 216 | * 217 | * @param Illuminate\Database\Schema\Blueprint $blueprint 218 | * @param Illuminate\Support\Fluent $command 219 | * @return string 220 | */ 221 | public function compileUnique(Blueprint $blueprint, Fluent $command) 222 | { 223 | $columns = $this->columnize($command->columns); 224 | 225 | $table = $this->wrapTable($blueprint); 226 | 227 | return "alter table {$table} add constraint {$command->index} unique ( {$columns} )"; 228 | } 229 | 230 | /** 231 | * Compile a plain index key command. 232 | * 233 | * @param Illuminate\Database\Schema\Blueprint $blueprint 234 | * @param Illuminate\Support\Fluent $command 235 | * @return string 236 | */ 237 | public function compileIndex(Blueprint $blueprint, Fluent $command) 238 | { 239 | $columns = $this->columnize($command->columns); 240 | 241 | $table = $this->wrapTable($blueprint); 242 | 243 | return "create index {$command->index} on {$table} ( {$columns} )"; 244 | } 245 | 246 | /** 247 | * Compile a drop table command. 248 | * 249 | * @param Illuminate\Database\Schema\Blueprint $blueprint 250 | * @param Illuminate\Support\Fluent $command 251 | * @return string 252 | */ 253 | public function compileDrop(Blueprint $blueprint, Fluent $command) 254 | { 255 | return 'drop table '.$this->wrapTable($blueprint); 256 | } 257 | 258 | /** 259 | * Compile a drop column command. 260 | * 261 | * @param Illuminate\Database\Schema\Blueprint $blueprint 262 | * @param Illuminate\Support\Fluent $command 263 | * @return string 264 | */ 265 | public function compileDropColumn(Blueprint $blueprint, Fluent $command) 266 | { 267 | $columns = $this->wrapArray($command->columns); 268 | 269 | $table = $this->wrapTable($blueprint); 270 | 271 | return 'alter table '.$table.' drop ( '.implode(', ', $columns).' )'; 272 | } 273 | 274 | /** 275 | * Compile a drop primary key command. 276 | * 277 | * @param Illuminate\Database\Schema\Blueprint $blueprint 278 | * @param Illuminate\Support\Fluent $command 279 | * @return string 280 | */ 281 | public function compileDropPrimary(Blueprint $blueprint, Fluent $command) 282 | { 283 | $table = $this->wrapTable($blueprint); 284 | 285 | return "alter table {$table} drop constraint {$command->index}"; 286 | } 287 | 288 | /** 289 | * Compile a drop unique key command. 290 | * 291 | * @param Illuminate\Database\Schema\Blueprint $blueprint 292 | * @param Illuminate\Support\Fluent $command 293 | * @return string 294 | */ 295 | public function compileDropUnique(Blueprint $blueprint, Fluent $command) 296 | { 297 | $table = $this->wrapTable($blueprint); 298 | 299 | return "alter table {$table} drop constraint {$command->index}"; 300 | } 301 | 302 | /** 303 | * Compile a drop index command. 304 | * 305 | * @param Illuminate\Database\Schema\Blueprint $blueprint 306 | * @param Illuminate\Support\Fluent $command 307 | * @return string 308 | */ 309 | public function compileDropIndex(Blueprint $blueprint, Fluent $command) 310 | { 311 | return "drop index {$command->index}"; 312 | } 313 | 314 | /** 315 | * Compile a drop foreign key command. 316 | * 317 | * @param Illuminate\Database\Schema\Blueprint $blueprint 318 | * @param Illuminate\Support\Fluent $command 319 | * @return string 320 | */ 321 | public function compileDropForeign(Blueprint $blueprint, Fluent $command) 322 | { 323 | $table = $this->wrapTable($blueprint); 324 | 325 | return "alter table {$table} drop constraint {$command->index}"; 326 | } 327 | 328 | /** 329 | * Compile a rename table command. 330 | * 331 | * @param Illuminate\Database\Schema\Blueprint $blueprint 332 | * @param Illuminate\Support\Fluent $command 333 | * @return string 334 | */ 335 | public function compileRename(Blueprint $blueprint, Fluent $command) 336 | { 337 | $table = $this->wrapTable($blueprint); 338 | 339 | return "alter table {$table} rename to ".$this->wrapTable($command->to); 340 | } 341 | 342 | /** 343 | * Compile a rename column command. 344 | * 345 | * @return array 346 | */ 347 | public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection) 348 | { 349 | $table = $this->wrapTable($blueprint); 350 | 351 | $rs = ['alter table '.$table.' rename column '.$command->from.' to '.$command->to]; 352 | 353 | return $rs; 354 | } 355 | 356 | /** 357 | * Wrap a single string in keyword identifiers. 358 | * 359 | * @param string $value 360 | * @return string 361 | */ 362 | protected function wrapValue($value) 363 | { 364 | if ($this->connection->getConfig('quoting') === true) { 365 | return parent::wrapValue($value); 366 | } 367 | 368 | return $value; 369 | } 370 | 371 | /** 372 | * Create the column definition for a string type. 373 | * 374 | * @param Illuminate\Support\Fluent $column 375 | * @return string 376 | */ 377 | protected function typeString(Fluent $column) 378 | { 379 | return "varchar2({$column->length})"; 380 | } 381 | 382 | /** 383 | * Create the column definition for a long text type. 384 | * 385 | * @return string 386 | */ 387 | protected function typeLongText(Fluent $column) 388 | { 389 | return 'clob'; 390 | } 391 | 392 | /** 393 | * Create the column definition for a medium text type. 394 | * 395 | * @return string 396 | */ 397 | protected function typeMediumText(Fluent $column) 398 | { 399 | return 'clob'; 400 | } 401 | 402 | /** 403 | * Create the column definition for a text type. 404 | * 405 | * @param Illuminate\Support\Fluent $column 406 | * @return string 407 | */ 408 | protected function typeText(Fluent $column) 409 | { 410 | return 'varchar2(4000)'; 411 | } 412 | 413 | /** 414 | * Create the column definition for a integer type. 415 | * 416 | * @param Illuminate\Support\Fluent $column 417 | * @return string 418 | */ 419 | protected function typeBigInteger(Fluent $column) 420 | { 421 | return 'number(19,0)'; 422 | } 423 | 424 | /** 425 | * Create the column definition for a integer type. 426 | * 427 | * @param Illuminate\Support\Fluent $column 428 | * @return string 429 | */ 430 | protected function typeInteger(Fluent $column) 431 | { 432 | return 'number(10,0)'; 433 | } 434 | 435 | /** 436 | * Create the column definition for a medium integer type. 437 | * 438 | * @param Illuminate\Support\Fluent $column 439 | * @return string 440 | */ 441 | protected function typeMediumInteger(Fluent $column) 442 | { 443 | return 'number(7,0)'; 444 | } 445 | 446 | /** 447 | * Create the column definition for a small integer type. 448 | * 449 | * @param Illuminate\Support\Fluent $column 450 | * @return string 451 | */ 452 | protected function typeSmallInteger(Fluent $column) 453 | { 454 | return 'number(5,0)'; 455 | } 456 | 457 | /** 458 | * Create the column definition for a tiny integer type. 459 | * 460 | * @return string 461 | */ 462 | protected function typeTinyInteger(Fluent $column) 463 | { 464 | return 'number(3,0)'; 465 | } 466 | 467 | /** 468 | * Create the column definition for a float type. 469 | * 470 | * @param Illuminate\Support\Fluent $column 471 | * @return string 472 | */ 473 | protected function typeFloat(Fluent $column) 474 | { 475 | if ($column->precision) { 476 | return "float({$column->precision})"; 477 | } 478 | 479 | return 'float'; 480 | } 481 | 482 | /** 483 | * Create the column definition for a double type. 484 | * 485 | * @return string 486 | */ 487 | protected function typeDouble(Fluent $column) 488 | { 489 | return 'double precision'; 490 | } 491 | 492 | /** 493 | * Create the column definition for a decimal type. 494 | * 495 | * @param Illuminate\Support\Fluent $column 496 | * @return string 497 | */ 498 | protected function typeDecimal(Fluent $column) 499 | { 500 | return "number({$column->total}, {$column->places})"; 501 | } 502 | 503 | /** 504 | * Create the column definition for a boolean type. 505 | * 506 | * @param Illuminate\Support\Fluent $column 507 | * @return string 508 | */ 509 | protected function typeBoolean(Fluent $column) 510 | { 511 | return 'char(1)'; 512 | } 513 | 514 | /** 515 | * Create the column definition for a enum type. 516 | * 517 | * @param Illuminate\Support\Fluent $column 518 | * @return string 519 | */ 520 | protected function typeEnum(Fluent $column) 521 | { 522 | return sprintf( 523 | 'varchar2(255) check(%s in (%s))', 524 | $column->name, 525 | $this->quoteString($column->allowed) 526 | ); 527 | } 528 | 529 | /** 530 | * Create the column definition for a date type. 531 | * 532 | * @param Illuminate\Support\Fluent $column 533 | * @return string 534 | */ 535 | protected function typeDate(Fluent $column) 536 | { 537 | return 'date'; 538 | } 539 | 540 | /** 541 | * Create the column definition for a date-time type. 542 | * 543 | * @param Illuminate\Support\Fluent $column 544 | * @return string 545 | */ 546 | protected function typeDateTime(Fluent $column) 547 | { 548 | return 'date'; 549 | } 550 | 551 | /** 552 | * Create the column definition for a time type. 553 | * 554 | * @param Illuminate\Support\Fluent $column 555 | * @return string 556 | */ 557 | protected function typeTime(Fluent $column) 558 | { 559 | return 'date'; 560 | } 561 | 562 | /** 563 | * Create the column definition for a timestamp type. 564 | * 565 | * @param Illuminate\Support\Fluent $column 566 | * @return string 567 | */ 568 | protected function typeTimestamp(Fluent $column) 569 | { 570 | return 'timestamp'; 571 | } 572 | 573 | /** 574 | * Create the column definition for a binary type. 575 | * 576 | * @param Illuminate\Support\Fluent $column 577 | * @return string 578 | */ 579 | protected function typeBinary(Fluent $column) 580 | { 581 | return 'blob'; 582 | } 583 | 584 | /** 585 | * Get the SQL for a nullable column modifier. 586 | * 587 | * @param Illuminate\Database\Schema\Blueprint $blueprint 588 | * @param Illuminate\Support\Fluent $column 589 | * @return string|null 590 | */ 591 | protected function modifyNullable(Blueprint $blueprint, Fluent $column) 592 | { 593 | $null = $column->nullable ? ' null' : ' not null'; 594 | if (! is_null($column->default)) { 595 | return ' default '.$this->getDefaultValue($column->default).$null; 596 | } 597 | 598 | return $null; 599 | } 600 | 601 | /** 602 | * Get the SQL for a default column modifier. 603 | * 604 | * @param Illuminate\Database\Schema\Blueprint $blueprint 605 | * @param Illuminate\Support\Fluent $column 606 | * @return string|null 607 | */ 608 | protected function modifyDefault(Blueprint $blueprint, Fluent $column) 609 | { 610 | return ''; 611 | } 612 | 613 | /** 614 | * Get the SQL for an auto-increment column modifier. 615 | * 616 | * @param Illuminate\Database\Schema\Blueprint $blueprint 617 | * @param Illuminate\Support\Fluent $column 618 | * @return string|null 619 | */ 620 | protected function modifyIncrement(Blueprint $blueprint, Fluent $column) 621 | { 622 | if (in_array($column->type, $this->serials) && $column->autoIncrement) { 623 | $blueprint->primary($column->name); 624 | } 625 | } 626 | } 627 | -------------------------------------------------------------------------------- /src/Jfelder/OracleDB/Schema/OracleBuilder.php: -------------------------------------------------------------------------------- 1 | grammar->compileTableExists(); 18 | 19 | $database = $this->connection->getDatabaseName(); 20 | 21 | $table = $this->connection->getTablePrefix().$table; 22 | 23 | return count($this->connection->select($sql, [$database, $table])) > 0; 24 | } 25 | 26 | /** 27 | * Get the column listing for a given table. 28 | * 29 | * @param string $table 30 | * @return array 31 | */ 32 | public function getColumnListing($table) 33 | { 34 | throw new RuntimeException('This database engine does not support column listing operations. Eloquent models must set $guarded to [] or not define it at all.'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/config/oracledb.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'driver' => 'oci8', 6 | 'tns' => env('DB_TNS', ''), 7 | 'host' => env('DB_HOST', ''), 8 | 'port' => env('DB_PORT', '1521'), 9 | 'database' => env('DB_DATABASE', ''), 10 | 'username' => env('DB_USERNAME', ''), 11 | 'password' => env('DB_PASSWORD', ''), 12 | 'charset' => env('DB_CHARSET', 'WE8ISO8859P1'), 13 | 'prefix' => '', 14 | 'quoting' => false, 15 | 'date_format' => env('DB_DATE_FORMAT', 'Y-m-d H:i:s'), 16 | ], 17 | ]; 18 | -------------------------------------------------------------------------------- /tests/OracleDBConnectionTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('The oci8 extension is not available.'); 18 | } 19 | } 20 | 21 | protected function tearDown(): void 22 | { 23 | m::close(); 24 | } 25 | 26 | public function testOracleInsertGetIdProperlyCallsPDO() 27 | { 28 | $pdo = $this->getMockBuilder(OracleDBConnectionTestMockPDO::class)->onlyMethods(['prepare'])->getMock(); 29 | $statement = $this->getMockBuilder(OracleDBConnectionTestMockOCIStatement::class)->onlyMethods(['execute', 'bindValue', 'bindParam'])->getMock(); 30 | $statement->expects($this->once())->method('bindValue')->with(0, 'bar', 2); 31 | $statement->expects($this->once())->method('bindParam')->with(1, 0, PDO::PARAM_INT | PDO::PARAM_INPUT_OUTPUT, 8); 32 | $statement->expects($this->once())->method('execute'); 33 | $pdo->expects($this->once())->method('prepare')->with($this->equalTo('foo'))->willReturn($statement); 34 | $mock = $this->getMockConnection(['prepareBindings'], $pdo); 35 | $mock->expects($this->once())->method('prepareBindings')->with($this->equalTo(['bar']))->willReturn(['bar']); 36 | $results = $mock->oracleInsertGetId('foo', ['bar']); 37 | $this->assertSame(0, $results); 38 | $log = $mock->getQueryLog(); 39 | $this->assertSame('foo', $log[0]['query']); 40 | $this->assertEquals(['bar'], $log[0]['bindings']); 41 | $this->assertIsNumeric($log[0]['time']); 42 | } 43 | 44 | protected function getMockConnection($methods = [], $pdo = null) 45 | { 46 | $pdo = $pdo ?: new OracleDBConnectionTestMockPDO; 47 | $defaults = ['getDefaultQueryGrammar', 'getDefaultPostProcessor', 'getDefaultSchemaGrammar']; 48 | $connection = $this->getMockBuilder(OracleConnection::class)->onlyMethods(array_merge($defaults, $methods))->setConstructorArgs([$pdo])->getMock(); 49 | $connection->enableQueryLog(); 50 | 51 | return $connection; 52 | } 53 | } 54 | 55 | class OracleDBConnectionTestMockPDO extends OCI 56 | { 57 | public function __construct() 58 | { 59 | // 60 | } 61 | 62 | public function __destruct() 63 | { 64 | // 65 | } 66 | } 67 | 68 | class OracleDBConnectionTestMockOCIStatement extends OCIStatement 69 | { 70 | public function __construct() 71 | { 72 | // 73 | } 74 | 75 | public function __destruct() 76 | { 77 | // 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/OracleDBConnectorTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(OracleConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock(); 21 | $connection = m::mock(\stdClass::class); 22 | $connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']); 23 | $connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection); 24 | $result = $connector->connect($config); 25 | 26 | $this->assertSame($result, $connection); 27 | } 28 | 29 | public static function oracleConnectProvider() 30 | { 31 | return [ 32 | [ 33 | 'oci:dbname=(DESCRIPTION =(ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1234))(CONNECT_DATA =(SID = ORCL)))', 34 | ['driver' => 'pdo', 'host' => 'localhost', 'port' => '1234', 'database' => 'ORCL', 'tns' => ''], 35 | ], 36 | [ 37 | 'oci:dbname=(DESCRIPTION =(ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 4321))(CONNECT_DATA =(SID = ORCL)))', 38 | ['driver' => 'pdo', 'tns' => '(DESCRIPTION =(ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 4321))(CONNECT_DATA =(SID = ORCL)))'], 39 | ], 40 | [ 41 | '(DESCRIPTION =(ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 6789))(CONNECT_DATA =(SID = ORCL)))', 42 | ['driver' => 'oci8', 'tns' => '(DESCRIPTION =(ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 6789))(CONNECT_DATA =(SID = ORCL)))'], 43 | ], 44 | [ 45 | '(DESCRIPTION =(ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 9876))(CONNECT_DATA =(SID = ORCL)))', 46 | ['driver' => 'oci8', 'host' => 'localhost', 'port' => '9876', 'database' => 'ORCL', 'tns' => ''], 47 | ], 48 | ]; 49 | } 50 | 51 | public function testOracleConnectWithInvalidDriver() 52 | { 53 | $this->expectException(\InvalidArgumentException::class); 54 | $this->expectExceptionMessage('Unsupported driver [garbage].'); 55 | 56 | (new OracleConnector)->createConnection('', ['driver' => 'garbage'], []); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/OracleDBOCIProcessorTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('The oci8 extension is not available.'); 19 | } 20 | } 21 | 22 | public function tearDown(): void 23 | { 24 | m::close(); 25 | } 26 | 27 | public function testInsertGetIdProcessing() 28 | { 29 | $connection = m::mock(OracleConnection::class); 30 | $connection->shouldReceive('oracleInsertGetId')->once()->with('sql', [1, 'foo', true, null])->andReturn(1234); 31 | 32 | $builder = m::mock(OracleBuilder::class); 33 | $builder->shouldReceive('getConnection')->once()->andReturn($connection); 34 | 35 | $processor = new OracleProcessor; 36 | $result = $processor->processInsertGetId($builder, 'sql', [1, 'foo', true, null]); 37 | $this->assertSame(1234, $result); 38 | } 39 | 40 | // Laravel's DatabaseMySqlProcessorTest, DatabasePostgresProcessorTest, etc have a test named 41 | // testProcessColumns, but $processor->processColumns is only used by Schema Builder class, and we 42 | // are planning to remove Schema Builder entirely (todo) from this package. 43 | } 44 | -------------------------------------------------------------------------------- /tests/OracleDBOCIStatementTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped( 56 | 'The oci8 extension is not available.' 57 | ); 58 | } else { 59 | global $OCIStatementStatus, $OCIExecuteStatus, $OCIFetchStatus, $OCIFetchAllReturnEmpty, $OCIBindChangeStatus; 60 | 61 | $OCIStatementStatus = true; 62 | $OCIExecuteStatus = true; 63 | $OCIFetchStatus = true; 64 | $OCIFetchAllReturnEmpty = false; 65 | $OCIBindChangeStatus = false; 66 | 67 | $this->oci = m::mock(new TestOCIStub('', null, null, [PDO::ATTR_CASE => PDO::CASE_LOWER])); 68 | $this->stmt = m::mock(new TestOCIStatementStub('oci8 statement', $this->oci, '', [4321 => 'attributeValue'])); 69 | 70 | // fake result sets for all the fetch calls 71 | $this->resultUpperArray = ['FNAME' => 'Test', 'LNAME' => 'Testerson', 'EMAIL' => 'tester@testing.com']; 72 | $this->resultUpperObject = (object) $this->resultUpperArray; 73 | $this->resultLowerArray = array_change_key_case($this->resultUpperArray, \CASE_LOWER); 74 | $this->resultLowerObject = (object) $this->resultLowerArray; 75 | 76 | $this->resultNumArray = [0 => 'Test', 1 => 'Testerson', 2 => 'tester@testing.com']; 77 | 78 | $this->resultBothUpperArray = [0 => 'Test', 1 => 'Testerson', 2 => 'tester@testing.com', 'FNAME' => 'Test', 'LNAME' => 'Testerson', 'EMAIL' => 'tester@testing.com']; 79 | $this->resultBothLowerArray = array_change_key_case($this->resultBothUpperArray, \CASE_LOWER); 80 | 81 | $this->resultAllUpperArray = [$this->resultUpperArray]; 82 | $this->resultAllUpperObject = [$this->resultUpperObject]; 83 | $this->resultAllLowerArray = [$this->resultLowerArray]; 84 | $this->resultAllLowerObject = [$this->resultLowerObject]; 85 | 86 | $this->resultAllNumArray = [$this->resultNumArray]; 87 | 88 | $this->resultAllBothUpperArray = [$this->resultBothUpperArray]; 89 | $this->resultAllBothLowerArray = [$this->resultBothLowerArray]; 90 | } 91 | } 92 | 93 | public function tearDown(): void 94 | { 95 | m::close(); 96 | } 97 | 98 | public function testConstructor() 99 | { 100 | $oci = new TestOCIStub; 101 | $ocistmt = new OCIStatement('oci8 statement', $oci); 102 | 103 | // use reflection to test values of protected properties 104 | $reflection = new ReflectionClass($ocistmt); 105 | 106 | // stmt property 107 | $property = $reflection->getProperty('stmt'); 108 | $property->setAccessible(true); 109 | $this->assertEquals('oci8 statement', $property->getValue($ocistmt)); 110 | 111 | //conn property 112 | $property = $reflection->getProperty('conn'); 113 | $property->setAccessible(true); 114 | $this->assertEquals($oci, $property->getValue($ocistmt)); 115 | 116 | //attributes property 117 | $property = $reflection->getProperty('attributes'); 118 | $property->setAccessible(true); 119 | $this->assertEquals([], $property->getValue($ocistmt)); 120 | } 121 | 122 | public function testConstructorWithoutValidStatementPassignIn() 123 | { 124 | global $OCIStatementStatus; 125 | $OCIStatementStatus = false; 126 | $this->expectException(OCIException::class); 127 | $ocistmt = new OCIStatement('oci8 statement', new TestOCIStub); 128 | } 129 | 130 | public function testDestructor() 131 | { 132 | global $OCIStatementStatus; 133 | $ocistmt = new OCIStatement('oci8 statement', new TestOCIStub); 134 | unset($ocistmt); 135 | $this->assertFalse($OCIStatementStatus); 136 | } 137 | 138 | public function testBindColumnWithColumnName() 139 | { 140 | $stmt = new TestOCIStatementStub('oci8 statement', $this->oci, 'sql', []); 141 | $holder = ''; 142 | $this->expectException(InvalidArgumentException::class); 143 | $stmt->bindColumn('holder', $holder, PDO::PARAM_STR); 144 | } 145 | 146 | public function testBindColumnWithColumnNumberLessThanOne() 147 | { 148 | $stmt = new TestOCIStatementStub('oci8 statement', $this->oci, 'sql', []); 149 | $holder = ''; 150 | $this->expectException(InvalidArgumentException::class); 151 | $stmt->bindColumn(0, $holder, PDO::PARAM_STR); 152 | } 153 | 154 | public function testBindColumnWithInvalidDataType() 155 | { 156 | $stmt = new TestOCIStatementStub('oci8 statement', $this->oci, 'sql', []); 157 | $holder = ''; 158 | $nonExistantDataType = 12345; 159 | $this->expectException(InvalidArgumentException::class); 160 | $stmt->bindColumn(1, $holder, $nonExistantDataType); 161 | } 162 | 163 | public function testBindColumnSuccess() 164 | { 165 | $stmt = new TestOCIStatementStub('oci8 statement', $this->oci, 'sql', []); 166 | $holder = ''; 167 | $this->assertTrue($stmt->bindColumn(1, $holder, PDO::PARAM_STR, 40)); 168 | 169 | $reflection = new ReflectionClass($stmt); 170 | 171 | // bindings property 172 | $property = $reflection->getProperty('bindings'); 173 | $property->setAccessible(true); 174 | $this->assertEquals([1 => ['var' => $holder, 'data_type' => PDO::PARAM_STR, 'max_length' => 40, 'driverdata' => null]], $property->getValue($stmt)); 175 | } 176 | 177 | public function testBindParamWithValidDataType() 178 | { 179 | global $OCIBindChangeStatus; 180 | $OCIBindChangeStatus = true; 181 | $variable = ''; 182 | 183 | $stmt = new TestOCIStatementStub(true, new TestOCIStub, '', []); 184 | $this->assertTrue($stmt->bindParam('param', $variable)); 185 | $this->assertEquals('oci_bind_by_name', $variable); 186 | } 187 | 188 | public function testBindParamWithInvalidDataType() 189 | { 190 | $variable = ''; 191 | $nonExistantDataType = 12345; 192 | $this->expectException(InvalidArgumentException::class); 193 | 194 | $stmt = new TestOCIStatementStub(true, new TestOCIStub, '', []); 195 | $stmt->bindParam('param', $variable, $nonExistantDataType); 196 | } 197 | 198 | public function testBindParamWithReturnDataType() 199 | { 200 | global $OCIBindChangeStatus; 201 | $OCIBindChangeStatus = true; 202 | $variable = ''; 203 | 204 | $stmt = new TestOCIStatementStub(true, new TestOCIStub, '', []); 205 | $this->assertTrue($stmt->bindParam('param', $variable, PDO::PARAM_INPUT_OUTPUT)); 206 | $this->assertEquals('oci_bind_by_name', $variable); 207 | } 208 | 209 | public function testBindValueWithValidDataType() 210 | { 211 | $this->assertTrue($this->stmt->bindValue('param', 'hello')); 212 | } 213 | 214 | public function testBindValueWithNullDataType() 215 | { 216 | global $OCIBindByNameTypeReceived; 217 | $this->assertTrue($this->stmt->bindValue('param', null, PDO::PARAM_NULL)); 218 | $this->assertSame(\SQLT_CHR, $OCIBindByNameTypeReceived); 219 | } 220 | 221 | public function testBindValueWithInvalidDataType() 222 | { 223 | $this->expectException(InvalidArgumentException::class); 224 | $this->stmt->bindValue(0, 'hello', 8); 225 | } 226 | 227 | // todo update this test once this method has been implemented 228 | public function testCloseCursor() 229 | { 230 | $this->assertTrue($this->stmt->closeCursor()); 231 | } 232 | 233 | public function testColumnCount() 234 | { 235 | $this->assertEquals(1, $this->stmt->columnCount()); 236 | } 237 | 238 | public function testDebugDumpParamsWhenNothingHasBeenSet() 239 | { 240 | $expectedOutput = print_r(['sql' => '', 'params' => []], true); 241 | 242 | $this->expectOutputString($expectedOutput); 243 | 244 | $this->assertTrue($this->stmt->debugDumpParams()); 245 | } 246 | 247 | public function testDebugDumpParamsWhenThingsHaveBeenSet() 248 | { 249 | global $OCIBindChangeStatus; 250 | $OCIBindChangeStatus = false; 251 | 252 | $sql = 'select * from table where id = :0 and name = :1'; 253 | $var = 'Hello'; 254 | $expectedOutput = print_r( 255 | [ 256 | 'sql' => $sql, 257 | 'params' => [ 258 | [ 259 | 'paramno' => 0, 260 | 'name' => ':0', 261 | 'value' => $var, 262 | 'is_param' => 1, 263 | 'param_type' => PDO::PARAM_INPUT_OUTPUT, 264 | ], 265 | [ 266 | 'paramno' => 1, 267 | 'name' => ':1', 268 | 'value' => 'hi', 269 | 'is_param' => 1, 270 | 'param_type' => PDO::PARAM_STR, 271 | ], 272 | ], 273 | ], 274 | true 275 | ); 276 | 277 | $stmt = new TestOCIStatementStub(true, true, $sql, []); 278 | $stmt->bindParam(0, $var, PDO::PARAM_INPUT_OUTPUT); 279 | $stmt->bindValue(1, 'hi'); 280 | 281 | $this->expectOutputString($expectedOutput); 282 | 283 | $this->assertTrue($stmt->debugDumpParams()); 284 | } 285 | 286 | public function testErrorCode() 287 | { 288 | $ocistmt = new TestOCIStatementStub(true, '', '', []); 289 | $this->assertNull($ocistmt->errorCode()); 290 | 291 | // use reflection to test values of protected properties 292 | $reflection = new ReflectionClass($ocistmt); 293 | 294 | // setErrorInfo 295 | $method = $reflection->getMethod('setErrorInfo'); 296 | $method->setAccessible(true); 297 | $method->invoke($ocistmt, '11111', '2222', 'Testing the errors'); 298 | 299 | $this->assertEquals('11111', $ocistmt->errorCode()); 300 | } 301 | 302 | public function testErrorInfo() 303 | { 304 | $ocistmt = new TestOCIStatementStub(true, '', '', []); 305 | $this->assertEquals([0 => '', 1 => null, 2 => null], $ocistmt->errorInfo()); 306 | 307 | // use reflection to test values of protected properties 308 | $reflection = new ReflectionClass($ocistmt); 309 | 310 | // setErrorInfo 311 | $method = $reflection->getMethod('setErrorInfo'); 312 | $method->setAccessible(true); 313 | $method->invoke($ocistmt, '11111', '2222', 'Testing the errors'); 314 | 315 | $this->assertEquals([0 => '11111', 1 => '2222', 2 => 'Testing the errors'], $ocistmt->errorInfo()); 316 | } 317 | 318 | public function testExecutePassesWithParameters() 319 | { 320 | $this->assertTrue($this->stmt->execute([0 => 1])); 321 | } 322 | 323 | public function testExecutePassesWithoutParameters() 324 | { 325 | $this->assertTrue($this->stmt->execute()); 326 | } 327 | 328 | public function testExecuteFailesWithParameters() 329 | { 330 | global $OCIExecuteStatus; 331 | $OCIExecuteStatus = false; 332 | $this->assertFalse($this->stmt->execute([0 => 1])); 333 | $this->assertEquals('07000', $this->stmt->errorCode()); 334 | } 335 | 336 | public function testExecuteFailesWithoutParameters() 337 | { 338 | global $OCIExecuteStatus; 339 | $OCIExecuteStatus = false; 340 | $this->assertFalse($this->stmt->execute()); 341 | $this->assertEquals('07000', $this->stmt->errorCode()); 342 | } 343 | 344 | public function testFetchWithBindColumn() 345 | { 346 | $this->oci->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); 347 | $stmt = new TestOCIStatementStub('oci8 statement', $this->oci, 'sql', []); 348 | $holder = 'dad'; 349 | $this->assertTrue($stmt->bindColumn(1, $holder, PDO::PARAM_STR, 40)); 350 | 351 | $reflection = new ReflectionClass($stmt); 352 | 353 | // bindings property 354 | $property = $reflection->getProperty('bindings'); 355 | $property->setAccessible(true); 356 | $this->assertEquals([1 => ['var' => $holder, 'data_type' => PDO::PARAM_STR, 'max_length' => 40, 'driverdata' => null]], $property->getValue($stmt)); 357 | 358 | $obj = $stmt->fetch(PDO::FETCH_CLASS); 359 | 360 | $this->assertEquals([1 => ['var' => $holder, 'data_type' => PDO::PARAM_STR, 'max_length' => 40, 'driverdata' => null]], $property->getValue($stmt)); 361 | 362 | $this->assertEquals($obj->fname, $holder); 363 | } 364 | 365 | public function testFetchSuccessReturnArray() 366 | { 367 | // return lower case 368 | $this->oci->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); 369 | $this->assertEquals($this->resultLowerArray, $this->stmt->fetch(PDO::FETCH_ASSOC)); 370 | $this->assertEquals($this->resultBothLowerArray, $this->stmt->fetch(PDO::FETCH_BOTH)); 371 | 372 | // return upper cased keyed object 373 | $this->oci->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER); 374 | $this->assertEquals($this->resultUpperArray, $this->stmt->fetch(PDO::FETCH_ASSOC)); 375 | $this->assertEquals($this->resultBothUpperArray, $this->stmt->fetch(PDO::FETCH_BOTH)); 376 | 377 | // return natural keyed object, in oracle that is upper case 378 | $this->oci->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); 379 | $this->assertEquals($this->resultUpperArray, $this->stmt->fetch(PDO::FETCH_ASSOC)); 380 | $this->assertEquals($this->resultBothUpperArray, $this->stmt->fetch(PDO::FETCH_BOTH)); 381 | 382 | $this->assertEquals($this->resultNumArray, $this->stmt->fetch(PDO::FETCH_NUM)); 383 | } 384 | 385 | public function testFetchSuccessReturnObject() 386 | { 387 | // return lower cased keyed object 388 | $this->oci->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); 389 | $this->assertEquals($this->resultLowerObject, $this->stmt->fetch(PDO::FETCH_CLASS)); 390 | 391 | // return upper cased keyed object 392 | $this->oci->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER); 393 | $this->assertEquals($this->resultUpperObject, $this->stmt->fetch(PDO::FETCH_CLASS)); 394 | 395 | // return natural keyed object, in oracle that is upper case 396 | $this->oci->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); 397 | $this->assertEquals($this->resultUpperObject, $this->stmt->fetch(PDO::FETCH_CLASS)); 398 | } 399 | 400 | public function testFetchFail() 401 | { 402 | global $OCIFetchStatus; 403 | $OCIFetchStatus = false; 404 | $this->assertFalse($this->stmt->fetch()); 405 | $this->assertEquals('07000', $this->stmt->errorCode()); 406 | } 407 | 408 | public function testFetchAllWithNoArg() 409 | { 410 | // return lower cased keyed object 411 | $this->oci->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); 412 | $this->assertEquals($this->resultAllLowerObject, $this->stmt->fetchAll()); 413 | 414 | // return upper cased keyed object 415 | $this->oci->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER); 416 | $this->assertEquals($this->resultAllUpperObject, $this->stmt->fetchAll()); 417 | 418 | // return natural keyed object, in oracle that is upper case 419 | $this->oci->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); 420 | $this->assertEquals($this->resultAllUpperObject, $this->stmt->fetchAll()); 421 | } 422 | 423 | public function testFetchAllReturnArray() 424 | { 425 | // return lower case 426 | $this->oci->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); 427 | $this->assertEquals($this->resultAllLowerArray, $this->stmt->fetchAll(PDO::FETCH_ASSOC)); 428 | 429 | // return upper cased keyed object 430 | $this->oci->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER); 431 | $this->assertEquals($this->resultAllUpperArray, $this->stmt->fetchAll(PDO::FETCH_ASSOC)); 432 | 433 | // return natural keyed object, in oracle that is upper case 434 | $this->oci->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); 435 | $this->assertEquals($this->resultAllUpperArray, $this->stmt->fetchAll(PDO::FETCH_ASSOC)); 436 | } 437 | 438 | public function testFetchAllReturnObject() 439 | { 440 | // return lower cased keyed object 441 | $this->oci->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); 442 | $this->assertEquals($this->resultAllLowerObject, $this->stmt->fetchAll(PDO::FETCH_CLASS)); 443 | 444 | // return upper cased keyed object 445 | $this->oci->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER); 446 | $this->assertEquals($this->resultAllUpperObject, $this->stmt->fetchAll(PDO::FETCH_CLASS)); 447 | 448 | // return natural keyed object, in oracle that is upper case 449 | $this->oci->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); 450 | $this->assertEquals($this->resultAllUpperObject, $this->stmt->fetchAll(PDO::FETCH_CLASS)); 451 | } 452 | 453 | public function testFetchAllWhenEmptyResultSet() 454 | { 455 | global $OCIFetchAllReturnEmpty; 456 | $OCIFetchAllReturnEmpty = true; 457 | $this->assertSame([], $this->stmt->fetchAll()); 458 | } 459 | 460 | public function testFetchAllFailWithInvalidFetchStyle() 461 | { 462 | $invalidMode = PDO::FETCH_BOTH; 463 | $this->expectException(InvalidArgumentException::class); 464 | $this->expectExceptionMessage('Invalid fetch style requested: '.$invalidMode.'. Only PDO::FETCH_CLASS and PDO::FETCH_ASSOC suported.'); 465 | $this->stmt->fetchAll($invalidMode); 466 | } 467 | 468 | public function testFetchColumnWithNoArg() 469 | { 470 | $this->assertEquals($this->resultNumArray[0], $this->stmt->fetchColumn()); 471 | } 472 | 473 | public function testFetchColumnWithColumnNumber() 474 | { 475 | $this->assertEquals($this->resultNumArray[1], $this->stmt->fetchColumn(1)); 476 | } 477 | 478 | public function testFetchObject() 479 | { 480 | // return lower cased keyed object 481 | $this->oci->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); 482 | $this->assertEquals($this->resultLowerObject, $this->stmt->fetchObject()); 483 | 484 | // return upper cased keyed object 485 | $this->oci->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER); 486 | $this->assertEquals($this->resultUpperObject, $this->stmt->fetchObject()); 487 | 488 | // return natural keyed object, in oracle that is upper case 489 | $this->oci->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); 490 | $this->assertEquals($this->resultUpperObject, $this->stmt->fetchObject()); 491 | } 492 | 493 | public function testGetAttributeForValidAttribute() 494 | { 495 | $this->assertEquals('attributeValue', $this->stmt->getAttribute(4321)); 496 | } 497 | 498 | public function testGetAttributeForInvalidAttribute() 499 | { 500 | $this->assertEquals(null, $this->stmt->getAttribute(12345)); 501 | } 502 | 503 | public function testGetColumnMeta() 504 | { 505 | $expected = ['native_type' => 1, 'driver:decl_type' => 1, 506 | 'name' => 1, 'len' => 1, 'precision' => 1, ]; 507 | 508 | $result = $this->stmt->getColumnMeta(0); 509 | $this->assertEquals($expected, $result); 510 | } 511 | 512 | public function testNextRowset() 513 | { 514 | $this->assertTrue($this->stmt->nextRowset()); 515 | } 516 | 517 | public function testRowCount() 518 | { 519 | $this->assertEquals(1, $this->stmt->rowCount()); 520 | } 521 | 522 | public function testSetAttribute() 523 | { 524 | $attr = PDO::ATTR_DEFAULT_FETCH_MODE; 525 | $value = PDO::FETCH_CLASS; 526 | 527 | $this->assertTrue($this->stmt->setAttribute($attr, $value)); 528 | $this->assertEquals($value, $this->stmt->getAttribute($attr)); 529 | } 530 | 531 | public function testSetFetchMode() 532 | { 533 | $this->assertTrue($this->stmt->setFetchMode(PDO::FETCH_CLASS)); 534 | } 535 | 536 | public function testGetOCIResource() 537 | { 538 | $this->assertEquals('oci8 statement', $this->stmt->getOCIResource()); 539 | } 540 | } 541 | -------------------------------------------------------------------------------- /tests/OracleDBOCITest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped( 24 | 'The oci8 extension is not available.' 25 | ); 26 | } else { 27 | global $OCITransactionStatus, $OCIStatementStatus, $OCIExecuteStatus; 28 | 29 | $OCITransactionStatus = true; 30 | $OCIStatementStatus = true; 31 | $OCIExecuteStatus = true; 32 | 33 | $this->oci = m::mock(new TestOCIStub('', null, null, [PDO::ATTR_CASE => PDO::CASE_LOWER])); 34 | } 35 | } 36 | 37 | public function tearDown(): void 38 | { 39 | m::close(); 40 | } 41 | 42 | public function testConstructorSuccessWithPersistentConnection() 43 | { 44 | $oci = new OCI('dsn', null, null, [PDO::ATTR_PERSISTENT => 1]); 45 | $this->assertInstanceOf(OCI::class, $oci); 46 | $this->assertEquals(1, $oci->getAttribute(PDO::ATTR_PERSISTENT)); 47 | } 48 | 49 | public function testConstructorSuccessWithoutPersistentConnection() 50 | { 51 | $oci = new OCI('dsn', null, null, [PDO::ATTR_PERSISTENT => 0]); 52 | $this->assertInstanceOf(OCI::class, $oci); 53 | $this->assertEquals(0, $oci->getAttribute(PDO::ATTR_PERSISTENT)); 54 | } 55 | 56 | public function testConstructorFailWithPersistentConnection() 57 | { 58 | global $OCITransactionStatus; 59 | $OCITransactionStatus = false; 60 | $this->expectException(OCIException::class); 61 | $oci = new OCI('dsn', null, null, [PDO::ATTR_PERSISTENT => 1]); 62 | } 63 | 64 | public function testConstructorFailWithoutPersistentConnection() 65 | { 66 | global $OCITransactionStatus; 67 | $OCITransactionStatus = false; 68 | $this->expectException(OCIException::class); 69 | $oci = new OCI('dsn', null, null, [PDO::ATTR_PERSISTENT => 0]); 70 | } 71 | 72 | public function testDestructor() 73 | { 74 | global $OCITransactionStatus; 75 | 76 | $oci = new OCI('dsn', '', ''); 77 | unset($oci); 78 | $this->assertFalse($OCITransactionStatus); 79 | } 80 | 81 | public function testBeginTransaction() 82 | { 83 | $result = $this->oci->beginTransaction(); 84 | $this->assertTrue($result); 85 | 86 | $this->assertEquals(0, $this->oci->getExecuteMode()); 87 | } 88 | 89 | public function testBeginTransactionAlreadyInTransaction() 90 | { 91 | $this->expectException(OCIException::class); 92 | $result = $this->oci->beginTransaction(); 93 | $result = $this->oci->beginTransaction(); 94 | } 95 | 96 | public function testCommitInTransactionPasses() 97 | { 98 | $this->oci->beginTransaction(); 99 | $this->assertTrue($this->oci->commit()); 100 | } 101 | 102 | public function testCommitInTransactionFails() 103 | { 104 | global $OCITransactionStatus; 105 | $OCITransactionStatus = false; 106 | $this->expectException(OCIException::class); 107 | $this->oci->beginTransaction(); 108 | $this->oci->commit(); 109 | } 110 | 111 | public function testCommitNotInTransaction() 112 | { 113 | $this->assertFalse($this->oci->commit()); 114 | } 115 | 116 | public function testErrorCode() 117 | { 118 | $oci = new TestOCIStub; 119 | $this->assertNull($oci->errorCode()); 120 | 121 | // use reflection to test values of protected properties 122 | $reflection = new \ReflectionClass($oci); 123 | 124 | // setErrorInfo 125 | $method = $reflection->getMethod('setErrorInfo'); 126 | $method->setAccessible(true); 127 | $method->invoke($oci, '11111', '2222', 'Testing the errors'); 128 | 129 | $this->assertEquals('11111', $oci->errorCode()); 130 | } 131 | 132 | public function testErrorInfo() 133 | { 134 | $oci = new TestOCIStub; 135 | $this->assertEquals([0 => '', 1 => null, 2 => null], $oci->errorInfo()); 136 | 137 | // use reflection to test values of protected properties 138 | $reflection = new \ReflectionClass($oci); 139 | 140 | // setErrorInfo 141 | $method = $reflection->getMethod('setErrorInfo'); 142 | $method->setAccessible(true); 143 | $method->invoke($oci, '11111', '2222', 'Testing the errors'); 144 | 145 | $this->assertEquals([0 => '11111', 1 => '2222', 2 => 'Testing the errors'], $oci->errorInfo()); 146 | } 147 | 148 | public function testExec() 149 | { 150 | $sql = 'select * from table'; 151 | $oci = new TestOCIStub; 152 | $stmt = $oci->exec($sql); 153 | $this->assertEquals(1, $stmt); 154 | 155 | // use reflection to test values of protected properties of OCI object 156 | $reflection = new \ReflectionClass($oci); 157 | 158 | // stmt property 159 | $property = $reflection->getProperty('stmt'); 160 | $property->setAccessible(true); 161 | $oci_stmt = $property->getValue($oci); 162 | $this->assertInstanceOf(OCIStatement::class, $oci_stmt); 163 | 164 | // use reflection to test values of protected properties of OCIStatement object 165 | $reflection = new \ReflectionClass($oci_stmt); 166 | //conn property 167 | $property = $reflection->getProperty('conn'); 168 | $property->setAccessible(true); 169 | $this->assertEquals($oci, $property->getValue($oci_stmt)); 170 | 171 | //attributes property 172 | $property = $reflection->getProperty('attributes'); 173 | $property->setAccessible(true); 174 | $this->assertEquals([], $property->getValue($oci_stmt)); 175 | } 176 | 177 | public function testExecFails() 178 | { 179 | global $OCIExecuteStatus; 180 | $OCIExecuteStatus = false; 181 | $sql = 'select * from table'; 182 | $oci = new TestOCIStub; 183 | $stmt = $oci->exec($sql); 184 | $this->assertFalse($stmt); 185 | } 186 | 187 | public function testGetAttributeForValidAttribute() 188 | { 189 | $this->assertEquals(1, $this->oci->getAttribute(PDO::ATTR_AUTOCOMMIT)); 190 | } 191 | 192 | public function testGetAttributeForInvalidAttribute() 193 | { 194 | $nonExistantAttr = 12345; 195 | $this->assertEquals(null, $this->oci->getAttribute($nonExistantAttr)); 196 | } 197 | 198 | public function testInTransactionWhileNotInTransaction() 199 | { 200 | $this->assertFalse($this->oci->inTransaction()); 201 | } 202 | 203 | public function testInTransactionWhileInTransaction() 204 | { 205 | $this->oci->beginTransaction(); 206 | $this->assertTrue($this->oci->inTransaction()); 207 | } 208 | 209 | public function testLastInsertIDWithName() 210 | { 211 | $this->expectException(OCIException::class); 212 | $result = $this->oci->lastInsertID('foo'); 213 | } 214 | 215 | public function testLastInsertIDWithoutName() 216 | { 217 | $this->expectException(OCIException::class); 218 | $result = $this->oci->lastInsertID(); 219 | } 220 | 221 | public function testPrepareWithNonParameterQuery() 222 | { 223 | $sql = 'select * from table'; 224 | $oci = new TestOCIStub; 225 | $stmt = $oci->prepare($sql); 226 | $this->assertInstanceOf(OCIStatement::class, $stmt); 227 | 228 | // use reflection to test values of protected properties 229 | $reflection = new \ReflectionClass($stmt); 230 | 231 | // stmt property 232 | $property = $reflection->getProperty('stmt'); 233 | $property->setAccessible(true); 234 | $this->assertEquals('oci8 statement', $property->getValue($stmt)); 235 | 236 | //conn property 237 | $property = $reflection->getProperty('conn'); 238 | $property->setAccessible(true); 239 | $this->assertEquals($oci, $property->getValue($stmt)); 240 | 241 | //attributes property 242 | $property = $reflection->getProperty('attributes'); 243 | $property->setAccessible(true); 244 | $this->assertEquals([], $property->getValue($stmt)); 245 | } 246 | 247 | public function testPrepareWithParameterQuery() 248 | { 249 | $sql = 'select * from table where id = ? and date = ?'; 250 | $oci = new TestOCIStub; 251 | $stmt = $oci->prepare($sql); 252 | $this->assertInstanceOf(OCIStatement::class, $stmt); 253 | 254 | // use reflection to test values of protected properties 255 | $reflection = new \ReflectionClass($stmt); 256 | 257 | // stmt property 258 | $property = $reflection->getProperty('stmt'); 259 | $property->setAccessible(true); 260 | $this->assertEquals('oci8 statement', $property->getValue($stmt)); 261 | 262 | //conn property 263 | $property = $reflection->getProperty('conn'); 264 | $property->setAccessible(true); 265 | $this->assertEquals($oci, $property->getValue($stmt)); 266 | 267 | //attributes property 268 | $property = $reflection->getProperty('attributes'); 269 | $property->setAccessible(true); 270 | $this->assertEquals([], $property->getValue($stmt)); 271 | } 272 | 273 | public function testPrepareFail() 274 | { 275 | global $OCIStatementStatus; 276 | $OCIStatementStatus = false; 277 | $sql = 'select * from table where id = ? and date = ?'; 278 | $oci = new TestOCIStub; 279 | $this->expectException(OCIException::class); 280 | $stmt = $oci->prepare($sql); 281 | } 282 | 283 | public function testQuery() 284 | { 285 | $sql = 'select * from table'; 286 | $oci = new TestOCIStub; 287 | $stmt = $oci->query($sql); 288 | $this->assertInstanceOf(OCIStatement::class, $stmt); 289 | 290 | // use reflection to test values of protected properties 291 | $reflection = new \ReflectionClass($stmt); 292 | 293 | // stmt property 294 | $property = $reflection->getProperty('stmt'); 295 | $property->setAccessible(true); 296 | $this->assertEquals('oci8 statement', $property->getValue($stmt)); 297 | 298 | //conn property 299 | $property = $reflection->getProperty('conn'); 300 | $property->setAccessible(true); 301 | $this->assertEquals($oci, $property->getValue($stmt)); 302 | 303 | //attributes property 304 | $property = $reflection->getProperty('attributes'); 305 | $property->setAccessible(true); 306 | $this->assertEquals([], $property->getValue($stmt)); 307 | } 308 | 309 | public function testQueryWithModeParams() 310 | { 311 | $sql = 'select * from table'; 312 | $oci = new TestOCIStub; 313 | $stmt = $oci->query($sql, PDO::FETCH_CLASS, 'stdClass', []); 314 | $this->assertInstanceOf(OCIStatement::class, $stmt); 315 | 316 | // use reflection to test values of protected properties 317 | $reflection = new \ReflectionClass($stmt); 318 | 319 | // stmt property 320 | $property = $reflection->getProperty('stmt'); 321 | $property->setAccessible(true); 322 | $this->assertEquals('oci8 statement', $property->getValue($stmt)); 323 | 324 | //conn property 325 | $property = $reflection->getProperty('conn'); 326 | $property->setAccessible(true); 327 | $this->assertEquals($oci, $property->getValue($stmt)); 328 | 329 | //attributes property 330 | $property = $reflection->getProperty('attributes'); 331 | $property->setAccessible(true); 332 | $this->assertEquals([], $property->getValue($stmt)); 333 | } 334 | 335 | public function testQueryFail() 336 | { 337 | global $OCIExecuteStatus; 338 | $OCIExecuteStatus = false; 339 | $sql = 'select * from table'; 340 | $oci = new TestOCIStub; 341 | $stmt = $oci->query($sql); 342 | $this->assertFalse($stmt); 343 | } 344 | 345 | public function testQuote() 346 | { 347 | $this->assertFalse($this->oci->quote('String')); 348 | $this->assertFalse($this->oci->quote('String', PDO::PARAM_STR)); 349 | } 350 | 351 | public function testRollBackInTransactionPasses() 352 | { 353 | $this->oci->beginTransaction(); 354 | $this->assertTrue($this->oci->rollBack()); 355 | } 356 | 357 | public function testRollBackInTransactionFails() 358 | { 359 | global $OCITransactionStatus; 360 | $OCITransactionStatus = false; 361 | $this->expectException(OCIException::class); 362 | $this->oci->beginTransaction(); 363 | $this->oci->rollBack(); 364 | } 365 | 366 | public function testRollBackNotInTransaction() 367 | { 368 | $this->assertFalse($this->oci->rollBack()); 369 | } 370 | 371 | public function testSetAttribute() 372 | { 373 | $attr = 12345; 374 | 375 | $this->oci->setAttribute($attr, 'value'); 376 | $this->assertEquals('value', $this->oci->getAttribute($attr)); 377 | $this->oci->setAttribute($attr, 4); 378 | $this->assertEquals(4, $this->oci->getAttribute($attr)); 379 | } 380 | 381 | public function testFlipExecuteMode() 382 | { 383 | $this->assertEquals(\OCI_COMMIT_ON_SUCCESS, $this->oci->getExecuteMode()); 384 | $this->oci->flipExecuteMode(); 385 | $this->assertEquals(\OCI_NO_AUTO_COMMIT, $this->oci->getExecuteMode()); 386 | } 387 | 388 | public function testGetExecuteMode() 389 | { 390 | $this->assertEquals(\OCI_COMMIT_ON_SUCCESS, $this->oci->getExecuteMode()); 391 | } 392 | 393 | public function testGetOCIResource() 394 | { 395 | $this->assertEquals('oci8', $this->oci->getOCIResource()); 396 | } 397 | 398 | public function testSetExecuteModeWithValidMode() 399 | { 400 | $this->oci->setExecuteMode(\OCI_COMMIT_ON_SUCCESS); 401 | $this->assertEquals(\OCI_COMMIT_ON_SUCCESS, $this->oci->getExecuteMode()); 402 | $this->oci->setExecuteMode(\OCI_NO_AUTO_COMMIT); 403 | $this->assertEquals(\OCI_NO_AUTO_COMMIT, $this->oci->getExecuteMode()); 404 | } 405 | 406 | public function testSetExecuteModeWithInvalidMode() 407 | { 408 | $this->expectException(OCIException::class); 409 | $this->oci->setExecuteMode('foo'); 410 | } 411 | } 412 | -------------------------------------------------------------------------------- /tests/OracleDBPDOProcessorTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(ProcessorTestPDOStub::class)->getMock(); 24 | $pdo->expects($this->once())->method('lastInsertId')->with($this->equalTo('id'))->willReturn('1'); 25 | 26 | $connection = m::mock(Connection::class); 27 | $connection->shouldReceive('insert')->once()->with('sql', ['foo']); 28 | $connection->shouldReceive('getPdo')->andReturn($pdo); 29 | 30 | $builder = m::mock(Builder::class); 31 | $builder->shouldReceive('getConnection')->times(2)->andReturn($connection); 32 | 33 | $processor = new Processor; 34 | 35 | $result = $processor->processInsertGetId($builder, 'sql', ['foo'], 'id'); 36 | $this->assertSame(1, $result); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/OracleDBQueryGrammarTest.php: -------------------------------------------------------------------------------- 1 | shouldReceive('escape')->with('foo', false)->andReturn("'foo'"); 22 | $grammar = new OracleGrammar; 23 | $grammar->setConnection($connection); 24 | 25 | $query = $grammar->substituteBindingsIntoRawSql( 26 | 'select * from "users" where \'Hello\\\'World?\' IS NOT NULL AND "email" = ?', 27 | ['foo'], 28 | ); 29 | 30 | $this->assertSame('select * from "users" where \'Hello\\\'World?\' IS NOT NULL AND "email" = \'foo\'', $query); 31 | } 32 | 33 | #[TestWith([null, 'Y-m-d H:i:s'])] 34 | #[TestWith(['d-M-y H:i:s', 'd-M-y H:i:s'])] 35 | public function testGetDateFormat($valFetchedFromConfig, $expectedResult) 36 | { 37 | $connection = m::mock(OracleConnection::class); 38 | $connection->shouldReceive('getConfig')->with('date_format')->andReturn($valFetchedFromConfig); 39 | $grammar = new OracleGrammar; 40 | $grammar->setConnection($connection); 41 | 42 | $this->assertSame($expectedResult, $grammar->getDateFormat()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/OracleDBSchemaGrammarTest.php: -------------------------------------------------------------------------------- 1 | expectException(LogicException::class); 28 | $this->expectExceptionMessage('This database driver does not support creating databases.'); 29 | 30 | $grammar->compileCreateDatabase('foo', m::mock(OracleConnection::class)); 31 | } 32 | 33 | public function testDropDatabaseIfExists() 34 | { 35 | $grammar = new class extends OracleGrammar {}; 36 | 37 | $this->expectException(LogicException::class); 38 | $this->expectExceptionMessage('This database driver does not support dropping databases.'); 39 | 40 | $grammar->compileDropDatabaseIfExists('foo'); 41 | } 42 | 43 | public function testBasicCreateTable() 44 | { 45 | $blueprint = new Blueprint('users'); 46 | $blueprint->create(); 47 | $blueprint->increments('id'); 48 | $blueprint->string('email'); 49 | 50 | $conn = $this->getConnection(); 51 | $conn->shouldNotReceive('getConfig'); 52 | 53 | $statements = $blueprint->toSql($conn, $this->getGrammar()); 54 | 55 | $this->assertCount(1, $statements); 56 | $this->assertSame('create table users ( id number(10,0) not null, email varchar2(255) not null, constraint users_id_primary primary key ( id ) )', $statements[0]); 57 | } 58 | 59 | public function testBasicCreateTableWithPrimary() 60 | { 61 | $blueprint = new Blueprint('users'); 62 | $blueprint->create(); 63 | $blueprint->integer('id')->primary(); 64 | $blueprint->string('email'); 65 | 66 | $conn = $this->getConnection(); 67 | 68 | $statements = $blueprint->toSql($conn, $this->getGrammar()); 69 | 70 | $this->assertEquals(1, count($statements)); 71 | $this->assertEquals('create table users ( id number(10,0) not null, email varchar2(255) not null, constraint users_id_primary primary key ( id ) )', $statements[0]); 72 | } 73 | 74 | public function testBasicCreateTableWithPrefix() 75 | { 76 | $blueprint = new Blueprint('users'); 77 | $blueprint->create(); 78 | $blueprint->increments('id'); 79 | $blueprint->string('email'); 80 | $grammar = $this->getGrammar(); 81 | $grammar->setTablePrefix('prefix_'); 82 | 83 | $conn = $this->getConnection(); 84 | 85 | $statements = $blueprint->toSql($conn, $grammar); 86 | 87 | $this->assertEquals(1, count($statements)); 88 | $this->assertEquals('create table prefix_users ( id number(10,0) not null, email varchar2(255) not null, constraint users_id_primary primary key ( id ) )', $statements[0]); 89 | } 90 | 91 | public function testBasicCreateTableWithDefaultValueAndIsNotNull() 92 | { 93 | $blueprint = new Blueprint('users'); 94 | $blueprint->create(); 95 | $blueprint->integer('id')->primary(); 96 | $blueprint->string('email')->default('user@test.com'); 97 | 98 | $conn = $this->getConnection(); 99 | 100 | $statements = $blueprint->toSql($conn, $this->getGrammar()); 101 | 102 | $this->assertEquals(1, count($statements)); 103 | $this->assertEquals('create table users ( id number(10,0) not null, email varchar2(255) default \'user@test.com\' not null, constraint users_id_primary primary key ( id ) )', $statements[0]); 104 | } 105 | 106 | public function testBasicCreateTableWithPrefixAndPrimary() 107 | { 108 | $blueprint = new Blueprint('users'); 109 | $blueprint->create(); 110 | $blueprint->integer('id')->primary(); 111 | $blueprint->string('email'); 112 | $grammar = $this->getGrammar(); 113 | $grammar->setTablePrefix('prefix_'); 114 | 115 | $conn = $this->getConnection(); 116 | 117 | $statements = $blueprint->toSql($conn, $grammar); 118 | 119 | $this->assertEquals(1, count($statements)); 120 | $this->assertEquals('create table prefix_users ( id number(10,0) not null, email varchar2(255) not null, constraint users_id_primary primary key ( id ) )', $statements[0]); 121 | } 122 | 123 | public function testBasicCreateTableWithPrefixPrimaryAndForeignKeys() 124 | { 125 | $blueprint = new Blueprint('users'); 126 | $blueprint->create(); 127 | $blueprint->integer('id')->primary(); 128 | $blueprint->string('email'); 129 | $blueprint->integer('foo_id'); 130 | $blueprint->foreign('foo_id')->references('id')->on('orders'); 131 | $grammar = $this->getGrammar(); 132 | $grammar->setTablePrefix('prefix_'); 133 | 134 | $conn = $this->getConnection(); 135 | 136 | $statements = $blueprint->toSql($conn, $grammar); 137 | 138 | $this->assertEquals(1, count($statements)); 139 | $this->assertEquals('create table prefix_users ( id number(10,0) not null, email varchar2(255) not null, foo_id number(10,0) not null, constraint users_foo_id_foreign foreign key ( foo_id ) references prefix_orders ( id ), constraint users_id_primary primary key ( id ) )', $statements[0]); 140 | } 141 | 142 | public function testBasicCreateTableWithPrefixPrimaryAndForeignKeysWithCascadeDelete() 143 | { 144 | $blueprint = new Blueprint('users'); 145 | $blueprint->create(); 146 | $blueprint->integer('id')->primary(); 147 | $blueprint->string('email'); 148 | $blueprint->integer('foo_id'); 149 | $blueprint->foreign('foo_id')->references('id')->on('orders')->onDelete('cascade'); 150 | $grammar = $this->getGrammar(); 151 | $grammar->setTablePrefix('prefix_'); 152 | 153 | $conn = $this->getConnection(); 154 | 155 | $statements = $blueprint->toSql($conn, $grammar); 156 | 157 | $this->assertEquals(1, count($statements)); 158 | $this->assertEquals('create table prefix_users ( id number(10,0) not null, email varchar2(255) not null, foo_id number(10,0) not null, constraint users_foo_id_foreign foreign key ( foo_id ) references prefix_orders ( id ) on delete cascade, constraint users_id_primary primary key ( id ) )', $statements[0]); 159 | } 160 | 161 | public function testBasicAlterTable() 162 | { 163 | $blueprint = new Blueprint('users'); 164 | $blueprint->increments('id'); 165 | $blueprint->string('email'); 166 | 167 | $conn = $this->getConnection(); 168 | $conn->shouldNotReceive('getConfig'); 169 | 170 | $statements = $blueprint->toSql($conn, $this->getGrammar()); 171 | 172 | $this->assertCount(2, $statements); 173 | $this->assertSame([ 174 | 'alter table users add ( id number(10,0) not null, constraint users_id_primary primary key ( id ) )', 175 | 'alter table users add ( email varchar2(255) not null )', 176 | ], $statements); 177 | } 178 | 179 | public function testBasicAlterTableWithPrefix() 180 | { 181 | $blueprint = new Blueprint('users'); 182 | $blueprint->increments('id'); 183 | $blueprint->string('email'); 184 | $grammar = $this->getGrammar(); 185 | $grammar->setTablePrefix('prefix_'); 186 | 187 | $conn = $this->getConnection(); 188 | 189 | $statements = $blueprint->toSql($conn, $grammar); 190 | 191 | // todo fix OracleGrammar.php code to name the constraint prefix_users_id_primary 192 | 193 | $this->assertCount(2, $statements); 194 | $this->assertSame([ 195 | 'alter table prefix_users add ( id number(10,0) not null, constraint users_id_primary primary key ( id ) )', 196 | 'alter table prefix_users add ( email varchar2(255) not null )', 197 | ], $statements); 198 | } 199 | 200 | public function testDropTable() 201 | { 202 | $blueprint = new Blueprint('users'); 203 | $blueprint->drop(); 204 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 205 | 206 | $this->assertEquals(1, count($statements)); 207 | $this->assertEquals('drop table users', $statements[0]); 208 | } 209 | 210 | public function testDropTableWithPrefix() 211 | { 212 | $blueprint = new Blueprint('users'); 213 | $blueprint->drop(); 214 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 215 | 216 | $this->assertEquals(1, count($statements)); 217 | $this->assertEquals('drop table users', $statements[0]); 218 | } 219 | 220 | public function testDropColumn() 221 | { 222 | $blueprint = new Blueprint('users'); 223 | $blueprint->dropColumn('foo'); 224 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 225 | 226 | $this->assertEquals(1, count($statements)); 227 | $this->assertEquals('alter table users drop ( foo )', $statements[0]); 228 | 229 | $blueprint = new Blueprint('users'); 230 | $blueprint->dropColumn(['foo', 'bar']); 231 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 232 | 233 | $this->assertEquals(1, count($statements)); 234 | $this->assertEquals('alter table users drop ( foo, bar )', $statements[0]); 235 | 236 | $blueprint = new Blueprint('users'); 237 | $blueprint->dropColumn('foo', 'bar'); 238 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 239 | 240 | $this->assertCount(1, $statements); 241 | $this->assertSame('alter table users drop ( foo, bar )', $statements[0]); 242 | } 243 | 244 | public function testDropPrimary() 245 | { 246 | $blueprint = new Blueprint('users'); 247 | $blueprint->dropPrimary('users_pk'); 248 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 249 | 250 | $this->assertEquals(1, count($statements)); 251 | $this->assertEquals('alter table users drop constraint users_pk', $statements[0]); 252 | } 253 | 254 | public function testDropUnique() 255 | { 256 | $blueprint = new Blueprint('users'); 257 | $blueprint->dropUnique('foo'); 258 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 259 | 260 | $this->assertEquals(1, count($statements)); 261 | $this->assertEquals('alter table users drop constraint foo', $statements[0]); 262 | } 263 | 264 | public function testDropIndex() 265 | { 266 | $blueprint = new Blueprint('users'); 267 | $blueprint->dropIndex('foo'); 268 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 269 | 270 | $this->assertEquals(1, count($statements)); 271 | $this->assertEquals('drop index foo', $statements[0]); 272 | } 273 | 274 | public function testDropForeign() 275 | { 276 | $blueprint = new Blueprint('users'); 277 | $blueprint->dropForeign('foo'); 278 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 279 | 280 | $this->assertEquals(1, count($statements)); 281 | $this->assertEquals('alter table users drop constraint foo', $statements[0]); 282 | } 283 | 284 | public function testDropTimestamps() 285 | { 286 | $blueprint = new Blueprint('users'); 287 | $blueprint->dropTimestamps(); 288 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 289 | 290 | $this->assertEquals(1, count($statements)); 291 | $this->assertEquals('alter table users drop ( created_at, updated_at )', $statements[0]); 292 | } 293 | 294 | public function testDropTimestampsTz() 295 | { 296 | $blueprint = new Blueprint('users'); 297 | $blueprint->dropTimestampsTz(); 298 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 299 | 300 | $this->assertCount(1, $statements); 301 | $this->assertSame('alter table users drop ( created_at, updated_at )', $statements[0]); 302 | } 303 | 304 | public function testDropMorphs() 305 | { 306 | $blueprint = new Blueprint('photos'); 307 | $blueprint->dropMorphs('imageable'); 308 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 309 | 310 | $this->assertCount(2, $statements); 311 | $this->assertSame('drop index photos_imageable_type_imageable_id_index', $statements[0]); 312 | $this->assertSame('alter table photos drop ( imageable_type, imageable_id )', $statements[1]); 313 | } 314 | 315 | public function testRenameTable() 316 | { 317 | $blueprint = new Blueprint('users'); 318 | $blueprint->rename('foo'); 319 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 320 | 321 | $this->assertEquals(1, count($statements)); 322 | $this->assertEquals('alter table users rename to foo', $statements[0]); 323 | } 324 | 325 | public function testRenameTableWithPrefix() 326 | { 327 | $blueprint = new Blueprint('users'); 328 | $blueprint->rename('foo'); 329 | $grammar = $this->getGrammar(); 330 | $grammar->setTablePrefix('prefix_'); 331 | 332 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 333 | 334 | $this->assertEquals(1, count($statements)); 335 | $this->assertEquals('alter table users rename to foo', $statements[0]); 336 | } 337 | 338 | public function testAddingPrimaryKey() 339 | { 340 | $blueprint = new Blueprint('users'); 341 | $blueprint->primary('foo', 'bar'); 342 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 343 | 344 | $this->assertEquals(1, count($statements)); 345 | $this->assertEquals('alter table users add constraint bar primary key (foo)', $statements[0]); 346 | } 347 | 348 | public function testAddingUniqueKey() 349 | { 350 | $blueprint = new Blueprint('users'); 351 | $blueprint->unique('foo', 'bar'); 352 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 353 | 354 | $this->assertEquals(1, count($statements)); 355 | $this->assertEquals('alter table users add constraint bar unique ( foo )', $statements[0]); 356 | } 357 | 358 | public function testAddingIndex() 359 | { 360 | $blueprint = new Blueprint('users'); 361 | $blueprint->index(['foo', 'bar'], 'baz'); 362 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 363 | 364 | $this->assertEquals(1, count($statements)); 365 | 366 | $this->assertEquals('create index baz on users ( foo, bar )', $statements[0]); 367 | } 368 | 369 | public function testAddingRawIndex() 370 | { 371 | $blueprint = new Blueprint('users'); 372 | $blueprint->rawIndex('(function(column))', 'raw_index'); 373 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 374 | 375 | $this->assertCount(1, $statements); 376 | $this->assertSame('create index raw_index on users ( (function(column)) )', $statements[0]); 377 | } 378 | 379 | public function testAddingForeignKey() 380 | { 381 | $blueprint = new Blueprint('users'); 382 | $blueprint->foreign('foo_id')->references('id')->on('orders'); 383 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 384 | 385 | $this->assertEquals(1, count($statements)); 386 | $this->assertEquals('alter table users add constraint users_foo_id_foreign foreign key ( foo_id ) references orders ( id )', $statements[0]); 387 | 388 | $blueprint = new Blueprint('users'); 389 | $blueprint->foreign('foo_id')->references('id')->on('orders')->cascadeOnDelete(); 390 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 391 | 392 | $this->assertCount(1, $statements); 393 | $this->assertSame('alter table users add constraint users_foo_id_foreign foreign key ( foo_id ) references orders ( id ) on delete cascade', $statements[0]); 394 | 395 | $blueprint = new Blueprint('users'); 396 | $blueprint->foreign('foo_id')->references('id')->on('orders')->cascadeOnUpdate(); 397 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 398 | 399 | $this->assertCount(1, $statements); 400 | $this->assertSame('alter table users add constraint users_foo_id_foreign foreign key ( foo_id ) references orders ( id ) on update cascade', $statements[0]); 401 | } 402 | 403 | public function testAddingForeignKeyWithCascadeDelete() 404 | { 405 | $blueprint = new Blueprint('users'); 406 | $blueprint->foreign('foo_id')->references('id')->on('orders')->onDelete('cascade'); 407 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 408 | 409 | $this->assertEquals(1, count($statements)); 410 | $this->assertEquals('alter table users add constraint users_foo_id_foreign foreign key ( foo_id ) references orders ( id ) on delete cascade', $statements[0]); 411 | } 412 | 413 | public function testAddingIncrementingID() 414 | { 415 | $blueprint = new Blueprint('users'); 416 | $blueprint->increments('id'); 417 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 418 | 419 | $this->assertEquals(1, count($statements)); 420 | $this->assertEquals('alter table users add ( id number(10,0) not null, constraint users_id_primary primary key ( id ) )', $statements[0]); 421 | } 422 | 423 | public function testAddingSmallIncrementingID() 424 | { 425 | $blueprint = new Blueprint('users'); 426 | $blueprint->smallIncrements('id'); 427 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 428 | 429 | $this->assertCount(1, $statements); 430 | $this->assertSame('alter table users add ( id number(5,0) not null, constraint users_id_primary primary key ( id ) )', $statements[0]); 431 | } 432 | 433 | public function testAddingID() 434 | { 435 | $blueprint = new Blueprint('users'); 436 | $blueprint->id(); 437 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 438 | 439 | $this->assertCount(1, $statements); 440 | $this->assertSame('alter table users add ( id number(19,0) not null, constraint users_id_primary primary key ( id ) )', $statements[0]); 441 | 442 | $blueprint = new Blueprint('users'); 443 | $blueprint->id('foo'); 444 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 445 | 446 | $this->assertCount(1, $statements); 447 | $this->assertSame('alter table users add ( foo number(19,0) not null, constraint users_foo_primary primary key ( foo ) )', $statements[0]); 448 | } 449 | 450 | public function testAddingForeignID() 451 | { 452 | $blueprint = new Blueprint('users'); 453 | $foreignId = $blueprint->foreignId('foo'); 454 | $blueprint->foreignId('company_id')->constrained(); 455 | $blueprint->foreignId('laravel_idea_id')->constrained(); 456 | $blueprint->foreignId('team_id')->references('id')->on('teams'); 457 | $blueprint->foreignId('team_column_id')->constrained('teams'); 458 | 459 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 460 | 461 | $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignId); 462 | $this->assertCount(9, $statements); 463 | $this->assertSame([ 464 | 'alter table users add ( foo number(19,0) not null )', 465 | 'alter table users add ( company_id number(19,0) not null )', 466 | 'alter table users add constraint users_company_id_foreign foreign key ( company_id ) references companies ( id )', 467 | 'alter table users add ( laravel_idea_id number(19,0) not null )', 468 | 'alter table users add constraint users_laravel_idea_id_foreign foreign key ( laravel_idea_id ) references laravel_ideas ( id )', 469 | 'alter table users add ( team_id number(19,0) not null )', 470 | 'alter table users add constraint users_team_id_foreign foreign key ( team_id ) references teams ( id )', 471 | 'alter table users add ( team_column_id number(19,0) not null )', 472 | 'alter table users add constraint users_team_column_id_foreign foreign key ( team_column_id ) references teams ( id )', 473 | ], $statements); 474 | } 475 | 476 | public function testAddingBigIncrementingID() 477 | { 478 | $blueprint = new Blueprint('users'); 479 | $blueprint->bigIncrements('id'); 480 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 481 | 482 | $this->assertCount(1, $statements); 483 | $this->assertSame('alter table users add ( id number(19,0) not null, constraint users_id_primary primary key ( id ) )', $statements[0]); 484 | } 485 | 486 | public function testAddingString() 487 | { 488 | $blueprint = new Blueprint('users'); 489 | $blueprint->string('foo'); 490 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 491 | 492 | $this->assertEquals(1, count($statements)); 493 | $this->assertEquals('alter table users add ( foo varchar2(255) not null )', $statements[0]); 494 | 495 | $blueprint = new Blueprint('users'); 496 | $blueprint->string('foo', 100); 497 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 498 | 499 | $this->assertEquals(1, count($statements)); 500 | $this->assertEquals('alter table users add ( foo varchar2(100) not null )', $statements[0]); 501 | 502 | $blueprint = new Blueprint('users'); 503 | $blueprint->string('foo', 100)->nullable()->default('bar'); 504 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 505 | 506 | $this->assertEquals(1, count($statements)); 507 | $this->assertEquals('alter table users add ( foo varchar2(100) default \'bar\' null )', $statements[0]); 508 | 509 | $blueprint = new Blueprint('users'); 510 | $blueprint->string('foo', 100)->nullable()->default(new Raw('CURRENT TIMESTAMP')); 511 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 512 | 513 | $this->assertEquals(1, count($statements)); 514 | $this->assertEquals('alter table users add ( foo varchar2(100) default CURRENT TIMESTAMP null )', $statements[0]); 515 | } 516 | 517 | public function testAddingLongText() 518 | { 519 | $blueprint = new Blueprint('users'); 520 | $blueprint->longText('foo'); 521 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 522 | 523 | $this->assertEquals(1, count($statements)); 524 | $this->assertEquals('alter table users add ( foo clob not null )', $statements[0]); 525 | } 526 | 527 | public function testAddingMediumText() 528 | { 529 | $blueprint = new Blueprint('users'); 530 | $blueprint->mediumText('foo'); 531 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 532 | 533 | $this->assertEquals(1, count($statements)); 534 | $this->assertEquals('alter table users add ( foo clob not null )', $statements[0]); 535 | } 536 | 537 | public function testAddingText() 538 | { 539 | $blueprint = new Blueprint('users'); 540 | $blueprint->text('foo'); 541 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 542 | 543 | $this->assertEquals(1, count($statements)); 544 | $this->assertEquals('alter table users add ( foo varchar2(4000) not null )', $statements[0]); 545 | } 546 | 547 | public function testAddingBigInteger() 548 | { 549 | $blueprint = new Blueprint('users'); 550 | $blueprint->bigInteger('foo'); 551 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 552 | 553 | $this->assertEquals(1, count($statements)); 554 | $this->assertEquals('alter table users add ( foo number(19,0) not null )', $statements[0]); 555 | 556 | $blueprint = new Blueprint('users'); 557 | $blueprint->bigInteger('foo', true); 558 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 559 | 560 | $this->assertEquals(1, count($statements)); 561 | $this->assertEquals('alter table users add ( foo number(19,0) not null, constraint users_foo_primary primary key ( foo ) )', $statements[0]); 562 | } 563 | 564 | public function testAddingInteger() 565 | { 566 | $blueprint = new Blueprint('users'); 567 | $blueprint->integer('foo'); 568 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 569 | 570 | $this->assertEquals(1, count($statements)); 571 | $this->assertEquals('alter table users add ( foo number(10,0) not null )', $statements[0]); 572 | 573 | $blueprint = new Blueprint('users'); 574 | $blueprint->integer('foo', true); 575 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 576 | 577 | $this->assertEquals(1, count($statements)); 578 | $this->assertEquals('alter table users add ( foo number(10,0) not null, constraint users_foo_primary primary key ( foo ) )', $statements[0]); 579 | } 580 | 581 | public function testAddingIncrementsWithStartingValues() 582 | { 583 | // calling ->startingValue() should have no effect on the generated sql because it hasn't been implemented 584 | 585 | $blueprint = new Blueprint('users'); 586 | $blueprint->id()->startingValue(1000); 587 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 588 | 589 | $this->assertCount(1, $statements); 590 | $this->assertSame('alter table users add ( id number(19,0) not null, constraint users_id_primary primary key ( id ) )', $statements[0]); 591 | } 592 | 593 | public function testAddingMediumInteger() 594 | { 595 | $blueprint = new Blueprint('users'); 596 | $blueprint->mediumInteger('foo'); 597 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 598 | 599 | $this->assertEquals(1, count($statements)); 600 | $this->assertEquals('alter table users add ( foo number(7,0) not null )', $statements[0]); 601 | } 602 | 603 | public function testAddingSmallInteger() 604 | { 605 | $blueprint = new Blueprint('users'); 606 | $blueprint->smallInteger('foo'); 607 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 608 | 609 | $this->assertEquals(1, count($statements)); 610 | $this->assertEquals('alter table users add ( foo number(5,0) not null )', $statements[0]); 611 | } 612 | 613 | public function testAddingTinyInteger() 614 | { 615 | $blueprint = new Blueprint('users'); 616 | $blueprint->tinyInteger('foo'); 617 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 618 | 619 | $this->assertEquals(1, count($statements)); 620 | $this->assertEquals('alter table users add ( foo number(3,0) not null )', $statements[0]); 621 | } 622 | 623 | public function testAddingFloat() 624 | { 625 | $blueprint = new Blueprint('users'); 626 | $blueprint->float('foo', 5); 627 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 628 | 629 | $this->assertEquals(1, count($statements)); 630 | $this->assertEquals('alter table users add ( foo float(5) not null )', $statements[0]); 631 | } 632 | 633 | public function testAddingDouble() 634 | { 635 | $blueprint = new Blueprint('users'); 636 | $blueprint->double('foo'); 637 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 638 | 639 | $this->assertEquals(1, count($statements)); 640 | $this->assertEquals('alter table users add ( foo double precision not null )', $statements[0]); 641 | } 642 | 643 | public function testAddingDecimal() 644 | { 645 | $blueprint = new Blueprint('users'); 646 | $blueprint->decimal('foo', 5, 2); 647 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 648 | 649 | $this->assertEquals(1, count($statements)); 650 | $this->assertEquals('alter table users add ( foo number(5, 2) not null )', $statements[0]); 651 | } 652 | 653 | public function testAddingBoolean() 654 | { 655 | $blueprint = new Blueprint('users'); 656 | $blueprint->boolean('foo'); 657 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 658 | 659 | $this->assertEquals(1, count($statements)); 660 | $this->assertEquals('alter table users add ( foo char(1) not null )', $statements[0]); 661 | } 662 | 663 | public function testAddingEnum() 664 | { 665 | $blueprint = new Blueprint('users'); 666 | $blueprint->enum('foo', ['bar', 'baz']); 667 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 668 | 669 | $this->assertEquals(1, count($statements)); 670 | $this->assertEquals('alter table users add ( foo varchar2(255) check(foo in (\'bar\', \'baz\')) not null )', $statements[0]); 671 | } 672 | 673 | public function testAddingDate() 674 | { 675 | $blueprint = new Blueprint('users'); 676 | $blueprint->date('foo'); 677 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 678 | 679 | $this->assertEquals(1, count($statements)); 680 | $this->assertEquals('alter table users add ( foo date not null )', $statements[0]); 681 | } 682 | 683 | public function testAddingDateTime() 684 | { 685 | $blueprint = new Blueprint('users'); 686 | $blueprint->dateTime('foo'); 687 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 688 | 689 | $this->assertEquals(1, count($statements)); 690 | $this->assertEquals('alter table users add ( foo date not null )', $statements[0]); 691 | } 692 | 693 | public function testAddingTime() 694 | { 695 | $blueprint = new Blueprint('users'); 696 | $blueprint->time('foo'); 697 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 698 | 699 | $this->assertEquals(1, count($statements)); 700 | $this->assertEquals('alter table users add ( foo date not null )', $statements[0]); 701 | } 702 | 703 | public function testAddingTimeStamp() 704 | { 705 | $blueprint = new Blueprint('users'); 706 | $blueprint->timestamp('foo'); 707 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 708 | 709 | $this->assertEquals(1, count($statements)); 710 | $this->assertEquals('alter table users add ( foo timestamp not null )', $statements[0]); 711 | } 712 | 713 | public function testAddingTimestampWithDefault() 714 | { 715 | $blueprint = new Blueprint('users'); 716 | $blueprint->timestamp('created_at')->default(new Raw('CURRENT_TIMESTAMP')); 717 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 718 | $this->assertCount(1, $statements); 719 | $this->assertSame('alter table users add ( created_at timestamp default CURRENT_TIMESTAMP not null )', $statements[0]); 720 | } 721 | 722 | public function testAddingTimeStamps() 723 | { 724 | $blueprint = new Blueprint('users'); 725 | $blueprint->timestamps(); 726 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 727 | 728 | $this->assertEquals(2, count($statements)); 729 | $this->assertSame([ 730 | 'alter table users add ( created_at timestamp null )', 731 | 'alter table users add ( updated_at timestamp null )', 732 | ], $statements); 733 | } 734 | 735 | public function testAddingBinary() 736 | { 737 | $blueprint = new Blueprint('users'); 738 | $blueprint->binary('foo'); 739 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 740 | 741 | $this->assertEquals(1, count($statements)); 742 | $this->assertEquals('alter table users add ( foo blob not null )', $statements[0]); 743 | } 744 | 745 | public function testAddingComment() 746 | { 747 | // calling ->comment() on a column should have no effect on the generated sql because it hasn't been implemented 748 | 749 | $blueprint = new Blueprint('users'); 750 | $blueprint->string('foo')->comment("Escape ' when using words like it's"); 751 | $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); 752 | 753 | $this->assertCount(1, $statements); 754 | $this->assertSame('alter table users add ( foo varchar2(255) not null )', $statements[0]); 755 | } 756 | 757 | public function testBasicSelectUsingQuotes() 758 | { 759 | $blueprint = new Blueprint('users'); 760 | $blueprint->create(); 761 | $blueprint->increments('id'); 762 | $blueprint->string('email'); 763 | 764 | $conn = $this->getConnection(); 765 | 766 | $statements = $blueprint->toSql($conn, $this->getGrammar(true)); 767 | 768 | $this->assertEquals(1, count($statements)); 769 | $this->assertEquals('create table "users" ( "id" number(10,0) not null, "email" varchar2(255) not null, constraint users_id_primary primary key ( "id" ) )', $statements[0]); 770 | } 771 | 772 | public function testBasicSelectNotUsingQuotes() 773 | { 774 | $blueprint = new Blueprint('users'); 775 | $blueprint->create(); 776 | $blueprint->increments('id'); 777 | $blueprint->string('email'); 778 | 779 | $conn = $this->getConnection(); 780 | 781 | $statements = $blueprint->toSql($conn, $this->getGrammar(false)); 782 | 783 | $this->assertEquals(1, count($statements)); 784 | $this->assertEquals('create table users ( id number(10,0) not null, email varchar2(255) not null, constraint users_id_primary primary key ( id ) )', $statements[0]); 785 | } 786 | 787 | public function testGrammarsAreMacroable() 788 | { 789 | $this->getGrammar()::macro('compileReplace', function () { 790 | return true; 791 | }); 792 | 793 | $c = $this->getGrammar()::compileReplace(); 794 | 795 | $this->assertTrue($c); 796 | } 797 | 798 | protected function getConnection() 799 | { 800 | return m::mock(OracleConnection::class); 801 | } 802 | 803 | public function getGrammar($quoting = false) 804 | { 805 | $connection = m::mock(OracleConnection::class); 806 | $connection->shouldReceive('getConfig')->with('quoting')->andReturn($quoting); 807 | $grammar = new OracleGrammar; 808 | $grammar->setConnection($connection); 809 | 810 | return $grammar; 811 | } 812 | } 813 | -------------------------------------------------------------------------------- /tests/mocks/OCIFunctions.php: -------------------------------------------------------------------------------- 1 | 0, 'message' => '', 'sqltext' => '']; 24 | } 25 | } 26 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_connect")) { 27 | function oci_connect($a = '') 28 | { 29 | global $OCITransactionStatus; 30 | 31 | return $OCITransactionStatus ? 'oci8' : false; 32 | } 33 | } 34 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_pconnect")) { 35 | function oci_pconnect($a = '') 36 | { 37 | global $OCITransactionStatus; 38 | 39 | return $OCITransactionStatus ? 'oci8' : false; 40 | } 41 | } 42 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_close")) { 43 | function oci_close($a = '') 44 | { 45 | global $OCITransactionStatus; 46 | $OCITransactionStatus = false; 47 | } 48 | } 49 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_commit")) { 50 | function oci_commit($a = '') 51 | { 52 | global $OCITransactionStatus; 53 | 54 | return $OCITransactionStatus; 55 | } 56 | } 57 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_rollback")) { 58 | function oci_rollback($a = '') 59 | { 60 | global $OCITransactionStatus; 61 | 62 | return $OCITransactionStatus; 63 | } 64 | } 65 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_parse")) { 66 | function oci_parse($a = '', $b = '') 67 | { 68 | global $OCITransactionStatus; 69 | 70 | return $OCITransactionStatus ? 'oci8 statement' : false; 71 | } 72 | } 73 | 74 | // For testing OCIStatement.php 75 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\get_resource_type")) { 76 | function get_resource_type($a = '') 77 | { 78 | global $OCIStatementStatus; 79 | 80 | return $OCIStatementStatus ? $a : 'invalid'; 81 | } 82 | } 83 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_bind_by_name")) { 84 | function oci_bind_by_name($a = '', $b = '', &$c = '', $d = '', $e = '') 85 | { 86 | global $OCIStatementStatus, $OCIBindChangeStatus, $OCIBindByNameTypeReceived; 87 | 88 | $OCIBindByNameTypeReceived = $e; 89 | 90 | if ($OCIBindChangeStatus) { 91 | $c = 'oci_bind_by_name'; 92 | } 93 | 94 | return $OCIStatementStatus; 95 | } 96 | } 97 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_num_fields")) { 98 | function oci_num_fields($a = '') 99 | { 100 | return 1; 101 | } 102 | } 103 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_free_statement")) { 104 | function oci_free_statement($a = '') 105 | { 106 | global $OCIStatementStatus; 107 | $OCIStatementStatus = false; 108 | } 109 | } 110 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_execute")) { 111 | function oci_execute($a = '', $b = '') 112 | { 113 | global $OCIExecuteStatus; 114 | 115 | return $OCIExecuteStatus; 116 | } 117 | } 118 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_fetch_assoc")) { 119 | function oci_fetch_assoc($a = '') 120 | { 121 | global $OCIFetchStatus; 122 | 123 | return $OCIFetchStatus ? ['FNAME' => 'Test', 'LNAME' => 'Testerson', 'EMAIL' => 'tester@testing.com'] : false; 124 | } 125 | } 126 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_fetch_row")) { 127 | function oci_fetch_row($a = '') 128 | { 129 | global $OCIFetchStatus; 130 | 131 | return $OCIFetchStatus ? [0 => 'Test', 1 => 'Testerson', 2 => 'tester@testing.com'] : false; 132 | } 133 | } 134 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_fetch_array")) { 135 | function oci_fetch_array($a = '') 136 | { 137 | global $OCIFetchStatus; 138 | 139 | return $OCIFetchStatus ? [0 => 'Test', 1 => 'Testerson', 2 => 'tester@testing.com', 'FNAME' => 'Test', 'LNAME' => 'Testerson', 'EMAIL' => 'tester@testing.com'] : false; 140 | } 141 | } 142 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_fetch_all")) { 143 | function oci_fetch_all($a = '', &$b = '') 144 | { 145 | global $OCIFetchAllReturnEmpty; 146 | 147 | $b = $OCIFetchAllReturnEmpty 148 | ? [] 149 | : [['FNAME' => 'Test', 'LNAME' => 'Testerson', 'EMAIL' => 'tester@testing.com']]; 150 | 151 | return count($b); 152 | } 153 | } 154 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_field_type")) { 155 | function oci_field_type($a, $b) 156 | { 157 | return 1; 158 | } 159 | } 160 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_field_type_raw")) { 161 | function oci_field_type_raw($a, $b) 162 | { 163 | return 1; 164 | } 165 | } 166 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_field_name")) { 167 | function oci_field_name($a, $b) 168 | { 169 | return 1; 170 | } 171 | } 172 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_field_size")) { 173 | function oci_field_size($a, $b) 174 | { 175 | return 1; 176 | } 177 | } 178 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_field_precision")) { 179 | function oci_field_precision($a, $b) 180 | { 181 | return 1; 182 | } 183 | } 184 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_num_rows")) { 185 | function oci_num_rows($a) 186 | { 187 | return 1; 188 | } 189 | } 190 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_client_version")) { 191 | function oci_client_version() 192 | { 193 | return 'Test Return'; 194 | } 195 | } 196 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_free_descriptor")) { 197 | function oci_free_descriptor($a) 198 | { 199 | return $a; 200 | } 201 | } 202 | if (! function_exists("Jfelder\OracleDB\OCI_PDO\oci_internal_debug")) { 203 | function oci_internal_debug($a) 204 | { 205 | global $OCITransactionStatus; 206 | $OCITransactionStatus = $a; 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /tests/mocks/OCIMocks.php: -------------------------------------------------------------------------------- 1 | attributes = $driver_options + $this->attributes; 12 | $this->conn = 'oci8'; 13 | } 14 | 15 | public function __destruct() {} 16 | } 17 | } 18 | 19 | if (! class_exists('TestOCIStatementStub')) { 20 | class TestOCIStatementStub extends OCIStatement 21 | { 22 | public function __construct($stmt, $conn, $sql, $options) 23 | { 24 | $this->stmt = $stmt; 25 | $this->conn = $conn; 26 | $this->sql = $sql; 27 | $this->attributes = $options; 28 | } 29 | 30 | public function __destruct() {} 31 | } 32 | } 33 | 34 | if (! class_exists('ProcessorTestOCIStub')) { 35 | class ProcessorTestOCIStub extends OCI 36 | { 37 | public function __construct() {} 38 | 39 | public function __destruct() {} 40 | 41 | public function prepare(string $query, array $options = []): OCIStatement|false {} 42 | } 43 | } 44 | 45 | if (! class_exists('ProcessorTestOCIStatementStub')) { 46 | class ProcessorTestOCIStatementStub extends OCIStatement 47 | { 48 | public function __construct() {} 49 | 50 | public function __destruct() {} 51 | 52 | public function bindValue(string|int $param, mixed $value, int $type = PDO::PARAM_STR): bool {} 53 | 54 | public function bindParam(string|int $param, mixed &$var, int $type = PDO::PARAM_STR, int $maxLength = 0, mixed $driverOptions = null): bool {} 55 | 56 | public function execute(?array $params = null): bool {} 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/mocks/PDOMocks.php: -------------------------------------------------------------------------------- 1 |