Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F92815104
AphrontBaseMySQLDatabaseConnection.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Sat, Nov 23, 22:16
Size
10 KB
Mime Type
text/x-php
Expires
Mon, Nov 25, 22:16 (1 d, 23 h)
Engine
blob
Format
Raw Data
Handle
22521494
Attached To
rPHU libphutil
AphrontBaseMySQLDatabaseConnection.php
View Options
<?php
abstract
class
AphrontBaseMySQLDatabaseConnection
extends
AphrontDatabaseConnection
{
private
$configuration
;
private
$connection
;
private
$connectionPool
=
array
();
private
$lastResult
;
private
$nextError
;
abstract
protected
function
connect
();
abstract
protected
function
rawQuery
(
$raw_query
);
abstract
protected
function
rawQueries
(
array
$raw_queries
);
abstract
protected
function
fetchAssoc
(
$result
);
abstract
protected
function
getErrorCode
(
$connection
);
abstract
protected
function
getErrorDescription
(
$connection
);
abstract
protected
function
closeConnection
();
abstract
protected
function
freeResult
(
$result
);
public
function
__construct
(
array
$configuration
)
{
$this
->
configuration
=
$configuration
;
}
public
function
__clone
()
{
$this
->
establishConnection
();
}
public
function
openConnection
()
{
$this
->
requireConnection
();
}
public
function
close
()
{
if
(
$this
->
lastResult
)
{
$this
->
lastResult
=
null
;
}
if
(
$this
->
connection
)
{
$this
->
closeConnection
();
$this
->
connection
=
null
;
}
}
public
function
escapeColumnName
(
$name
)
{
return
'`'
.
str_replace
(
'`'
,
'``'
,
$name
).
'`'
;
}
public
function
escapeMultilineComment
(
$comment
)
{
// These can either terminate a comment, confuse the hell out of the parser,
// make MySQL execute the comment as a query, or, in the case of semicolon,
// are quasi-dangerous because the semicolon could turn a broken query into
// a working query plus an ignored query.
static
$map
=
array
(
'--'
=>
'(DOUBLEDASH)'
,
'*/'
=>
'(STARSLASH)'
,
'//'
=>
'(SLASHSLASH)'
,
'#'
=>
'(HASH)'
,
'!'
=>
'(BANG)'
,
';'
=>
'(SEMICOLON)'
,
);
$comment
=
str_replace
(
array_keys
(
$map
),
array_values
(
$map
),
$comment
);
// For good measure, kill anything else that isn't a nice printable
// character.
$comment
=
preg_replace
(
'/[^
\x
20-
\x
7F]+/'
,
' '
,
$comment
);
return
'/* '
.
$comment
.
' */'
;
}
public
function
escapeStringForLikeClause
(
$value
)
{
$value
=
addcslashes
(
$value
,
'
\%
_'
);
$value
=
$this
->
escapeUTF8String
(
$value
);
return
$value
;
}
protected
function
getConfiguration
(
$key
,
$default
=
null
)
{
return
idx
(
$this
->
configuration
,
$key
,
$default
);
}
private
function
establishConnection
()
{
$host
=
$this
->
getConfiguration
(
'host'
);
$database
=
$this
->
getConfiguration
(
'database'
);
$profiler
=
PhutilServiceProfiler
::
getInstance
();
$call_id
=
$profiler
->
beginServiceCall
(
array
(
'type'
=>
'connect'
,
'host'
=>
$host
,
'database'
=>
$database
,
));
$retries
=
max
(
1
,
$this
->
getConfiguration
(
'retries'
,
3
));
while
(
$retries
--)
{
try
{
$conn
=
$this
->
connect
();
$profiler
->
endServiceCall
(
$call_id
,
array
());
break
;
}
catch
(
AphrontQueryException
$ex
)
{
if
(
$retries
&&
$ex
->
getCode
()
==
2003
)
{
$class
=
get_class
(
$ex
);
$message
=
$ex
->
getMessage
();
phlog
(
pht
(
'Retrying (%d) after %s: %s'
,
$retries
,
$class
,
$message
));
}
else
{
$profiler
->
endServiceCall
(
$call_id
,
array
());
throw
$ex
;
}
}
}
$this
->
connection
=
$conn
;
}
protected
function
requireConnection
()
{
if
(!
$this
->
connection
)
{
if
(
$this
->
connectionPool
)
{
$this
->
connection
=
array_pop
(
$this
->
connectionPool
);
}
else
{
$this
->
establishConnection
();
}
}
return
$this
->
connection
;
}
protected
function
beginAsyncConnection
()
{
$connection
=
$this
->
requireConnection
();
$this
->
connection
=
null
;
return
$connection
;
}
protected
function
endAsyncConnection
(
$connection
)
{
if
(
$this
->
connection
)
{
$this
->
connectionPool
[]
=
$this
->
connection
;
}
$this
->
connection
=
$connection
;
}
public
function
selectAllResults
()
{
$result
=
array
();
$res
=
$this
->
lastResult
;
if
(
$res
==
null
)
{
throw
new
Exception
(
pht
(
'No query result to fetch from!'
));
}
while
((
$row
=
$this
->
fetchAssoc
(
$res
)))
{
$result
[]
=
$row
;
}
return
$result
;
}
public
function
executeQuery
(
PhutilQueryString
$query
)
{
$display_query
=
$query
->
getMaskedString
();
$raw_query
=
$query
->
getUnmaskedString
();
$this
->
lastResult
=
null
;
$retries
=
max
(
1
,
$this
->
getConfiguration
(
'retries'
,
3
));
while
(
$retries
--)
{
try
{
$this
->
requireConnection
();
$is_write
=
$this
->
checkWrite
(
$raw_query
);
$profiler
=
PhutilServiceProfiler
::
getInstance
();
$call_id
=
$profiler
->
beginServiceCall
(
array
(
'type'
=>
'query'
,
'config'
=>
$this
->
configuration
,
'query'
=>
$display_query
,
'write'
=>
$is_write
,
));
$result
=
$this
->
rawQuery
(
$raw_query
);
$profiler
->
endServiceCall
(
$call_id
,
array
());
if
(
$this
->
nextError
)
{
$result
=
null
;
}
if
(
$result
)
{
$this
->
lastResult
=
$result
;
break
;
}
$this
->
throwQueryException
(
$this
->
connection
);
}
catch
(
AphrontConnectionLostQueryException
$ex
)
{
$can_retry
=
(
$retries
>
0
);
if
(
$this
->
isInsideTransaction
())
{
// Zero out the transaction state to prevent a second exception
// ("program exited with open transaction") from being thrown, since
// we're about to throw a more relevant/useful one instead.
$state
=
$this
->
getTransactionState
();
while
(
$state
->
getDepth
())
{
$state
->
decreaseDepth
();
}
$can_retry
=
false
;
}
if
(
$this
->
isHoldingAnyLock
())
{
$this
->
forgetAllLocks
();
$can_retry
=
false
;
}
$this
->
close
();
if
(!
$can_retry
)
{
throw
$ex
;
}
}
}
}
public
function
executeRawQueries
(
array
$raw_queries
)
{
if
(!
$raw_queries
)
{
return
array
();
}
$is_write
=
false
;
foreach
(
$raw_queries
as
$key
=>
$raw_query
)
{
$is_write
=
$is_write
||
$this
->
checkWrite
(
$raw_query
);
$raw_queries
[
$key
]
=
rtrim
(
$raw_query
,
"
\r\n\t
;"
);
}
$profiler
=
PhutilServiceProfiler
::
getInstance
();
$call_id
=
$profiler
->
beginServiceCall
(
array
(
'type'
=>
'multi-query'
,
'config'
=>
$this
->
configuration
,
'queries'
=>
$raw_queries
,
'write'
=>
$is_write
,
));
$results
=
$this
->
rawQueries
(
$raw_queries
);
$profiler
->
endServiceCall
(
$call_id
,
array
());
return
$results
;
}
protected
function
processResult
(
$result
)
{
if
(!
$result
)
{
try
{
$this
->
throwQueryException
(
$this
->
requireConnection
());
}
catch
(
Exception
$ex
)
{
return
$ex
;
}
}
else
if
(
is_bool
(
$result
))
{
return
$this
->
getAffectedRows
();
}
$rows
=
array
();
while
((
$row
=
$this
->
fetchAssoc
(
$result
)))
{
$rows
[]
=
$row
;
}
$this
->
freeResult
(
$result
);
return
$rows
;
}
protected
function
checkWrite
(
$raw_query
)
{
// NOTE: The opening "(" allows queries in the form of:
//
// (SELECT ...) UNION (SELECT ...)
$is_write
=
!
preg_match
(
'/^[(]*(SELECT|SHOW|EXPLAIN)
\s
/'
,
$raw_query
);
if
(
$is_write
)
{
if
(
$this
->
getReadOnly
())
{
throw
new
Exception
(
pht
(
'Attempting to issue a write query on a read-only '
.
'connection (to database "%s")!'
,
$this
->
getConfiguration
(
'database'
)));
}
AphrontWriteGuard
::
willWrite
();
return
true
;
}
return
false
;
}
protected
function
throwQueryException
(
$connection
)
{
if
(
$this
->
nextError
)
{
$errno
=
$this
->
nextError
;
$error
=
pht
(
'Simulated error.'
);
$this
->
nextError
=
null
;
}
else
{
$errno
=
$this
->
getErrorCode
(
$connection
);
$error
=
$this
->
getErrorDescription
(
$connection
);
}
$this
->
throwQueryCodeException
(
$errno
,
$error
);
}
private
function
throwCommonException
(
$errno
,
$error
)
{
$message
=
pht
(
'#%d: %s'
,
$errno
,
$error
);
switch
(
$errno
)
{
case
2013
:
// Connection Dropped
throw
new
AphrontConnectionLostQueryException
(
$message
);
case
2006
:
// Gone Away
$more
=
pht
(
'This error may occur if your configured MySQL "wait_timeout" or '
.
'"max_allowed_packet" values are too small. This may also indicate '
.
'that something used the MySQL "KILL <process>" command to kill '
.
'the connection running the query.'
);
throw
new
AphrontConnectionLostQueryException
(
"{$message}
\n\n
{$more}"
);
case
1213
:
// Deadlock
throw
new
AphrontDeadlockQueryException
(
$message
);
case
1205
:
// Lock wait timeout exceeded
throw
new
AphrontLockTimeoutQueryException
(
$message
);
case
1062
:
// Duplicate Key
// NOTE: In some versions of MySQL we get a key name back here, but
// older versions just give us a key index ("key 2") so it's not
// portable to parse the key out of the error and attach it to the
// exception.
throw
new
AphrontDuplicateKeyQueryException
(
$message
);
case
1044
:
// Access denied to database
case
1142
:
// Access denied to table
case
1143
:
// Access denied to column
case
1227
:
// Access denied (e.g., no SUPER for SHOW SLAVE STATUS).
throw
new
AphrontAccessDeniedQueryException
(
$message
);
case
1045
:
// Access denied (auth)
throw
new
AphrontInvalidCredentialsQueryException
(
$message
);
case
1146
:
// No such table
case
1049
:
// No such database
case
1054
:
// Unknown column "..." in field list
throw
new
AphrontSchemaQueryException
(
$message
);
}
// TODO: 1064 is syntax error, and quite terrible in production.
return
null
;
}
protected
function
throwConnectionException
(
$errno
,
$error
,
$user
,
$host
)
{
$this
->
throwCommonException
(
$errno
,
$error
);
$message
=
pht
(
'Attempt to connect to %s@%s failed with error #%d: %s.'
,
$user
,
$host
,
$errno
,
$error
);
throw
new
AphrontConnectionQueryException
(
$message
,
$errno
);
}
protected
function
throwQueryCodeException
(
$errno
,
$error
)
{
$this
->
throwCommonException
(
$errno
,
$error
);
$message
=
pht
(
'#%d: %s'
,
$errno
,
$error
);
throw
new
AphrontQueryException
(
$message
,
$errno
);
}
/**
* Force the next query to fail with a simulated error. This should be used
* ONLY for unit tests.
*/
public
function
simulateErrorOnNextQuery
(
$error
)
{
$this
->
nextError
=
$error
;
return
$this
;
}
/**
* Check inserts for characters outside of the BMP. Even with the strictest
* settings, MySQL will silently truncate data when it encounters these, which
* can lead to data loss and security problems.
*/
protected
function
validateUTF8String
(
$string
)
{
if
(
phutil_is_utf8
(
$string
))
{
return
;
}
throw
new
AphrontCharacterSetQueryException
(
pht
(
'Attempting to construct a query using a non-utf8 string when '
.
'utf8 is expected. Use the `%%B` conversion to escape binary '
.
'strings data.'
));
}
}
Event Timeline
Log In to Comment