Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Q
question2answer
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
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
outils
question2answer
Commits
09c048cc
Commit
09c048cc
authored
Jul 20, 2020
by
Scott
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Extract methods to DbQueryHelper class
parent
e5d49898
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
210 additions
and
162 deletions
+210
-162
qa-db.php
qa-include/qa-db.php
+3
-4
DbConnection.php
qa-src/Database/DbConnection.php
+12
-142
DbQueryHelper.php
qa-src/Database/DbQueryHelper.php
+176
-0
DbResult.php
qa-src/Database/DbResult.php
+3
-0
DbQueryHelperTest.php
qa-tests/src/Database/DbQueryHelperTest.php
+16
-16
No files found.
qa-include/qa-db.php
View file @
09c048cc
...
...
@@ -152,7 +152,7 @@ function qa_db_escape_string($string)
/**
* Return $argument escaped for MySQL. Add quotes around it if $alwaysquote is true or it's not numeric.
* If $argument is an array, return a comma-separated list of escaped elements, with or without $arraybrackets.
* @deprecated 1.9.0
* @deprecated 1.9.0
Use DbQueryHelper->expandParameters() instead.
* @param mixed|null $argument
* @param bool $alwaysquote
* @param bool $arraybrackets
...
...
@@ -185,7 +185,7 @@ function qa_db_argument_to_mysql($argument, $alwaysquote, $arraybrackets = false
/**
* Return the full name (with prefix) of database table $rawname, usually if it used after a ^ symbol.
* @deprecated 1.9.0 Use Db
Connection
->addTablePrefix() instead.
* @deprecated 1.9.0 Use Db
QueryHelper
->addTablePrefix() instead.
* @param string $rawname
* @return string
*/
...
...
@@ -215,7 +215,7 @@ function qa_db_prefix_callback($matches)
* it is converted recursively into comma-separated list). Each element in $arguments is escaped.
* $ is replaced by the argument in quotes (even if it's a number), # only adds quotes if the argument is non-numeric.
* It's important to use $ when matching a textual column since MySQL won't use indexes to compare text against numbers.
* @deprecated 1.9.0 Use Db
Connection->applyTableSub
() instead.
* @deprecated 1.9.0 Use Db
QueryHelper->expandParameters
() instead.
* @param string $query
* @param array $arguments
* @return mixed
...
...
@@ -294,7 +294,6 @@ function qa_db_num_rows($result)
if
(
$result
instanceof
\Q2A\Database\DbResult
)
return
$result
->
affectedRows
();
// backwards compatibility
if
(
$result
instanceof
mysqli_result
)
return
$result
->
num_rows
;
...
...
qa-src/Database/DbConnection.php
View file @
09c048cc
...
...
@@ -22,7 +22,11 @@ use PDO;
use
PDOException
;
use
PDOStatement
;
use
Q2A\Database\Exceptions\SelectSpecException
;
use
Q2A\Storage\CacheFactory
;
/**
* Core database class. Handles all SQL queries within Q2A.
*/
class
DbConnection
{
/** @var PDO */
...
...
@@ -74,6 +78,7 @@ class DbConnection
/**
* Indicates to the Q2A database layer that database connections are permitted from this point forwards (before
* this point, some plugins may not have had a chance to override some database access functions).
* @return void
*/
public
function
allowConnect
()
{
...
...
@@ -90,8 +95,8 @@ class DbConnection
}
/**
* Connect to the Q2A database, optionally install the $failHandler (and call it if necessary).
Uses PDO as of Q2A
* 1.9.
* Connect to the Q2A database, optionally install the $failHandler (and call it if necessary).
*
Uses PDO as of Q2A
1.9.
* @param string $failHandler
* @return mixed|void
*/
...
...
@@ -178,11 +183,10 @@ class DbConnection
$this
->
connect
();
}
$query
=
$this
->
applyTableSub
(
$query
);
// handle old-style placeholders
$query
=
str_replace
([
'#'
,
'$'
],
'?'
,
$query
);
// handle IN queries
list
(
$query
,
$params
)
=
$this
->
expandQueryParameters
(
$query
,
$params
);
$helper
=
new
DbQueryHelper
;
$query
=
$helper
->
applyTableSub
(
$query
);
// handle WHERE..IN and INSERT..VALUES queries
list
(
$query
,
$params
)
=
$helper
->
expandParameters
(
$query
,
$params
);
if
(
substr_count
(
$query
,
'?'
)
!=
count
(
$params
))
{
throw
new
SelectSpecException
(
'The number of parameters and placeholders do not match'
);
...
...
@@ -297,7 +301,7 @@ class DbConnection
{
// check for cached results
if
(
isset
(
$selectspec
[
'caching'
]))
{
$cacheDriver
=
\Q2A\Storage\
CacheFactory
::
getCacheDriver
();
$cacheDriver
=
CacheFactory
::
getCacheDriver
();
$cacheKey
=
'query:'
.
$selectspec
[
'caching'
][
'key'
];
if
(
$cacheDriver
->
isEnabled
())
{
...
...
@@ -506,121 +510,6 @@ class DbConnection
}
/**
* Substitute ^ in a SQL query with the configured table prefix.
* @param string $query
* @return string
*/
public
function
applyTableSub
(
$query
)
{
return
preg_replace_callback
(
'/\^([A-Za-z_0-9]+)/'
,
function
(
$matches
)
{
return
$this
->
addTablePrefix
(
$matches
[
1
]);
},
$query
);
}
/**
* Return the full name (with prefix) of a database table identifier.
* @param string $rawName
* @return string
*/
public
function
addTablePrefix
(
$rawName
)
{
$prefix
=
QA_MYSQL_TABLE_PREFIX
;
if
(
defined
(
'QA_MYSQL_USERS_PREFIX'
))
{
switch
(
strtolower
(
$rawName
))
{
case
'users'
:
case
'userlogins'
:
case
'userprofile'
:
case
'userfields'
:
case
'messages'
:
case
'cookies'
:
case
'blobs'
:
case
'cache'
:
case
'userlogins_ibfk_1'
:
// also special cases for constraint names
case
'userprofile_ibfk_1'
:
$prefix
=
QA_MYSQL_USERS_PREFIX
;
break
;
}
}
return
$prefix
.
$rawName
;
}
/**
* Substitute single '?' in a SQL query with multiple '?' for array parameters, and flatten parameter list accordingly.
* @param string $query
* @param array $params
* @return array Query and flattened parameter list
* @throws SelectSpecException
*/
public
function
expandQueryParameters
(
$query
,
array
$params
=
[])
{
$numParams
=
count
(
$params
);
$explodedQuery
=
explode
(
'?'
,
$query
);
if
(
$numParams
!==
count
(
$explodedQuery
)
-
1
)
{
throw
new
SelectSpecException
(
'The number of parameters and placeholders do not match'
);
}
if
(
empty
(
$params
))
{
return
[
$query
,
$params
];
}
$outQuery
=
''
;
$outParams
=
[];
// use array_values to ensure consistent indexing
foreach
(
array_values
(
$params
)
as
$i
=>
$param
)
{
$outQuery
.=
$explodedQuery
[
$i
];
if
(
is_array
(
$param
))
{
$subArray
=
array_values
(
$param
);
if
(
is_array
(
$subArray
[
0
]))
{
// INSERT..VALUES query for inserting multiple rows
$subArrayCount
=
count
(
$subArray
[
0
]);
foreach
(
$subArray
as
$subArrayParam
)
{
// If the first subparam is an array, the rest of the parameter groups should have the same
// amount of elements, i.e. the output should be '(?, ?), (?, ?)' rather than '(?), (?, ?)'
if
(
!
is_array
(
$subArrayParam
)
||
count
(
$subArrayParam
)
!==
$subArrayCount
)
{
throw
new
SelectSpecException
(
'All parameter groups must have the same amount of parameters'
);
}
$outParams
=
array_merge
(
$outParams
,
$subArrayParam
);
}
$outQuery
.=
$this
->
repeatStringWithSeparators
(
'('
.
$this
->
repeatStringWithSeparators
(
'?'
,
$subArrayCount
)
.
')'
,
count
(
$subArray
)
);
}
else
{
// WHERE..IN query
$outQuery
.=
$this
->
repeatStringWithSeparators
(
'?'
,
count
(
$subArray
));
$outParams
=
array_merge
(
$outParams
,
$subArray
);
}
}
else
{
// standard query
$outQuery
.=
'?'
;
$outParams
[]
=
$param
;
}
}
$outQuery
.=
$explodedQuery
[
$numParams
];
return
[
$outQuery
,
$outParams
];
}
/**
* Repeat a string a given amount of times separating each of the instances with ', '.
* @param string $string
* @param int $amount
* @return string
*/
private
function
repeatStringWithSeparators
(
$string
,
$amount
)
{
return
$amount
==
1
?
$string
:
str_repeat
(
$string
.
', '
,
$amount
-
1
)
.
$string
;
}
/**
* Return the value of the auto-increment column for the last inserted row.
* @return string
*/
...
...
@@ -647,23 +536,4 @@ class DbConnection
{
return
$this
->
updateCountsSuspended
<=
0
;
}
/**
* Flatten a two-level or three-level array into a one-level array.
* @param mixed $elements Input elements which can be one-level deep arrays
* @return array
*/
private
function
flattenArray
(
$elements
)
{
$result
=
array
();
foreach
(
$elements
as
$element
)
{
if
(
is_array
(
$element
))
{
$result
=
array_merge
(
$result
,
$this
->
flattenArray
(
$element
));
}
else
{
$result
[]
=
$element
;
}
}
return
$result
;
}
}
qa-src/Database/DbQueryHelper.php
0 → 100644
View file @
09c048cc
<?php
/*
Question2Answer by Gideon Greenspan and contributors
http://www.question2answer.org/
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
More about this license: http://www.question2answer.org/license.php
*/
namespace
Q2A\Database
;
use
Q2A\Database\Exceptions\SelectSpecException
;
/**
* Various database utility functions.
*/
class
DbQueryHelper
{
/**
* Substitute ^ in a SQL query with the configured table prefix.
* @param string $query
* @return string
*/
public
function
applyTableSub
(
$query
)
{
return
preg_replace_callback
(
'/\^([A-Za-z_0-9]+)/'
,
function
(
$matches
)
{
return
$this
->
addTablePrefix
(
$matches
[
1
]);
},
$query
);
}
/**
* Return the full name (with prefix) of a database table identifier.
* @param string $rawName
* @return string
*/
public
function
addTablePrefix
(
$rawName
)
{
$prefix
=
QA_MYSQL_TABLE_PREFIX
;
if
(
defined
(
'QA_MYSQL_USERS_PREFIX'
))
{
switch
(
strtolower
(
$rawName
))
{
case
'users'
:
case
'userlogins'
:
case
'userprofile'
:
case
'userfields'
:
case
'messages'
:
case
'cookies'
:
case
'blobs'
:
case
'cache'
:
case
'userlogins_ibfk_1'
:
// also special cases for constraint names
case
'userprofile_ibfk_1'
:
$prefix
=
QA_MYSQL_USERS_PREFIX
;
break
;
}
}
return
$prefix
.
$rawName
;
}
/**
* Substitute single '?' in a SQL query with multiple '?' for array parameters, and flatten parameter list accordingly.
* @param string $query
* @param array $params
* @return array Query and flattened parameter list
* @throws SelectSpecException
*/
public
function
expandParameters
(
$query
,
array
$params
=
[])
{
// handle old-style placeholders
$query
=
str_replace
([
'#'
,
'$'
],
'?'
,
$query
);
$numParams
=
count
(
$params
);
$explodedQuery
=
explode
(
'?'
,
$query
);
if
(
$numParams
!==
count
(
$explodedQuery
)
-
1
)
{
throw
new
SelectSpecException
(
'The number of parameters and placeholders do not match'
);
}
if
(
empty
(
$params
))
{
return
[
$query
,
$params
];
}
$outQuery
=
''
;
$outParams
=
[];
// use array_values to ensure consistent indexing
foreach
(
array_values
(
$params
)
as
$i
=>
$param
)
{
$outQuery
.=
$explodedQuery
[
$i
];
if
(
is_array
(
$param
))
{
$subArray
=
array_values
(
$param
);
if
(
is_array
(
$subArray
[
0
]))
{
$this
->
handleInsertValuesQuery
(
$subArray
,
$outQuery
,
$outParams
);
}
else
{
$this
->
handleWhereInQuery
(
$subArray
,
$outQuery
,
$outParams
);
}
}
else
{
$this
->
handleStandardQuery
(
$param
,
$outQuery
,
$outParams
);
}
}
$outQuery
.=
$explodedQuery
[
$numParams
];
return
[
$outQuery
,
$outParams
];
}
/**
* Basic query with individual parameters.
* @param array $param
* @param string $outQuery
* @param array $outParams
*/
private
function
handleStandardQuery
(
$param
,
&
$outQuery
,
array
&
$outParams
)
{
$outQuery
.=
'?'
;
$outParams
[]
=
$param
;
}
/**
* WHERE..IN query.
* @param array $params
* @param string $outQuery
* @param array $outParams
*/
private
function
handleWhereInQuery
(
array
$params
,
&
$outQuery
,
array
&
$outParams
)
{
$outQuery
.=
$this
->
repeatStringWithSeparators
(
'?'
,
count
(
$params
));
$outParams
=
array_merge
(
$outParams
,
$params
);
}
/**
* INSERT INTO..VALUES query for inserting multiple rows.
* If the first subparam is an array, the rest of the parameter groups should have the same
* amount of elements, i.e. the output should be '(?, ?), (?, ?)' rather than '(?), (?, ?)'.
* @param array $subArray
* @param string $outQuery
* @param array $outParams
*/
private
function
handleInsertValuesQuery
(
array
$subArray
,
&
$outQuery
,
array
&
$outParams
)
{
$subArrayCount
=
count
(
$subArray
[
0
]);
foreach
(
$subArray
as
$subArrayParam
)
{
if
(
!
is_array
(
$subArrayParam
)
||
count
(
$subArrayParam
)
!==
$subArrayCount
)
{
throw
new
SelectSpecException
(
'All parameter groups must have the same amount of parameters'
);
}
$outParams
=
array_merge
(
$outParams
,
$subArrayParam
);
}
$outQuery
.=
$this
->
repeatStringWithSeparators
(
'('
.
$this
->
repeatStringWithSeparators
(
'?'
,
$subArrayCount
)
.
')'
,
count
(
$subArray
)
);
}
/**
* Repeat a string a given amount of times separating each of the instances with ', '.
* @param string $string
* @param int $amount
* @return string
*/
private
function
repeatStringWithSeparators
(
$string
,
$amount
)
{
return
$amount
==
1
?
$string
:
str_repeat
(
$string
.
', '
,
$amount
-
1
)
.
$string
;
}
}
qa-src/Database/DbResult.php
View file @
09c048cc
...
...
@@ -22,6 +22,9 @@ use PDO;
use
PDOStatement
;
use
Q2A\Database\Exceptions\ReadingFromEmptyResultException
;
/**
* Thin wrapper around the PDOStatement class which returns results in a variety of formats.
*/
class
DbResult
{
private
$stmt
;
...
...
qa-tests/src/Database/Db
Connection
Test.php
→
qa-tests/src/Database/Db
QueryHelper
Test.php
View file @
09c048cc
<?php
use
Q2A\Database\Db
Connection
;
use
Q2A\Database\Db
QueryHelper
;
class
Db
Connection
Test
extends
PHPUnit_Framework_TestCase
class
Db
QueryHelper
Test
extends
PHPUnit_Framework_TestCase
{
/** @var Db
Connection
*/
private
$
dbConnection
;
/** @var Db
QueryHelper
*/
private
$
helper
;
protected
function
setUp
()
{
$this
->
dbConnection
=
new
DbConnection
();
$this
->
helper
=
new
DbQueryHelper
();
}
public
function
test__expand
Query
Parameters_success
()
public
function
test__expandParameters_success
()
{
$result
=
$this
->
dbConnection
->
expandQuery
Parameters
(
'SELECT * FROM table WHERE field = 1'
,
[]);
$result
=
$this
->
helper
->
expand
Parameters
(
'SELECT * FROM table WHERE field = 1'
,
[]);
$expected
=
[
'SELECT * FROM table WHERE field = 1'
,
[]];
$this
->
assertSame
(
$expected
,
$result
);
$result
=
$this
->
dbConnection
->
expandQuery
Parameters
(
'SELECT * FROM table WHERE field = ?'
,
[
1
]);
$result
=
$this
->
helper
->
expand
Parameters
(
'SELECT * FROM table WHERE field = ?'
,
[
1
]);
$expected
=
[
'SELECT * FROM table WHERE field = ?'
,
[
1
]];
$this
->
assertSame
(
$expected
,
$result
);
$result
=
$this
->
dbConnection
->
expandQuery
Parameters
(
'SELECT * FROM table WHERE field IN (?)'
,
[[
1
]]);
$result
=
$this
->
helper
->
expand
Parameters
(
'SELECT * FROM table WHERE field IN (?)'
,
[[
1
]]);
$expected
=
[
'SELECT * FROM table WHERE field IN (?)'
,
[
1
]];
$this
->
assertSame
(
$expected
,
$result
);
$result
=
$this
->
dbConnection
->
expandQuery
Parameters
(
'SELECT * FROM table WHERE field IN (?)'
,
[[
1
,
2
]]);
$result
=
$this
->
helper
->
expand
Parameters
(
'SELECT * FROM table WHERE field IN (?)'
,
[[
1
,
2
]]);
$expected
=
[
'SELECT * FROM table WHERE field IN (?, ?)'
,
[
1
,
2
]];
$this
->
assertSame
(
$expected
,
$result
);
$result
=
$this
->
dbConnection
->
expandQuery
Parameters
(
'INSERT INTO table(field) VALUES ?'
,
[[
[
1
]
]]);
$result
=
$this
->
helper
->
expand
Parameters
(
'INSERT INTO table(field) VALUES ?'
,
[[
[
1
]
]]);
$expected
=
[
'INSERT INTO table(field) VALUES (?)'
,
[
1
]];
$this
->
assertSame
(
$expected
,
$result
);
$result
=
$this
->
dbConnection
->
expandQuery
Parameters
(
'INSERT INTO table(field) VALUES ?'
,
[[
[
1
],
[
2
]
]]);
$result
=
$this
->
helper
->
expand
Parameters
(
'INSERT INTO table(field) VALUES ?'
,
[[
[
1
],
[
2
]
]]);
$expected
=
[
'INSERT INTO table(field) VALUES (?), (?)'
,
[
1
,
2
]];
$this
->
assertSame
(
$expected
,
$result
);
$result
=
$this
->
dbConnection
->
expandQuery
Parameters
(
'INSERT INTO table(field1, field2) VALUES ?'
,
[[
[
1
,
2
]
]]);
$result
=
$this
->
helper
->
expand
Parameters
(
'INSERT INTO table(field1, field2) VALUES ?'
,
[[
[
1
,
2
]
]]);
$expected
=
[
'INSERT INTO table(field1, field2) VALUES (?, ?)'
,
[
1
,
2
]];
$this
->
assertSame
(
$expected
,
$result
);
$result
=
$this
->
dbConnection
->
expandQuery
Parameters
(
'INSERT INTO table(field1, field2) VALUES ?'
,
[[
[
1
,
2
],
[
3
,
4
]
]]);
$result
=
$this
->
helper
->
expand
Parameters
(
'INSERT INTO table(field1, field2) VALUES ?'
,
[[
[
1
,
2
],
[
3
,
4
]
]]);
$expected
=
[
'INSERT INTO table(field1, field2) VALUES (?, ?), (?, ?)'
,
[
1
,
2
,
3
,
4
]];
$this
->
assertSame
(
$expected
,
$result
);
}
public
function
test__expand
Query
Parameters_incorrect_groups_error
()
public
function
test__expandParameters_incorrect_groups_error
()
{
$this
->
setExpectedException
(
'Q2A\Database\Exceptions\SelectSpecException'
);
$this
->
dbConnection
->
expandQuery
Parameters
(
'INSERT INTO table(field1, field2) VALUES ?'
,
[[
[
1
,
2
],
[
3
]
]]);
$this
->
helper
->
expand
Parameters
(
'INSERT INTO table(field1, field2) VALUES ?'
,
[[
[
1
,
2
],
[
3
]
]]);
}
}
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