Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
D
doctrine-dbal
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Tomáš Trávníček
doctrine-dbal
Commits
1e4ac55e
Unverified
Commit
1e4ac55e
authored
Jan 12, 2020
by
Sergei Morozov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Made the OCI8Statement class final
parent
d0fe5727
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
240 additions
and
223 deletions
+240
-223
UPGRADE.md
UPGRADE.md
+6
-1
ConvertPositionalToNamedPlaceholders.php
...DBAL/Driver/OCI8/ConvertPositionalToNamedPlaceholders.php
+159
-0
NonTerminatedStringLiteral.php
lib/Doctrine/DBAL/Driver/OCI8/NonTerminatedStringLiteral.php
+20
-0
OCI8Statement.php
lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php
+4
-158
ConvertPositionalToNamedPlaceholdersTest.php
.../Driver/OCI8/ConvertPositionalToNamedPlaceholdersTest.php
+51
-11
OCI8StatementTest.php
tests/Doctrine/Tests/DBAL/Driver/OCI8/OCI8StatementTest.php
+0
-53
No files found.
UPGRADE.md
View file @
1e4ac55e
# Upgrade to 3.0
# Upgrade to 3.0
## BC BREAK: `OCI8Statement::convertPositionalToNamedPlaceholders()` is removed.
The
`OCI8Statement::convertPositionalToNamedPlaceholders()`
method has been extracted to an internal utility class.
## BC BREAK: Dropped handling of one-based numeric arrays of parameters in `Statement::execute()`
## BC BREAK: Dropped handling of one-based numeric arrays of parameters in `Statement::execute()`
The statement implementations no longer detect whether
`$params`
is a zero- or one-based array. A zero-based numeric array is expected.
The statement implementations no longer detect whether
`$params`
is a zero- or one-based array. A zero-based numeric array is expected.
...
@@ -56,9 +60,10 @@ Table columns are no longer indexed by column name. Use the `name` attribute of
...
@@ -56,9 +60,10 @@ Table columns are no longer indexed by column name. Use the `name` attribute of
-
Class
`Doctrine\DBAL\Sharding\SQLAzure\SQLAzureFederationsSynchronizer`
was made final.
-
Class
`Doctrine\DBAL\Sharding\SQLAzure\SQLAzureFederationsSynchronizer`
was made final.
-
Class
`Doctrine\DBAL\Sharding\PoolingShardManager`
was made final.
-
Class
`Doctrine\DBAL\Sharding\PoolingShardManager`
was made final.
-
Class
`Doctrine\DBAL\Id\TableGeneratorSchemaVisitor`
was made final.
-
Class
`Doctrine\DBAL\Id\TableGeneratorSchemaVisitor`
was made final.
-
Class
`Doctrine\DBAL\Driver\OCI8\Driver`
was made final.
-
Class
`Doctrine\DBAL\Driver\Mysqli\Driver`
was made final.
-
Class
`Doctrine\DBAL\Driver\Mysqli\Driver`
was made final.
-
Class
`Doctrine\DBAL\Driver\Mysqli\MysqliStatement`
was made final.
-
Class
`Doctrine\DBAL\Driver\Mysqli\MysqliStatement`
was made final.
-
Class
`Doctrine\DBAL\Driver\OCI8\Driver`
was made final.
-
Class
`Doctrine\DBAL\Driver\OCI8\OCI8Statement`
was made final.
-
Class
`Doctrine\DBAL\Driver\PDOSqlsrv\Driver`
was made final.
-
Class
`Doctrine\DBAL\Driver\PDOSqlsrv\Driver`
was made final.
-
Class
`Doctrine\DBAL\Driver\PDOSqlsrv\Statement`
was made final.
-
Class
`Doctrine\DBAL\Driver\PDOSqlsrv\Statement`
was made final.
-
Class
`Doctrine\DBAL\Driver\PDOMySql\Driver`
was made final.
-
Class
`Doctrine\DBAL\Driver\PDOMySql\Driver`
was made final.
...
...
lib/Doctrine/DBAL/Driver/OCI8/ConvertPositionalToNamedPlaceholders.php
0 → 100644
View file @
1e4ac55e
<?php
declare
(
strict_types
=
1
);
namespace
Doctrine\DBAL\Driver\OCI8
;
use
const
PREG_OFFSET_CAPTURE
;
use
function
count
;
use
function
implode
;
use
function
preg_match
;
use
function
preg_quote
;
use
function
substr
;
/**
* Converts positional (?) into named placeholders (:param<num>).
*
* Oracle does not support positional parameters, hence this method converts all
* positional parameters into artificially named parameters. Note that this conversion
* is not perfect. All question marks (?) in the original statement are treated as
* placeholders and converted to a named parameter.
*
* @internal This class is not covered by the backward compatibility promise
*/
final
class
ConvertPositionalToNamedPlaceholders
{
/**
* @param string $statement The SQL statement to convert.
*
* @return mixed[] [0] => the statement value (string), [1] => the paramMap value (array).
*
* @throws OCI8Exception
*/
public
function
__invoke
(
string
$statement
)
:
array
{
$fragmentOffset
=
$tokenOffset
=
0
;
$fragments
=
$paramMap
=
[];
$currentLiteralDelimiter
=
null
;
do
{
if
(
$currentLiteralDelimiter
===
null
)
{
$result
=
$this
->
findPlaceholderOrOpeningQuote
(
$statement
,
$tokenOffset
,
$fragmentOffset
,
$fragments
,
$currentLiteralDelimiter
,
$paramMap
);
}
else
{
$result
=
$this
->
findClosingQuote
(
$statement
,
$tokenOffset
,
$currentLiteralDelimiter
);
}
}
while
(
$result
);
if
(
$currentLiteralDelimiter
)
{
throw
NonTerminatedStringLiteral
::
new
(
$tokenOffset
-
1
);
}
$fragments
[]
=
substr
(
$statement
,
$fragmentOffset
);
$statement
=
implode
(
''
,
$fragments
);
return
[
$statement
,
$paramMap
];
}
/**
* Finds next placeholder or opening quote.
*
* @param string $statement The SQL statement to parse
* @param int $tokenOffset The offset to start searching from
* @param int $fragmentOffset The offset to build the next fragment from
* @param string[] $fragments Fragments of the original statement not containing placeholders
* @param string|null $currentLiteralDelimiter The delimiter of the current string literal
* or NULL if not currently in a literal
* @param string[] $paramMap Mapping of the original parameter positions to their named replacements
*
* @return bool Whether the token was found
*/
private
function
findPlaceholderOrOpeningQuote
(
string
$statement
,
int
&
$tokenOffset
,
int
&
$fragmentOffset
,
array
&
$fragments
,
?
string
&
$currentLiteralDelimiter
,
array
&
$paramMap
)
:
bool
{
$token
=
$this
->
findToken
(
$statement
,
$tokenOffset
,
'/[?\'"]/'
);
if
(
!
$token
)
{
return
false
;
}
if
(
$token
===
'?'
)
{
$position
=
count
(
$paramMap
)
+
1
;
$param
=
':param'
.
$position
;
$fragments
[]
=
substr
(
$statement
,
$fragmentOffset
,
$tokenOffset
-
$fragmentOffset
);
$fragments
[]
=
$param
;
$paramMap
[
$position
]
=
$param
;
$tokenOffset
+=
1
;
$fragmentOffset
=
$tokenOffset
;
return
true
;
}
$currentLiteralDelimiter
=
$token
;
++
$tokenOffset
;
return
true
;
}
/**
* Finds closing quote
*
* @param string $statement The SQL statement to parse
* @param int $tokenOffset The offset to start searching from
* @param string $currentLiteralDelimiter The delimiter of the current string literal
*
* @return bool Whether the token was found
*/
private
function
findClosingQuote
(
string
$statement
,
int
&
$tokenOffset
,
string
&
$currentLiteralDelimiter
)
:
bool
{
$token
=
$this
->
findToken
(
$statement
,
$tokenOffset
,
'/'
.
preg_quote
(
$currentLiteralDelimiter
,
'/'
)
.
'/'
);
if
(
!
$token
)
{
return
false
;
}
$currentLiteralDelimiter
=
null
;
++
$tokenOffset
;
return
true
;
}
/**
* Finds the token described by regex starting from the given offset. Updates the offset with the position
* where the token was found.
*
* @param string $statement The SQL statement to parse
* @param int $offset The offset to start searching from
* @param string $regex The regex containing token pattern
*
* @return string|null Token or NULL if not found
*/
private
function
findToken
(
string
$statement
,
int
&
$offset
,
string
$regex
)
:
?
string
{
if
(
preg_match
(
$regex
,
$statement
,
$matches
,
PREG_OFFSET_CAPTURE
,
$offset
))
{
$offset
=
$matches
[
0
][
1
];
return
$matches
[
0
][
0
];
}
return
null
;
}
}
lib/Doctrine/DBAL/Driver/OCI8/NonTerminatedStringLiteral.php
0 → 100644
View file @
1e4ac55e
<?php
declare
(
strict_types
=
1
);
namespace
Doctrine\DBAL\Driver\OCI8
;
use
function
sprintf
;
final
class
NonTerminatedStringLiteral
extends
OCI8Exception
{
public
static
function
new
(
int
$offset
)
:
self
{
return
new
self
(
sprintf
(
'The statement contains non-terminated string literal starting at offset %d.'
,
$offset
)
);
}
}
lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php
View file @
1e4ac55e
...
@@ -22,12 +22,10 @@ use const OCI_NUM;
...
@@ -22,12 +22,10 @@ use const OCI_NUM;
use
const
OCI_RETURN_LOBS
;
use
const
OCI_RETURN_LOBS
;
use
const
OCI_RETURN_NULLS
;
use
const
OCI_RETURN_NULLS
;
use
const
OCI_TEMP_BLOB
;
use
const
OCI_TEMP_BLOB
;
use
const
PREG_OFFSET_CAPTURE
;
use
const
SQLT_CHR
;
use
const
SQLT_CHR
;
use
function
array_key_exists
;
use
function
array_key_exists
;
use
function
assert
;
use
function
assert
;
use
function
count
;
use
function
count
;
use
function
implode
;
use
function
is_int
;
use
function
is_int
;
use
function
is_resource
;
use
function
is_resource
;
use
function
oci_bind_by_name
;
use
function
oci_bind_by_name
;
...
@@ -41,15 +39,12 @@ use function oci_new_descriptor;
...
@@ -41,15 +39,12 @@ use function oci_new_descriptor;
use
function
oci_num_fields
;
use
function
oci_num_fields
;
use
function
oci_num_rows
;
use
function
oci_num_rows
;
use
function
oci_parse
;
use
function
oci_parse
;
use
function
preg_match
;
use
function
preg_quote
;
use
function
sprintf
;
use
function
sprintf
;
use
function
substr
;
/**
/**
* The OCI8 implementation of the Statement interface.
* The OCI8 implementation of the Statement interface.
*/
*/
class
OCI8Statement
implements
IteratorAggregate
,
Statement
final
class
OCI8Statement
implements
IteratorAggregate
,
Statement
{
{
/** @var resource */
/** @var resource */
protected
$_dbh
;
protected
$_dbh
;
...
@@ -95,10 +90,12 @@ class OCI8Statement implements IteratorAggregate, Statement
...
@@ -95,10 +90,12 @@ class OCI8Statement implements IteratorAggregate, Statement
*
*
* @param resource $dbh The connection handle.
* @param resource $dbh The connection handle.
* @param string $query The SQL query.
* @param string $query The SQL query.
*
* @throws OCI8Exception
*/
*/
public
function
__construct
(
$dbh
,
string
$query
,
OCI8Connection
$conn
)
public
function
__construct
(
$dbh
,
string
$query
,
OCI8Connection
$conn
)
{
{
[
$query
,
$paramMap
]
=
self
::
convertPositionalToNamedPlaceholders
(
$query
);
[
$query
,
$paramMap
]
=
(
new
ConvertPositionalToNamedPlaceholders
())
(
$query
);
$stmt
=
oci_parse
(
$dbh
,
$query
);
$stmt
=
oci_parse
(
$dbh
,
$query
);
assert
(
is_resource
(
$stmt
));
assert
(
is_resource
(
$stmt
));
...
@@ -109,157 +106,6 @@ class OCI8Statement implements IteratorAggregate, Statement
...
@@ -109,157 +106,6 @@ class OCI8Statement implements IteratorAggregate, Statement
$this
->
_conn
=
$conn
;
$this
->
_conn
=
$conn
;
}
}
/**
* Converts positional (?) into named placeholders (:param<num>).
*
* Oracle does not support positional parameters, hence this method converts all
* positional parameters into artificially named parameters. Note that this conversion
* is not perfect. All question marks (?) in the original statement are treated as
* placeholders and converted to a named parameter.
*
* The algorithm uses a state machine with two possible states: InLiteral and NotInLiteral.
* Question marks inside literal strings are therefore handled correctly by this method.
* This comes at a cost, the whole sql statement has to be looped over.
*
* @param string $statement The SQL statement to convert.
*
* @return mixed[] [0] => the statement value (string), [1] => the paramMap value (array).
*
* @throws OCI8Exception
*
* @todo extract into utility class in Doctrine\DBAL\Util namespace
* @todo review and test for lost spaces. we experienced missing spaces with oci8 in some sql statements.
*/
public
static
function
convertPositionalToNamedPlaceholders
(
string
$statement
)
:
array
{
$fragmentOffset
=
$tokenOffset
=
0
;
$fragments
=
$paramMap
=
[];
$currentLiteralDelimiter
=
null
;
do
{
if
(
$currentLiteralDelimiter
===
null
)
{
$result
=
self
::
findPlaceholderOrOpeningQuote
(
$statement
,
$tokenOffset
,
$fragmentOffset
,
$fragments
,
$currentLiteralDelimiter
,
$paramMap
);
}
else
{
$result
=
self
::
findClosingQuote
(
$statement
,
$tokenOffset
,
$currentLiteralDelimiter
);
}
}
while
(
$result
);
if
(
$currentLiteralDelimiter
)
{
throw
new
OCI8Exception
(
sprintf
(
'The statement contains non-terminated string literal starting at offset %d.'
,
$tokenOffset
-
1
));
}
$fragments
[]
=
substr
(
$statement
,
$fragmentOffset
);
$statement
=
implode
(
''
,
$fragments
);
return
[
$statement
,
$paramMap
];
}
/**
* Finds next placeholder or opening quote.
*
* @param string $statement The SQL statement to parse
* @param int $tokenOffset The offset to start searching from
* @param int $fragmentOffset The offset to build the next fragment from
* @param string[] $fragments Fragments of the original statement not containing placeholders
* @param string|null $currentLiteralDelimiter The delimiter of the current string literal
* or NULL if not currently in a literal
* @param string[] $paramMap Mapping of the original parameter positions to their named replacements
*
* @return bool Whether the token was found
*/
private
static
function
findPlaceholderOrOpeningQuote
(
string
$statement
,
int
&
$tokenOffset
,
int
&
$fragmentOffset
,
array
&
$fragments
,
?
string
&
$currentLiteralDelimiter
,
array
&
$paramMap
)
:
bool
{
$token
=
self
::
findToken
(
$statement
,
$tokenOffset
,
'/[?\'"]/'
);
if
(
!
$token
)
{
return
false
;
}
if
(
$token
===
'?'
)
{
$position
=
count
(
$paramMap
)
+
1
;
$param
=
':param'
.
$position
;
$fragments
[]
=
substr
(
$statement
,
$fragmentOffset
,
$tokenOffset
-
$fragmentOffset
);
$fragments
[]
=
$param
;
$paramMap
[
$position
]
=
$param
;
$tokenOffset
+=
1
;
$fragmentOffset
=
$tokenOffset
;
return
true
;
}
$currentLiteralDelimiter
=
$token
;
++
$tokenOffset
;
return
true
;
}
/**
* Finds closing quote
*
* @param string $statement The SQL statement to parse
* @param int $tokenOffset The offset to start searching from
* @param string $currentLiteralDelimiter The delimiter of the current string literal
*
* @return bool Whether the token was found
*/
private
static
function
findClosingQuote
(
string
$statement
,
int
&
$tokenOffset
,
string
&
$currentLiteralDelimiter
)
:
bool
{
$token
=
self
::
findToken
(
$statement
,
$tokenOffset
,
'/'
.
preg_quote
(
$currentLiteralDelimiter
,
'/'
)
.
'/'
);
if
(
!
$token
)
{
return
false
;
}
$currentLiteralDelimiter
=
null
;
++
$tokenOffset
;
return
true
;
}
/**
* Finds the token described by regex starting from the given offset. Updates the offset with the position
* where the token was found.
*
* @param string $statement The SQL statement to parse
* @param int $offset The offset to start searching from
* @param string $regex The regex containing token pattern
*
* @return string|null Token or NULL if not found
*/
private
static
function
findToken
(
string
$statement
,
int
&
$offset
,
string
$regex
)
:
?
string
{
if
(
preg_match
(
$regex
,
$statement
,
$matches
,
PREG_OFFSET_CAPTURE
,
$offset
))
{
$offset
=
$matches
[
0
][
1
];
return
$matches
[
0
][
0
];
}
return
null
;
}
/**
/**
* {@inheritdoc}
* {@inheritdoc}
*/
*/
...
...
tests/Doctrine/Tests/DBAL/
Util
Test.php
→
tests/Doctrine/Tests/DBAL/
Driver/OCI8/ConvertPositionalToNamedPlaceholders
Test.php
View file @
1e4ac55e
...
@@ -2,17 +2,39 @@
...
@@ -2,17 +2,39 @@
declare
(
strict_types
=
1
);
declare
(
strict_types
=
1
);
namespace
Doctrine\Tests\DBAL
;
namespace
Doctrine\Tests\DBAL
\Driver\OCI8
;
use
Doctrine\DBAL\Driver\OCI8\OCI8Statement
;
use
Doctrine\DBAL\Driver\OCI8\ConvertPositionalToNamedPlaceholders
;
use
Doctrine\DBAL\Driver\OCI8\OCI8Exception
;
use
Doctrine\Tests\DbalTestCase
;
use
Doctrine\Tests\DbalTestCase
;
class
Util
Test
extends
DbalTestCase
class
ConvertPositionalToNamedPlaceholders
Test
extends
DbalTestCase
{
{
/** @var ConvertPositionalToNamedPlaceholders */
private
$convertPositionalToNamedPlaceholders
;
protected
function
setUp
()
:
void
{
$this
->
convertPositionalToNamedPlaceholders
=
new
ConvertPositionalToNamedPlaceholders
();
}
/**
* @param mixed[] $expectedOutputParamsMap
*
* @dataProvider positionalToNamedPlaceholdersProvider
*/
public
function
testConvertPositionalToNamedParameters
(
string
$inputSQL
,
string
$expectedOutputSQL
,
array
$expectedOutputParamsMap
)
:
void
{
[
$statement
,
$params
]
=
(
$this
->
convertPositionalToNamedPlaceholders
)(
$inputSQL
);
self
::
assertEquals
(
$expectedOutputSQL
,
$statement
);
self
::
assertEquals
(
$expectedOutputParamsMap
,
$params
);
}
/**
/**
* @return mixed[][]
* @return mixed[][]
*/
*/
public
static
function
dataConvertPositionalToNamedParameters
()
:
iterable
public
static
function
positionalToNamedPlaceholdersProvider
()
:
iterable
{
{
return
[
return
[
[
[
...
@@ -69,15 +91,33 @@ class UtilTest extends DbalTestCase
...
@@ -69,15 +91,33 @@ class UtilTest extends DbalTestCase
}
}
/**
/**
* @param mixed[] $expectedOutputParamsMap
* @dataProvider nonTerminatedLiteralProvider
*
* @dataProvider dataConvertPositionalToNamedParameters
*/
*/
public
function
testConvert
PositionalToNamedParameters
(
string
$inputSQL
,
string
$expectedOutputSQL
,
array
$expectedOutputParamsMa
p
)
:
void
public
function
testConvert
NonTerminatedLiteral
(
string
$sql
,
string
$expectedExceptionMessageRegEx
p
)
:
void
{
{
[
$statement
,
$params
]
=
OCI8Statement
::
convertPositionalToNamedPlaceholders
(
$inputSQL
);
$this
->
expectException
(
OCI8Exception
::
class
);
$this
->
expectExceptionMessageMatches
(
$expectedExceptionMessageRegExp
);
(
$this
->
convertPositionalToNamedPlaceholders
)(
$sql
);
}
self
::
assertEquals
(
$expectedOutputSQL
,
$statement
);
/**
self
::
assertEquals
(
$expectedOutputParamsMap
,
$params
);
* @return array<string, array<int, mixed>>
*/
public
static
function
nonTerminatedLiteralProvider
()
:
iterable
{
return
[
'no-matching-quote'
=>
[
"SELECT 'literal FROM DUAL"
,
'/offset 7./'
,
],
'no-matching-double-quote'
=>
[
'SELECT 1 "COL1 FROM DUAL'
,
'/offset 9./'
,
],
'incorrect-escaping-syntax'
=>
[
"SELECT 'quoted
\\
'string' FROM DUAL"
,
'/offset 23./'
,
],
];
}
}
}
}
tests/Doctrine/Tests/DBAL/Driver/OCI8/OCI8StatementTest.php
deleted
100644 → 0
View file @
d0fe5727
<?php
declare
(
strict_types
=
1
);
namespace
Doctrine\Tests\DBAL\Driver\OCI8
;
use
Doctrine\DBAL\Driver\OCI8\OCI8Exception
;
use
Doctrine\DBAL\Driver\OCI8\OCI8Statement
;
use
Doctrine\Tests\DbalTestCase
;
use
function
extension_loaded
;
class
OCI8StatementTest
extends
DbalTestCase
{
protected
function
setUp
()
:
void
{
if
(
!
extension_loaded
(
'oci8'
))
{
$this
->
markTestSkipped
(
'oci8 is not installed.'
);
}
parent
::
setUp
();
}
/**
* @dataProvider nonTerminatedLiteralProvider
*/
public
function
testConvertNonTerminatedLiteral
(
string
$sql
,
string
$message
)
:
void
{
$this
->
expectException
(
OCI8Exception
::
class
);
$this
->
expectExceptionMessageRegExp
(
$message
);
OCI8Statement
::
convertPositionalToNamedPlaceholders
(
$sql
);
}
/**
* @return array<string, array<int, mixed>>
*/
public
static
function
nonTerminatedLiteralProvider
()
:
iterable
{
return
[
'no-matching-quote'
=>
[
"SELECT 'literal FROM DUAL"
,
'/offset 7./'
,
],
'no-matching-double-quote'
=>
[
'SELECT 1 "COL1 FROM DUAL'
,
'/offset 9./'
,
],
'incorrect-escaping-syntax'
=>
[
"SELECT 'quoted
\\
'string' FROM DUAL"
,
'/offset 23./'
,
],
];
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment