├── .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 |
6 |
7 |
8 |
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 |