Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3789
DatabaseMSPDO.php
Public
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Authored By
•
bzimport
Nov 21 2014, 9:38 PM
2014-11-21 21:38:10 (UTC+0)
Size
46 KB
Referenced Files
None
Subscribers
None
DatabaseMSPDO.php
View Options
<?php
/**
* @defgroup Database DatabaseMSPDO
*
* @file
* @ingroup Database
* This file deals with Microsoft SQL interface functions
* and query specifics/optimisations using PDO
* @package MediaWiki
*/
/******************************************************************************/
class
PDOdbMetaData
{
public
$name
=
''
;
public
$table
=
''
;
public
$def
=
''
;
public
$max_length
=
1
;
public
$not_null
=
1
;
public
$primary_key
=
0
;
public
$unique_key
=
0
;
public
$multiple_key
=
1
;
public
$blob
=
0
;
public
$type
=
0
;
public
$unsigned
=
0
;
public
$zerofill
=
0
;
public
$non_unique
=
1
;
}
/**
* Specialization of the Database abstraction object
* @package MediaWiki
*/
class
DatabasePDO
extends
Database
{
#------------------------------------------------------------------------------
# Variables
#------------------------------------------------------------------------------
var
$replaceQuote
=
"
\\
'"
;
/// string to use to replace quotes
#------------------------------------------------------------------------------
# Accessors
#------------------------------------------------------------------------------
# These optionally set a variable and return the previous state
/**
* Returns true if this database uses timestamps rather than integers
*/
function
realTimestamps
()
{
return
true
;
}
#------------------------------------------------------------------------------
# Other functions
#------------------------------------------------------------------------------
/**
* Constructor.
* @param $server String: database server host
* @param $user String: database user name
* @param $password String: database user password
* @param $dbName String: database name
* @param $failFunction
* @param $flags
* @param $tablePrefix String: database table prefixes. By default use the prefix gave in LocalSettings.php
*/
function
__construct
(
$server
=
false
,
$user
=
false
,
$password
=
false
,
$dbName
=
false
,
$failFunction
=
false
,
$flags
=
0
,
$tablePrefix
=
'get from global'
)
{
if
(
$server
==
"localhost"
)
$server
=
"(local)"
;
parent
::
__construct
(
$server
,
$user
,
$password
,
$dbName
,
$failFunction
,
$flags
,
$tablePrefix
);
$this
->
limitOffset
=
0
;
}
/**
* Same as new Database( ... ), kept for backward compatibility
* @param $server String: database server host
* @param $user String: database user name
* @param $password String: database user password
* @param $dbName String: database name
* @param failFunction
* @param $flags
*/
static
function
newFromParams
(
$server
,
$user
,
$password
,
$dbName
,
$failFunction
=
false
,
$flags
=
0
)
{
if
(
$server
==
"localhost"
)
$server
=
"(local)"
;
return
new
DatabasePDO
(
$server
,
$user
,
$password
,
$dbName
,
$failFunction
,
$flags
);
}
/**
* Usually aborts on failure
* If the failFunction is set to a non-zero integer, returns success
* @param $server String: database server host
* @param $user String: database user name
* @param $password String: database user password
* @param $dbName String: database name
*/
function
open
(
$server
,
$user
,
$password
,
$dbName
)
{
global
$wguname
;
wfProfileIn
(
__METHOD__
);
if
(
$server
==
"localhost"
)
$server
=
"(local)"
;
$this
->
close
();
$this
->
mServer
=
$server
;
$this
->
mUser
=
$user
;
$this
->
mPassword
=
$password
;
$this
->
mDBname
=
$dbName
;
$success
=
false
;
wfDebug
(
"Opening db $server
\n
"
);
wfProfileIn
(
"dbconnect-$server"
);
$this
->
mConn
=
false
;
$max
=
3
;
$dsn
=
"sqlsrv:Server=$server;Database=$dbName"
;
for
(
$i
=
0
;
$i
<
$max
&&
!
$this
->
mConn
;
$i
++
)
{
if
(
$i
>
1
)
{
usleep
(
1000
);
}
if
(
$this
->
mFlags
&
DBO_PERSISTENT
)
{
try
{
$this
->
mConn
=
new
PDO
(
$dsn
,
$user
,
$password
);
$this
->
mConn
->
setAttribute
(
PDO
::
ATTR_ERRMODE
,
PDO
::
ERRMODE_EXCEPTION
);
//$this->mConn->setAttribute( PDO::SQLSRV_ATTR_ENCODING, PDO::SQLSRV_ENCODING_SYSTEM );
$this
->
mConn
->
setAttribute
(
PDO
::
SQLSRV_ATTR_ENCODING
,
PDO
::
SQLSRV_ENCODING_UTF8
);
$this
->
mConn
->
setAttribute
(
PDO
::
ATTR_CASE
,
PDO
::
CASE_LOWER
);
}
catch
(
exception
$pe
)
{
throw
new
DBConnectionError
(
$this
,
$pe
->
getMessage
());
}
}
else
{
# Create a new connection...
try
{
$this
->
mConn
=
new
PDO
(
$dsn
,
$user
,
$password
);
$this
->
mConn
->
setAttribute
(
PDO
::
ATTR_ERRMODE
,
PDO
::
ERRMODE_EXCEPTION
);
}
catch
(
exception
$pe
)
{
throw
new
DBConnectionError
(
$this
,
$pe
->
getMessage
());
}
}
if
(
$this
->
mConn
===
false
)
{
$iplus
=
$i
+
1
;
wfLogDBError
(
"Connect loop error $iplus of $max ($server): "
.
" - "
.
"
\n
"
);
}
}
$this
->
mConn
->
exec
(
"SET NOCOUNT OFF"
);
$this
->
mConn
->
setAttribute
(
PDO
::
ATTR_DEFAULT_FETCH_MODE
,
PDO
::
FETCH_BOTH
);
wfProfileOut
(
"dbconnect-$server"
);
$success
=
(
bool
)
$this
->
mConn
;
if
(
$success
)
{
// Setting the character set is handled differently from MySQL
}
else
{
$this
->
reportConnectionError
();
}
$this
->
mOpened
=
$success
;
wfProfileOut
(
__METHOD__
);
return
$success
;
}
/**@}}*/
public
function
resetConnection
()
{
return
$this
->
open
(
$this
->
mServer
,
$this
->
mUser
,
$this
->
mPassword
,
$this
->
mDBname
);
}
/**
* Closes a database connection.
* if it is open : commits any open transactions
*
* @return Bool operation success. true if already closed.
*/
function
close
()
{
wfDebug
(
"Closing database
\n
"
);
$this
->
mOpened
=
false
;
if
(
$this
->
mConn
)
{
if
(
$this
->
trxLevel
()
)
{
$this
->
immediateCommit
();
}
$this
->
mConn
=
null
;
return
true
;
}
else
{
return
true
;
}
}
/**
* Usually aborts on failure
* If errors are explicitly ignored, returns success
*/
function
query
(
$sql
,
$fname
=
''
,
$tempIgnore
=
false
)
{
global
$wgProfiling
;
if
(
!
strncmp
(
"LOCK"
,
$sql
,
4
)
)
return
;
if
(
!
strncmp
(
"UNLOCK"
,
$sql
,
6
)
)
return
;
$limitLoc
=
strrpos
(
$sql
,
"LIMIT"
);
// TODO: Clean up this LIMIT --> SELECT TOP nonsense
if
(
$limitLoc
!=
FALSE
&&
$limitLoc
>
strlen
(
$sql
)
-
15
)
{
$foundLimit
=
preg_match
(
'/LIMIT (
\d
*)/'
,
$sql
,
$m
);
if
(
$foundLimit
)
{
$limitNum
=
$m
[
1
];
//substr( $sql, $limitLoc + 5 );
$sql
=
preg_replace
(
'/LIMIT
\d
*/'
,
''
,
$sql
);
}
//$numLen = strlen($limitNum);
$topLoc
=
strpos
(
$sql
,
"SELECT TOP"
);
if
(
$topLoc
===
FALSE
)
{
$selectPos
=
strpos
(
$sql
,
"SELECT"
);
if
(
$selectPos
===
FALSE
)
$endReplace
=
0
;
else
if
(
$selectPos
==
0
)
$endReplace
=
6
;
else
if
(
$selectPos
==
1
)
$endReplace
=
0
;
$sql
=
substr_replace
(
$sql
,
"SELECT TOP $limitNum "
,
0
,
$endReplace
);
}
}
if
(
$wgProfiling
)
{
# generalizeSQL will probably cut down the query to reasonable
# logging size most of the time. The substr is really just a sanity check.
# Who's been wasting my precious column space? -- TS
#$profName = 'query: ' . $fname . ' ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
if
(
is_null
(
$this
->
getLBInfo
(
'master'
)
)
)
{
$queryProf
=
'query: '
.
substr
(
Database
::
generalizeSQL
(
$sql
),
0
,
255
);
$totalProf
=
'Database::query'
;
}
else
{
$queryProf
=
'query-m: '
.
substr
(
Database
::
generalizeSQL
(
$sql
),
0
,
255
);
$totalProf
=
'Database::query-master'
;
}
wfProfileIn
(
$totalProf
);
wfProfileIn
(
$queryProf
);
}
$this
->
mLastQuery
=
$sql
;
# Add a comment for easy SHOW PROCESSLIST interpretation
if
(
$fname
)
{
$commentedSql
=
preg_replace
(
'/
\s
/'
,
" /* $fname */ "
,
$sql
,
1
);
}
else
{
$commentedSql
=
$sql
;
}
# If DBO_TRX is set, start a transaction
if
(
(
$this
->
mFlags
&
DBO_TRX
)
&&
!
$this
->
trxLevel
()
&&
$sql
!=
'BEGIN'
&&
$sql
!=
'COMMIT'
&&
$sql
!=
'ROLLBACK'
)
{
$this
->
begin
();
}
if
(
$this
->
debug
()
)
{
$sqlx
=
substr
(
$commentedSql
,
0
,
500
);
$sqlx
=
strtr
(
$sqlx
,
"
\t\n
"
,
' '
);
wfDebug
(
"SQL: $sqlx
\n
"
);
}
# Do the query and handle errors
$ret
=
$this
->
doQuery
(
$commentedSql
);
# Try reconnecting if the connection was lost
if
(
false
===
$ret
&&
(
$this
->
lastErrno
()
==
2013
||
$this
->
lastErrno
()
==
2006
)
)
{
# Transaction is gone, like it or not
$this
->
mTrxLevel
=
0
;
wfDebug
(
"Connection lost, reconnecting...
\n
"
);
if
(
$this
->
ping
()
)
{
wfDebug
(
"Reconnected
\n
"
);
$ret
=
$this
->
doQuery
(
$commentedSql
);
}
else
{
wfDebug
(
"Failed
\n
"
);
}
}
if
(
false
===
$ret
)
{
$this
->
reportQueryError
(
$this
->
lastError
(),
$this
->
lastErrno
(),
$sql
,
$fname
,
$tempIgnore
);
}
if
(
isset
(
$wgProfiler
)
)
{
wfProfileOut
(
$queryProf
);
wfProfileOut
(
$totalProf
);
}
return
$this
->
resultObject
(
$ret
);
}
/**
* The DBMS-dependent part of query()
* @param $sql String: SQL query.
* @return Result object to feed to fetchObject, fetchRow, ...; or false on failure
* @private
*/
function
doQuery
(
$sql
)
{
$ret
=
false
;
try
{
$pdoStmt
=
$this
->
mConn
->
prepare
(
$sql
,
array
(
PDO
::
ATTR_CURSOR
=>
PDO
::
CURSOR_SCROLL
)
);
$this
->
lastPDOStatement
=
$pdoStmt
;
$ok
=
$pdoStmt
->
execute
();
if
(
$ok
)
{
$ret
=
$pdoStmt
;
}
}
catch
(
exception
$e
)
{
//adodb_backtrace($e->gettrace());
}
return
$ret
;
}
/**#@+
* @param mixed $res A SQL result
*/
/**
* Free a result object
*/
function
freeResult
(
$res
)
{
if
(
$res
instanceof
ResultWrapper
)
{
$res
=
$res
->
result
;
}
$res
->
closeCursor
();
}
/**
* Fetch the next row from the given result object, in object form.
* Fields can be retrieved with $row->fieldname, with fields acting like
* member variables.
*
* @param $res SQL result object as returned from Database::query(), etc.
* @return MySQL row object
* @throws DBUnexpectedError Thrown if the database returns an error
*/
function
fetchObject
(
$res
)
{
if
(
$res
instanceof
ResultWrapper
)
{
$res
=
$res
->
result
;
}
if
(!
$res
)
return
false
;
if
(
$this
->
limitOffset
!=
0
)
{
$row
=
$res
->
fetch
(
PDO
::
FETCH_OBJ
,
PDO
::
FETCH_ORI_ABS
,
$this
->
limitOffset
);
$this
->
limitOffset
=
0
;
}
else
{
$row
=
$res
->
fetchObject
();
}
if
(
$this
->
lastErrno
()
)
{
throw
new
DBUnexpectedError
(
$this
,
'Error in fetchObject(): '
.
htmlspecialchars
(
$this
->
lastError
()
)
);
}
return
$row
;
}
function
fetchNextObject
(
$res
)
{
if
(
$res
instanceof
ResultWrapper
)
{
$res
=
$res
->
result
;
}
return
$this
->
fetchObject
(
$res
);
}
/**
* Fetch the next row from the given result object, in associative array
* form. Fields are retrieved with $row['fieldname'].
*
* @param $res SQL result object as returned from Database::query(), etc.
* @return MySQL row object
* @throws DBUnexpectedError Thrown if the database returns an error
*/
function
fetchRow
(
$res
)
{
if
(
$res
instanceof
ResultWrapper
)
{
$res
=
$res
->
result
;
}
//$res->setFetchMode(PDO::FETCH_ASSOC);
if
(
$this
->
limitOffset
!=
0
)
{
$res
->
Move
(
$this
->
limitOffset
);
$this
->
limitOffset
=
0
;
}
$row
=
$res
->
fetch
(
PDO
::
FETCH_BOTH
);
if
(
$this
->
lastErrno
()
)
{
throw
new
DBUnexpectedError
(
$this
,
'Error in fetchRow(): '
.
htmlspecialchars
(
$this
->
lastError
()
)
);
}
return
$row
;
}
/**
* Get the number of rows in a result object
* Lies about the number of row if there was an offset!
*/
function
numRows
(
$res
)
{
if
(
$res
instanceof
ResultWrapper
)
{
$res
=
$res
->
result
;
}
$retVal
=
$res
->
rowCount
();
if
(
$this
->
limitOffset
!=
0
)
{
return
$retVal
-
$this
->
limitOffset
;
}
else
{
return
$retVal
;
}
}
/**
* Get the number of fields in a result object
* See documentation for mssql_num_fields()
*/
function
numFields
(
$res
)
{
if
(
$res
instanceof
ResultWrapper
)
{
$res
=
$res
->
result
;
}
return
$res
->
columnCount
();
}
/**
* Get a field name in a result object
* See documentation for mssql_field_name():
* http://www.php.net/mssql_field_name
*/
function
fieldName
(
$res
,
$n
)
{
if
(
$res
instanceof
ResultWrapper
)
{
$res
=
$res
->
result
;
}
$fldObj
=
$res
->
getColumnMeta
(
$n
);
return
$fldObj
[
'name'
];
}
/**
* Get the inserted value of an auto-increment row
*
* The value inserted should be fetched from nextSequenceValue()
*
* Example:
* $id = $dbw->nextSequenceValue('page_page_id_seq');
* $dbw->insert('page',array('page_id' => $id));
* $id = $dbw->insertId();
*/
function
insertId
()
{
return
$this
->
mConn
->
lastInsertId
();
}
/**
* Change the position of the cursor in a result object
* See mysql_data_seek()
* @param $res Mixed: A SQL result
* @param $row Mixed: Either MySQL row or ResultWrapper
*/
function
dataSeek
(
$res
,
$row
)
{
if
(
$res
instanceof
ResultWrapper
)
{
$res
=
$res
->
result
;
}
if
(
$row
==
0
)
{
$res
->
execute
();
}
else
{
$res
->
fetch
(
PDO
::
FETCH_BOTH
,
PDO
::
FETCH_ORI_ABS
,
$row
-
1
);
return
$res
;
}
return
$res
;
}
/**
* Get the last error number
* See mssql_errno()
*/
function
lastErrno
()
{
if
(
$this
->
mConn
)
{
$eInfo
=
$this
->
mConn
->
errorInfo
();
return
(
int
)
$eInfo
[
0
];
}
else
{
return
-
1
;
}
}
/**
* Get a description of the last error
* See mssql_error() for more details
*/
function
lastError
()
{
if
(
$this
->
mConn
)
{
# Even if it's non-zero, it can still be invalid
wfSuppressWarnings
();
$error
=
$this
->
mConn
->
errorInfo
();
wfRestoreWarnings
();
}
else
{
$error
=
"No Connection"
;
}
if
(
isset
(
$error
[
0
])
&&
$error
[
0
]
!=
0
)
{
$error
=
$error
[
2
]
.
' ('
.
$this
->
mServer
.
')'
;
return
$error
;
}
return
0
;
}
/**
* Get the number of rows affected by the last write query
* See mssql_affected_rows() for more details
*/
function
affectedRows
()
{
return
$this
->
lastPDOStatement
->
rowCount
();
}
/**#@-*/
// end of template : @param $result
/**
* Simple SELECT wrapper, returns a single field, input must be encoded
* Usually aborts on failure
* If errors are explicitly ignored, returns FALSE on failure
*/
function
selectField
(
$table
,
$var
,
$cond
=
''
,
$fname
=
'Database::selectField'
,
$options
=
array
()
)
{
if
(
!
is_array
(
$options
)
)
{
$options
=
array
(
$options
);
}
#$options['LIMIT'] = 1;
$res
=
$this
->
select
(
$table
,
$var
,
$cond
,
$fname
,
$options
);
if
(
$res
===
false
||
!
$this
->
numRows
(
$res
)
)
{
return
false
;
}
$row
=
$this
->
fetchRow
(
$res
);
if
(
$row
!==
false
)
{
$this
->
freeResult
(
$res
);
return
$row
[
0
];
}
else
{
return
false
;
}
}
/**
* Returns an optional USE INDEX clause to go after the table, and a
* string to go at the end of the query
*
* @private
*
* @param array $options an associative array of options to be turned into
* an SQL query, valid keys are listed in the function.
* @return array
*/
function
makeSelectOptions
(
$options
)
{
$preLimitTail
=
$postLimitTail
=
''
;
$startOpts
=
''
;
$noKeyOptions
=
array
();
foreach
(
$options
as
$key
=>
$option
)
{
if
(
is_numeric
(
$key
)
)
{
$noKeyOptions
[
$option
]
=
true
;
}
}
if
(
isset
(
$options
[
'GROUP BY'
]
)
)
$preLimitTail
.=
" GROUP BY {$options['GROUP BY']}"
;
if
(
isset
(
$options
[
'HAVING'
]
)
)
$preLimitTail
.=
" HAVING {$options['HAVING']}"
;
if
(
isset
(
$options
[
'ORDER BY'
]
)
)
$preLimitTail
.=
" ORDER BY {$options['ORDER BY']}"
;
//if (isset($options['LIMIT'])) {
// $tailOpts .= $this->limitResult('', $options['LIMIT'],
// isset($options['OFFSET']) ? $options['OFFSET']
// : false);
//}
//if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $postLimitTail .= ' FOR UPDATE';
//if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $postLimitTail .= ' LOCK IN SHARE MODE';
if
(
isset
(
$noKeyOptions
[
'DISTINCT'
]
)
||
isset
(
$noKeyOptions
[
'DISTINCTROW'
]
)
)
$startOpts
.=
'DISTINCT'
;
# Various MySQL extensions
if
(
isset
(
$noKeyOptions
[
'STRAIGHT_JOIN'
]
)
)
$startOpts
.=
' /*! STRAIGHT_JOIN */'
;
//if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) $startOpts .= ' HIGH_PRIORITY';
//if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) $startOpts .= ' SQL_BIG_RESULT';
//if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) $startOpts .= ' SQL_BUFFER_RESULT';
//if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) $startOpts .= ' SQL_SMALL_RESULT';
//if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) $startOpts .= ' SQL_CALC_FOUND_ROWS';
//if ( isset( $noKeyOptions['SQL_CACHE'] ) ) $startOpts .= ' SQL_CACHE';
//if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) $startOpts .= ' SQL_NO_CACHE';
if
(
isset
(
$options
[
'USE INDEX'
]
)
&&
!
is_array
(
$options
[
'USE INDEX'
]
)
)
{
$useIndex
=
$this
->
useIndexClause
(
$options
[
'USE INDEX'
]
);
}
else
{
$useIndex
=
''
;
}
return
array
(
$startOpts
,
$useIndex
,
$preLimitTail
,
$postLimitTail
);
}
/**
* SELECT wrapper
*
* @param $table Mixed: Array or string, table name(s) (prefix auto-added)
* @param $vars Mixed: Array or string, field name(s) to be retrieved
* @param $conds Mixed: Array or string, condition(s) for WHERE
* @param $fname String: Calling function name (use __METHOD__) for logs/profiling
* @param $options Array: Associative array of options (e.g. array('GROUP BY' => 'page_title')),
* see Database::makeSelectOptions code for list of supported stuff
* @param $join_conds Array: Associative array of table join conditions (optional)
* (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
* @return mixed Database result resource (feed to Database::fetchObject or whatever), or false on failure
*/
function
select
(
$table
,
$vars
,
$conds
=
''
,
$fname
=
'Database::select'
,
$options
=
array
(),
$join_conds
=
array
()
)
{
if
(
is_array
(
$vars
)
)
{
$vars
=
implode
(
','
,
$vars
);
}
if
(
!
is_array
(
$options
)
)
{
$options
=
array
(
$options
);
}
if
(
is_array
(
$table
)
)
{
if
(
!
empty
(
$join_conds
)
||
isset
(
$options
[
'USE INDEX'
]
)
&&
is_array
(
@
$options
[
'USE INDEX'
]
)
)
$from
=
' FROM '
.
$this
->
tableNamesWithUseIndexOrJoin
(
$table
,
array
(),
$join_conds
);
else
$from
=
' FROM '
.
implode
(
','
,
array_map
(
array
(
&
$this
,
'tableName'
),
$table
)
);
}
elseif
(
$table
!=
''
)
{
if
(
$table
{
0
}==
' '
)
{
$from
=
' FROM '
.
$table
;
}
else
{
$from
=
' FROM '
.
$this
->
tableName
(
$table
);
}
}
else
{
$from
=
''
;
}
list
(
$startOpts
,
$useIndex
,
$preLimitTail
,
$postLimitTail
)
=
$this
->
makeSelectOptions
(
$options
);
if
(
!
empty
(
$conds
)
)
{
if
(
is_array
(
$conds
)
)
{
$conds
=
$this
->
makeList
(
$conds
,
LIST_AND
);
}
$sql
=
"SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail"
;
}
else
{
$sql
=
"SELECT $startOpts $vars $from $useIndex $preLimitTail"
;
}
if
(
isset
(
$options
[
'LIMIT'
]))
$sql
=
$this
->
limitResult
(
$sql
,
$options
[
'LIMIT'
],
isset
(
$options
[
'OFFSET'
])
?
$options
[
'OFFSET'
]
:
false
);
$sql
=
"$sql $postLimitTail"
;
if
(
isset
(
$options
[
'EXPLAIN'
]))
{
$sql
=
'EXPLAIN '
.
$sql
;
}
return
$this
->
query
(
$sql
,
$fname
);
}
/**
* Single row SELECT wrapper
* Aborts or returns FALSE on error
*
* @param $table String: table name
* @param $vars String: the selected variables
* @param $conds Array: a condition map, terms are ANDed together.
* Items with numeric keys are taken to be literal conditions
* Takes an array of selected variables, and a condition map, which is ANDed
* e.g: selectRow( "page", array( "page_id" ), array( "page_namespace" =>
* NS_MAIN, "page_title" => "Astronomy" ) ) would return an object where
* $obj- >page_id is the ID of the Astronomy article
* @param $fname String: Calling functio name
* @param $options Array
* @param $join_conds Array
*
* @todo migrate documentation to phpdocumentor format
*/
function
selectRow
(
$table
,
$vars
,
$conds
,
$fname
=
'Database::selectRow'
,
$options
=
array
(),
$join_conds
=
array
()
)
{
#$options['LIMIT'] = 1;
$res
=
$this
->
select
(
$table
,
$vars
,
$conds
,
$fname
,
$options
);
if
(
$res
===
false
)
return
false
;
if
(
$res
->
result
->
columnCount
()
==
0
)
{
$this
->
freeResult
(
$res
);
return
false
;
}
$obj
=
$this
->
fetchObject
(
$res
);
$this
->
freeResult
(
$res
);
return
$obj
;
}
/**
* Removes most variables from an SQL query and replaces them with X or N for numbers.
* It's only slightly flawed. Don't use for anything important.
*
* @param string $sql A SQL Query
* @static
*/
static
function
generalizeSQL
(
$sql
)
{
# This does the same as the regexp below would do, but in such a way
# as to avoid crashing php on some large strings.
# $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql);
$sql
=
str_replace
(
"
\\\\
"
,
''
,
$sql
);
$sql
=
str_replace
(
"
\\
'"
,
''
,
$sql
);
$sql
=
str_replace
(
"
\\\"
"
,
''
,
$sql
);
$sql
=
preg_replace
(
"/'.*'/s"
,
"'X'"
,
$sql
);
$sql
=
preg_replace
(
'/".*"/s'
,
"'X'"
,
$sql
);
# All newlines, tabs, etc replaced by single space
$sql
=
preg_replace
(
'/
\s
+/'
,
' '
,
$sql
);
# All numbers => N
$sql
=
preg_replace
(
'/-?[0-9]+/s'
,
'N'
,
$sql
);
return
$sql
;
}
/**
* Determines whether a field exists in a table
* Usually aborts on failure
* If errors are explicitly ignored, returns NULL on failure
*/
function
fieldExists
(
$table
,
$field
,
$fname
=
'Database::fieldExists'
)
{
#$table = $this->tableName( $table );
$res
=
$this
->
query
(
"SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = '"
.
$table
.
"' AND column_name = '"
.
$field
.
"'"
,
$fname
);
if
(
!
$res
)
{
return
NULL
;
}
$found
=
false
;
while
(
$row
=
$this
->
fetchObject
(
$res
)
)
{
if
(
$row
->
COLUMN_NAME
==
$field
)
{
$found
=
true
;
break
;
}
}
return
$found
;
}
/**
* Determines whether an index exists
* Usually aborts on failure
* If errors are explicitly ignored, returns NULL on failure
*/
function
indexExists
(
$table
,
$index
,
$fname
=
'Database::indexExists'
)
{
$info
=
$this
->
indexInfo
(
$table
,
$index
,
$fname
);
if
(
is_null
(
$info
)
)
{
return
NULL
;
}
else
{
return
$info
!==
false
;
}
}
/**
* Get information about an index into an object
* Returns false if the index does not exist
*/
function
indexInfo
(
$table
,
$index
,
$fname
=
'Database::indexInfo'
)
{
# This does not return the same info as MYSQL would, but that's OK because MediaWiki never uses the
# returned value except to check for the existance of indexes.
#$table = $this->tableName( $table );
$sql
=
"sp_helpindex '"
.
$table
.
"'"
;
$res
=
$this
->
query
(
$sql
,
$fname
);
if
(
!
$res
)
{
return
NULL
;
}
$result
=
array
();
while
(
$row
=
$res
->
fetchObject
()
)
{
if
(
$row
->
index_name
==
$index
)
{
$row
->
Non_unique
=
!
stristr
(
$row
->
index_description
,
"unique"
);
$cols
=
explode
(
", "
,
$row
->
index_keys
);
foreach
(
$cols
as
$col
)
{
$row
->
Column_name
=
trim
(
$col
);
$result
[]
=
clone
$row
;
}
}
else
if
(
$index
==
'PRIMARY'
&&
stristr
(
$row
->
index_description
,
'PRIMARY'
)
)
{
$row
->
Non_unique
=
0
;
$cols
=
explode
(
", "
,
$row
->
index_keys
);
foreach
(
$cols
as
$col
)
{
$row
->
Column_name
=
trim
(
$col
);
$result
[]
=
clone
$row
;
}
}
}
#$this->freeResult($res);
return
empty
(
$result
)
?
false
:
$result
;
}
/**
* Query whether a given table exists
*/
function
tableExists
(
$table
)
{
$qs
=
"SELECT COUNT(*) AS numtables FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '"
.
$table
.
"'"
;
$res
=
$this
->
mConn
->
query
(
$qs
);
$retVal
=
$res
->
fetchColumn
();
if
(
$retVal
)
{
#$this->freeResult( $res );
return
true
;
}
else
{
return
false
;
}
}
/**
* mssql_fetch_field() wrapper
* Returns false if the field doesn't exist
*
* @param $table
* @param $field
*/
function
fieldInfo
(
$table
,
$field
)
{
#$table = $this->tableName( $table );
$res
=
$this
->
query
(
"SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='"
.
$table
.
"' AND COLUMN_NAME='"
.
$field
.
"'"
);
$colinfo
=
$res
->
FetchObject
(
true
);
if
(!
is_object
(
$colinfo
)
)
return
false
;
// We don't need any particular kind of object, just one with the fields the caller might expect.
$retValue
=
new
ADOdbMetaData
();
$retValue
->
name
=
$colinfo
->
COLUMN_NAME
;
$retValue
->
table
=
$colinfo
->
TABLE_NAME
;
$retValue
->
def
=
$colinfo
->
COLUMN_DEFAULT
;
$retValue
->
max_length
=
$colinfo
->
CHARACTER_MAXIMUM_LENGTH
;
$retValue
->
not_null
=
$colinfo
->
IS_NULLABLE
==
"NO"
;
$retValue
->
primary_key
=
0
;
// temporary
$retValue
->
unique_key
=
0
;
// temporarynn
$retValue
->
multiple_key
=
0
;
// temporary
$retValue
->
blob
=
0
;
// temporary
$retValue
->
type
=
$colinfo
->
DATA_TYPE
;
$retValue
->
unsigned
=
0
;
$retValue
->
zerofill
=
0
;
$vres
=
$this
->
query
(
"SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME='"
.
$table
.
"'"
);
if
(
$vres
->
NumRows
()
>
0
)
$res
=
$this
->
query
(
"sp_helpindex '"
.
$table
.
"2'"
);
else
$res
=
$this
->
query
(
"sp_helpindex '"
.
$table
.
"'"
);
while
(
$helpInfo
=
$res
->
FetchNextObj
()
)
{
$keys
=
explode
(
", "
,
$helpInfo
->
index_keys
);
if
(
in_array
(
$field
,
$keys
)
)
{
if
(
stristr
(
$helpInfo
->
index_description
,
"primary_key"
)
)
{
$retValue
->
primary_key
=
1
;
}
if
(
stristr
(
$helpInfo
->
index_description
,
"unique"
)
)
{
$retValue
->
unique_key
=
1
;
$retValue
->
Non_unique
=
0
;
}
else
{
$retValue
->
multiple_key
=
1
;
}
}
}
return
$retValue
;
}
/**
* mssql_field_type() wrapper
*/
function
fieldType
(
$res
,
$index
)
{
if
(
$res
instanceof
ResultWrapper
)
{
$res
=
$res
->
result
;
return
$res
;
}
else
{
$fo
=
$res
->
getColumnMeta
(
$index
);
return
$fo
[
'sqlserv:decl_type'
];
}
}
/**
* INSERT wrapper, inserts an array into a table
*
* $a may be a single associative array, or an array of these with numeric keys, for
* multi-row insert.
*
* Usually aborts on failure
* If errors are explicitly ignored, returns success
*/
function
insert
(
$table
,
$a
,
$fname
=
'Database::insert'
,
$options
=
array
()
)
{
# No rows to insert, easy just return now
if
(
!
count
(
$a
)
)
{
return
true
;
}
$table
=
$this
->
tableName
(
$table
);
if
(
!
is_array
(
$options
)
)
{
$options
=
array
(
$options
);
}
if
(
isset
(
$a
[
0
]
)
&&
is_array
(
$a
[
0
]
)
)
{
$multi
=
true
;
$keys
=
array_keys
(
$a
[
0
]
);
$values
=
array_values
(
$a
[
0
]
);
$record
=
$a
[
0
];
}
else
{
$multi
=
false
;
$keys
=
array_keys
(
$a
);
$values
=
array_values
(
$a
);
$record
=
$a
;
}
// Microsoft SQL Server doesn't allow any options on an INSERT
$sql
=
"INSERT INTO $table ("
.
implode
(
','
,
$keys
)
.
') VALUES ('
;
$first
=
true
;
foreach
(
$keys
as
$row
)
{
if
(
$first
)
{
$first
=
false
;
$sql
.=
'?'
;
}
else
{
$sql
.=
',?'
;
}
}
$sql
.=
')'
;
$ok
=
false
;
try
{
$pdoStmt
=
$this
->
mConn
->
prepare
(
$sql
);
$this
->
lastPDOStatement
=
$pdoStmt
;
$ok
=
$pdoStmt
->
execute
(
$values
);
}
catch
(
PDOException
$ex
)
{
if
(!
in_array
(
'IGNORE'
,
$options
)
)
{
$errorTriple
=
$this
->
mConn
->
errorInfo
();
throw
new
DBQueryError
(
$this
,
$ex
->
errorInfo
[
2
],
$this
->
mConn
->
errorCode
(),
$sql
,
$fname
);
}
}
return
$ok
;
}
/**
* Execute SQL
*
* @param sql SQL statement to execute, or possibly an array holding prepared statement ($sql[0] will hold sql text)
* @param [inputarr] holds the input data to bind to. Null elements will be set to null.
* @return RecordSet or false
*/
function
Execute
(
$sql
,
$inputarr
=
false
)
{
if
(
$this
->
fnExecute
)
{
$fn
=
$this
->
fnExecute
;
$ret
=
$fn
(
$this
,
$sql
,
$inputarr
);
if
(
isset
(
$ret
))
return
$ret
;
}
if
(
$inputarr
)
{
if
(!
is_array
(
$inputarr
))
$inputarr
=
array
(
$inputarr
);
$element0
=
reset
(
$inputarr
);
# is_object check because oci8 descriptors can be passed in
$array_2d
=
is_array
(
$element0
);
// && !is_object(reset($element0));// Removed this check 30 JUL 2008 to reduce breakage BY: djb
//remove extra memory copy of input -mikefedyk
unset
(
$element0
);
if
(!
is_array
(
$sql
)
&&
!
$this
->
_bindInputArray
)
{
$sqlarr
=
explode
(
'?'
,
$sql
);
if
(!
$array_2d
)
$inputarr
=
array
(
$inputarr
);
foreach
(
$inputarr
as
$arr
)
{
$sql
=
''
;
$i
=
0
;
//Use each() instead of foreach to reduce memory usage -mikefedyk
while
(
list
(,
$v
)
=
each
(
$arr
))
{
$sql
.=
$sqlarr
[
$i
];
// from Ron Baldwin <ron.baldwin#sourceprose.com>
// Only quote string types
$typ
=
gettype
(
$v
);
if
(
$typ
==
'string'
)
//New memory copy of input created here -mikefedyk
$sql
.=
$this
->
qstr
(
$v
);
else
if
(
$typ
==
'double'
)
$sql
.=
str_replace
(
','
,
'.'
,
$v
);
// locales fix so 1.1 does not get converted to 1,1
else
if
(
$typ
==
'boolean'
)
$sql
.=
$v
?
$this
->
true
:
$this
->
false
;
else
if
(
$typ
==
'object'
)
{
if
(
method_exists
(
$v
,
'__toString'
))
$sql
.=
$this
->
qstr
(
$v
->
__toString
());
else
$sql
.=
$this
->
qstr
((
string
)
$v
);
}
else
if
(
$v
===
null
)
$sql
.=
'NULL'
;
else
$sql
.=
$v
;
$i
+=
1
;
}
if
(
isset
(
$sqlarr
[
$i
]))
{
$sql
.=
$sqlarr
[
$i
];
if
(
$i
+
1
!=
sizeof
(
$sqlarr
))
$this
->
outp_throw
(
"Input Array does not match ?: "
.
htmlspecialchars
(
$sql
),
'Execute'
);
}
else
if
(
$i
!=
sizeof
(
$sqlarr
))
$this
->
outp_throw
(
"Input array does not match ?: "
.
htmlspecialchars
(
$sql
),
'Execute'
);
$ret
=
$this
->
_Execute
(
$sql
);
if
(!
$ret
)
return
$ret
;
}
}
else
{
if
(
$array_2d
)
{
if
(
is_string
(
$sql
))
$stmt
=
$this
->
Prepare
(
$sql
);
else
$stmt
=
$sql
;
foreach
(
$inputarr
as
$arr
)
{
$ret
=
$this
->
_Execute
(
$stmt
,
$arr
);
if
(!
$ret
)
return
$ret
;
}
}
else
{
$ret
=
$this
->
_Execute
(
$sql
,
$inputarr
);
}
}
}
else
{
$ret
=
$this
->
_Execute
(
$sql
,
false
);
}
return
$ret
;
}
/**
* Change the current database
*/
function
selectDB
(
$db
)
{
$this
->
mDBname
=
$db
;
$cmd
=
"USE "
.
$db
;
return
$this
->
mConn
->
exec
(
$cmd
);
}
/**
* Format a table name ready for use in constructing an SQL query
*
* This does two important things: it brackets table names which as necessary,
* and it adds a table prefix if there is one.
*
* All functions of this object which require a table name call this function
* themselves. Pass the canonical name to such functions. This is only needed
* when calling query() directly.
*
* @param string $name database table name
*/
function
tableName
(
$name
)
{
global
$wgSharedDB
;
# Skip quoted literals
if
(
$name
{
0
}
!=
'['
)
{
if
(
$this
->
mTablePrefix
!==
''
&&
strpos
(
'.'
,
$name
)
===
false
)
{
$name
=
"{$this->mTablePrefix}$name"
;
}
if
(
isset
(
$wgSharedDB
)
&&
"{$this->mTablePrefix}user"
==
$name
)
{
$name
=
"[$wgSharedDB].[$name]"
;
}
else
{
# Standard quoting
$name
=
"[$name]"
;
}
}
return
$name
;
}
/**
* @private
*/
function
tableNamesWithUseIndexOrJoin
(
$tables
,
$use_index
=
array
(),
$join_conds
=
array
()
)
{
$ret
=
array
();
$retJOIN
=
array
();
$use_index_safe
=
is_array
(
$use_index
)
?
$use_index
:
array
();
$join_conds_safe
=
is_array
(
$join_conds
)
?
$join_conds
:
array
();
foreach
(
$tables
as
$table
)
{
// Is there a JOIN and INDEX clause for this table?
if
(
isset
(
$join_conds_safe
[
$table
])
&&
isset
(
$use_index_safe
[
$table
])
)
{
$tableClause
=
$join_conds_safe
[
$table
][
0
]
.
' '
.
$this
->
tableName
(
$table
);
$tableClause
.=
' '
.
$this
->
useIndexClause
(
implode
(
','
,
(
array
)
$use_index_safe
[
$table
]
)
);
$tableClause
.=
' ON ('
.
$this
->
makeList
((
array
)
$join_conds_safe
[
$table
][
1
],
LIST_AND
)
.
')'
;
$retJOIN
[]
=
$tableClause
;
// Is there an INDEX clause?
}
else
if
(
isset
(
$use_index_safe
[
$table
])
)
{
$tableClause
=
$this
->
tableName
(
$table
);
$tableClause
.=
' '
.
$this
->
useIndexClause
(
implode
(
','
,
(
array
)
$use_index_safe
[
$table
]
)
);
$ret
[]
=
$tableClause
;
// Is there a JOIN clause?
}
else
if
(
isset
(
$join_conds_safe
[
$table
])
)
{
$tableClause
=
$join_conds_safe
[
$table
][
0
]
.
' '
.
$this
->
tableName
(
$table
);
$tableClause
.=
' ON ('
.
$this
->
makeList
((
array
)
$join_conds_safe
[
$table
][
1
],
LIST_AND
)
.
')'
;
$retJOIN
[]
=
$tableClause
;
}
else
{
$tableClause
=
$this
->
tableName
(
$table
);
$ret
[]
=
$tableClause
;
}
}
// We can't separate explicit JOIN clauses with ',', use ' ' for those
$straightJoins
=
!
empty
(
$ret
)
?
implode
(
','
,
$ret
)
:
""
;
$otherJoins
=
!
empty
(
$retJOIN
)
?
implode
(
' '
,
$retJOIN
)
:
""
;
// Compile our final table clause
return
implode
(
' '
,
array
(
$straightJoins
,
$otherJoins
)
);
}
/**
* Wrapper for addslashes()
* @param string $s String to be slashed.
* @return string slashed string.
*/
function
strencode
(
$s
)
{
$s
=
$this
->
qstr
(
$s
,
get_magic_quotes_gpc
());
#$s = substr($s, 1, -1); // qstr adds quotes, which the caller is presumably going to do again.
return
$s
;
}
/**
* Correctly quotes a string so that all strings are escaped. We prefix and append
* to the string single-quotes.
* An example is $db->qstr("Don't bother",magic_quotes_runtime());
*
* @param s the string to quote
* @param [magic_quotes] if $s is GET/POST var, set to get_magic_quotes_gpc().
* This undoes the stupidity of magic quotes for GPC.
*
* @return quoted string to be sent back to database
*/
function
qstr
(
$s
,
$magic_quotes
=
false
)
{
return
$this
->
mConn
->
quote
(
$s
);
// In case the above doesn't work!
if
(!
$magic_quotes
)
{
if
(
$this
->
replaceQuote
[
0
]
==
'
\\
'
){
// only since php 4.0.5
$s
=
str_replace
(
array
(
'
\\
'
,
"
\0
"
),
array
(
'
\\\\
'
,
"
\\\0
"
),
$s
);
//$s = str_replace("\0","\\\0", str_replace('\\','\\\\',$s));
}
return
"'"
.
str_replace
(
"'"
,
$this
->
replaceQuote
,
$s
).
"'"
;
}
// undo magic quotes for "
$s
=
str_replace
(
'
\\
"'
,
'"'
,
$s
);
if
(
$this
->
replaceQuote
==
"
\\
'"
)
// ' already quoted, no need to change anything
return
"'$s'"
;
else
{
// change \' to '' for sybase/mssql
$s
=
str_replace
(
'
\\\\
'
,
'
\\
'
,
$s
);
return
"'"
.
str_replace
(
"
\\
'"
,
$this
->
replaceQuote
,
$s
).
"'"
;
}
}
/**
* If it's a string, adds quotes and backslashes
* Otherwise returns as-is
*/
function
addQuotes
(
$s
)
{
if
(
is_null
(
$s
)
)
{
return
'NULL'
;
}
else
if
(
is_numeric
(
$s
)
)
{
return
"'"
.
$s
.
"'"
;
}
else
{
# This will also quote numeric values. This should be harmless,
# and protects against weird problems that occur when they really
# _are_ strings such as article titles and string->number->string
# conversion is not 1:1.
return
$this
->
strencode
(
$s
);
}
}
/**
* Escape string for safe LIKE usage
*/
function
escapeLike
(
$s
)
{
//$s=$this->strencode( $s );
$s
=
str_replace
(
array
(
'%'
,
'_'
),
array
(
'
\%
'
,
'
\_
'
),
$s
);
return
$s
;
}
/**
* USE INDEX clause
* SQL Server doesn't have them and returns ""
*/
function
useIndexClause
(
$index
)
{
return
""
;
}
/**
* REPLACE query wrapper
* PostgreSQL simulates this with a DELETE followed by INSERT
* $row is the row to insert, an associative array
* $uniqueIndexes is an array of indexes. Each element may be either a
* field name or an array of field names
*
* It may be more efficient to leave off unique indexes which are unlikely to collide.
* However if you do this, you run the risk of encountering errors which wouldn't have
* occurred in MySQL
*
* @todo migrate comment to phodocumentor format
*/
function
replace
(
$table
,
$uniqueIndexes
,
$rows
,
$fname
=
'Database::replace'
)
{
$table
=
$this
->
tableName
(
$table
);
# Single row case
if
(
!
is_array
(
reset
(
$rows
)
)
)
{
$rows
=
array
(
$rows
);
}
foreach
(
$rows
as
$row
)
{
# Delete rows which collide
if
(
$uniqueIndexes
)
{
$sql
=
"DELETE FROM $table WHERE "
;
$first
=
true
;
foreach
(
$uniqueIndexes
as
$index
)
{
if
(
$first
)
{
$first
=
false
;
$sql
.=
"("
;
}
else
{
$sql
.=
') OR ('
;
}
if
(
is_array
(
$index
)
)
{
$first2
=
true
;
foreach
(
$index
as
$col
)
{
if
(
$first2
)
{
$first2
=
false
;
}
else
{
$sql
.=
' AND '
;
}
$sql
.=
$col
.
'='
.
$this
->
addQuotes
(
$row
[
$col
]
);
}
}
else
{
$sql
.=
$index
.
'='
.
$this
->
addQuotes
(
$row
[
$index
]
);
}
}
$sql
.=
')'
;
try
{
$this
->
query
(
$sql
,
$fname
);
}
catch
(
ADODB_Exception
$exc
)
{
}
}
# Now insert the row
$sql
=
"INSERT INTO $table ("
.
$this
->
makeList
(
array_keys
(
$row
),
LIST_NAMES
)
.
') VALUES ('
.
$this
->
makeList
(
$row
,
LIST_COMMA
)
.
')'
;
$this
->
query
(
$sql
,
$fname
);
}
}
/**
* DELETE where the condition is a join
* MySQL does this with a multi-table DELETE syntax, PostgreSQL does it with sub-selects
* Still need to check what SQL Server should do. For now, this is the same as the inherited implementation.
* For safety, an empty $conds will not delete everything. If you want to delete all rows where the
* join condition matches, set $conds='*'
*
* DO NOT put the join condition in $conds
*
* @param $delTable String: The table to delete from.
* @param $joinTable String: The other table.
* @param $delVar String: The variable to join on, in the first table.
* @param $joinVar String: The variable to join on, in the second table.
* @param $conds Array: Condition array of field names mapped to variables, ANDed together in the WHERE clause
* @param $fname String: Calling function name (use __METHOD__) for logs/profiling
*/
function
deleteJoin
(
$delTable
,
$joinTable
,
$delVar
,
$joinVar
,
$conds
,
$fname
=
'Database::deleteJoin'
)
{
if
(
!
$conds
)
{
throw
new
DBUnexpectedError
(
$this
,
'Database::deleteJoin() called with empty $conds'
);
}
$delTable
=
$this
->
tableName
(
$delTable
);
$joinTable
=
$this
->
tableName
(
$joinTable
);
$sql
=
"DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar "
;
if
(
$conds
!=
'*'
)
{
$sql
.=
' AND '
.
$this
->
makeList
(
$conds
,
LIST_AND
);
}
return
$this
->
query
(
$sql
,
$fname
);
}
/**
* @return string Returns the text of the low priority option if it is supported, or a blank string otherwise
*/
function
lowPriorityOption
()
{
return
''
;
}
/**
* Construct a LIMIT query with optional offset
* This is used for query pages
* $sql string SQL query we will append the limit too
* $limit integer the SQL limit
* $offset integer the SQL offset (default false)
*/
function
limitResult
(
$sql
,
$limit
,
$offset
=
false
)
{
if
(
!
is_numeric
(
$limit
)
)
{
throw
new
DBUnexpectedError
(
$this
,
"Invalid non-numeric limit passed to limitResult()
\n
"
);
}
if
(
is_numeric
(
$offset
)
&&
$offset
!=
0
)
{
$this
->
limitOffset
=
$offset
;
$limit
+=
$offset
;
}
else
{
$this
->
limitOffset
=
0
;
}
if
(
strpos
(
$sql
,
"SELECT"
)
===
false
)
{
return
"TOP {$limit} "
.
$sql
;
}
else
{
return
preg_replace
(
"/SELECT/"
,
"SELECT TOP {$limit}"
,
$sql
);
}
}
function
limitResultForUpdate
(
$sql
,
$num
)
{
# Can't really do this on Microsoft SQL?
return
$sql
;
#return $this->limitResult($sql, $num, 0);
}
/**
* Returns an SQL expression for a simple conditional.
* Uses CASE on SQL Server
*
* @param string $cond SQL expression which will result in a boolean value
* @param string $trueVal SQL expression to return if true
* @param string $falseVal SQL expression to return if false
* @return string SQL fragment
*/
function
conditional
(
$cond
,
$trueVal
,
$falseVal
)
{
return
" CASE WHEN $cond THEN $trueVal ELSE $falseVal END "
;
}
/**
* Determines if the last failure was due to a deadlock
*/
function
wasDeadlock
()
{
return
$this
->
lastErrno
()
==
1213
;
}
/**
* Perform a deadlock-prone transaction.
*
* This function invokes a callback function to perform a set of write
* queries. If a deadlock occurs during the processing, the transaction
* will be rolled back and the callback function will be called again.
*
* Usage:
* $dbw->deadlockLoop( callback, ... );
*
* Extra arguments are passed through to the specified callback function.
*
* Returns whatever the callback function returned on its successful,
* iteration, or false on error, for example if the retry limit was
* reached.
*/
function
deadlockLoop
()
{
$myFname
=
'Database::deadlockLoop'
;
$this
->
begin
();
$args
=
func_get_args
();
$function
=
array_shift
(
$args
);
$oldIgnore
=
$this
->
ignoreErrors
(
true
);
$tries
=
DEADLOCK_TRIES
;
if
(
is_array
(
$function
)
)
{
$fname
=
$function
[
0
];
}
else
{
$fname
=
$function
;
}
do
{
$retVal
=
call_user_func_array
(
$function
,
$args
);
$error
=
$this
->
lastError
();
$errno
=
$this
->
lastErrno
();
$sql
=
$this
->
lastQuery
();
if
(
$errno
)
{
if
(
$this
->
wasDeadlock
()
)
{
# Retry
usleep
(
mt_rand
(
DEADLOCK_DELAY_MIN
,
DEADLOCK_DELAY_MAX
)
);
}
else
{
$this
->
reportQueryError
(
$error
,
$errno
,
$sql
,
$fname
);
}
}
}
while
(
$this
->
wasDeadlock
()
&&
--
$tries
>
0
);
$this
->
ignoreErrors
(
$oldIgnore
);
if
(
$tries
<=
0
)
{
$this
->
rollback
();
$this
->
reportQueryError
(
$error
,
$errno
,
$sql
,
$fname
);
return
false
;
}
else
{
$this
->
commit
();
return
$retVal
;
}
}
/**
* Get the position of the master from SHOW SLAVE STATUS
*/
function
getSlavePos
()
{
$res
=
$this
->
query
(
'SHOW SLAVE STATUS'
,
'Database::getSlavePos'
);
$row
=
$this
->
fetchObj
(
$res
);
if
(
$row
)
{
return
array
(
$row
->
Master_Log_File
,
$row
->
Read_Master_Log_Pos
);
}
else
{
return
array
(
false
,
false
);
}
}
/**
* Get the position of the master from SHOW MASTER STATUS
*/
function
getMasterPos
()
{
$res
=
$this
->
query
(
'SHOW MASTER STATUS'
,
'Database::getMasterPos'
);
$row
=
$this
->
fetchObj
(
$res
);
if
(
$row
)
{
return
array
(
$row
->
File
,
$row
->
Position
);
}
else
{
return
array
(
false
,
false
);
}
}
/**
* Begin a transaction, committing any previously open transaction
*/
function
begin
(
$fname
=
'Database::begin'
)
{
try
{
$this
->
mConn
->
commit
();
}
catch
(
PDOException
$e
)
{
}
try
{
if
(
$this
->
mTrxLevel
!=
1
)
$this
->
mConn
->
beginTransaction
();
$this
->
mTrxLevel
=
1
;
}
catch
(
PDOException
$e
)
{
}
}
/**
* End a transaction
*/
function
commit
(
$fname
=
'Database::commit'
)
{
try
{
if
(
$this
->
mTrxLevel
==
1
)
$this
->
mConn
->
commit
();
$this
->
mTrxLevel
=
0
;
}
catch
(
PDOException
$e
)
{
}
}
/**
* Rollback a transaction
*/
function
rollback
(
$fname
=
'Database::rollback'
)
{
try
{
if
(
$this
->
mTrxLevel
==
1
)
$this
->
mConn
->
rollback
();
$this
->
mTrxLevel
=
0
;
}
catch
(
PDOException
$e
)
{
}
}
/**
* @return string wikitext of a link to the server software's web site
*/
function
getSoftwareLink
()
{
return
"[http://www.microsoft.com/download/en/details.aspx?id=2098 Microsoft Drivers for PHP for SQL Server]"
;
}
/**
* @return string Version information from the database
*/
function
getServerVersion
()
{
$result
=
$this
->
mConn
->
query
(
"SELECT @@VERSION AS version"
);
return
$result
->
fetchColumn
();
}
/**
* Ping the server and try to reconnect if it there is no connection
*/
function
ping
()
{
wfDebug
(
"Tried to call mssql_ping but this is the SQL Server version. Faking it!
\n
"
);
return
true
;
}
/**
* Get slave lag.
* At the moment, this will only work if the DB user has the PROCESS privilege
*/
function
getLag
()
{
return
false
;
// Still need to figure out what to do on SQL Server
$res
=
$this
->
query
(
'SHOW PROCESSLIST'
);
# Find slave SQL thread. Assumed to be the second one running, which is a bit
# dubious, but unfortunately there's no easy rigorous way
while
(
$row
=
$this
->
FetchNextObj
(
$res
)
)
{
/* This should work for most situations - when default db
* for thread is not specified, it had no events executed,
* and therefore it doesn't know yet how lagged it is.
*
* Relay log I/O thread does not select databases.
*/
if
(
$row
->
User
==
'system user'
&&
$row
->
State
!=
'Waiting for master to send event'
&&
$row
->
State
!=
'Connecting to master'
&&
$row
->
State
!=
'Queueing master event to the relay log'
&&
$row
->
State
!=
'Waiting for master update'
&&
$row
->
State
!=
'Requesting binlog dump'
)
{
# This is it, return the time (except -ve)
if
(
$row
->
Time
>
0x7fffffff
)
{
return
false
;
}
else
{
return
$row
->
Time
;
}
}
}
return
false
;
}
/**
* Get status information from SHOW STATUS in an associative array
*/
function
getStatus
(
$which
=
"%"
)
{
return
false
;
// Still need to figure this out for SQL Server
$res
=
$this
->
query
(
"SHOW STATUS LIKE '{$which}'"
);
$status
=
array
();
while
(
$row
=
$this
->
FetchNextObj
(
$res
)
)
{
$status
[
$row
->
Variable_name
]
=
$row
->
Value
;
}
return
$status
;
}
function
encodeBlob
(
$b
)
{
return
utf8_encode
(
$b
);
//return $this->mConn->BlobEncode($b);
}
function
decodeBlob
(
$b
)
{
return
utf8_decode
(
$b
);
//return $this->mConn->BlobDecode($b);
}
/**
* Override database's default connection timeout.
* May be useful for very long batch queries such as
* full-wiki dumps, where a single query reads out
* over hours or days.
* @param int $timeout in seconds
*/
public
function
setTimeout
(
$timeout
)
{
//$this->query( "SET net_read_timeout=$timeout" );
//$this->query( "SET net_write_timeout=$timeout" );
}
function
setup_database
()
{
global
$wgVersion
,
$wgDBmwschema
,
$wgDBts2schema
,
$wgDBport
;
dbsource
(
"../maintenance/sqlserver/tables.sql"
,
$this
);
## Update version information
$mwv
=
$this
->
addQuotes
(
$wgVersion
);
$pgv
=
$this
->
addQuotes
(
$this
->
getServerVersion
());
$pgu
=
$this
->
addQuotes
(
$this
->
mUser
);
$mws
=
$this
->
addQuotes
(
$wgDBmwschema
);
$tss
=
$this
->
addQuotes
(
$wgDBts2schema
);
$pgp
=
$this
->
addQuotes
(
$wgDBport
);
$dbn
=
$this
->
addQuotes
(
$this
->
mDBname
);
$ctype
=
"UNKNOWN"
;
//$ctype = odbc_fetch_result($this->doQuery("SHOW lc_ctype"),0,0);
$SQL
=
"UPDATE mediawiki_version SET mw_version=$mwv, pg_version=$pgv, pg_user=$pgu, "
.
"mw_schema = $mws, ts2_schema = $tss, pg_port=$pgp, pg_dbname=$dbn, "
.
"ctype = '$ctype' "
.
"WHERE type = 'Creation'"
;
$this
->
query
(
$SQL
);
## Avoid the non-standard "REPLACE INTO" syntax
$f
=
fopen
(
"../maintenance/interwiki.sql"
,
'r'
);
if
(
$f
==
false
)
{
dieout
(
"<li>Could not find the interwiki.sql file"
);
}
## We simply assume it is already empty as we have just created it
$SQL
=
"INSERT INTO interwiki(iw_prefix,iw_url,iw_local) VALUES "
;
while
(
!
feof
(
$f
)
)
{
$line
=
fgets
(
$f
,
1024
);
$matches
=
array
();
if
(!
preg_match
(
'/^
\s
*(
\(
.+?),(
\d
)
\)
/'
,
$line
,
$matches
))
{
continue
;
}
$this
->
query
(
"$SQL $matches[1],$matches[2])"
);
}
print
" (table interwiki successfully populated)...
\n
"
;
}
/**
* Replace variables in sourced SQL
*/
protected
function
replaceVars
(
$ins
)
{
$varnames
=
array
(
'wgDBserver'
,
'wgDBname'
,
'wgDBintlname'
,
'wgDBuser'
,
'wgDBpassword'
,
'wgDBsqluser'
,
'wgDBsqlpassword'
,
'wgDBadminuser'
,
'wgDBadminpassword'
,
);
// Ordinary variables
foreach
(
$varnames
as
$var
)
{
if
(
isset
(
$GLOBALS
[
$var
]
)
)
{
$val
=
addslashes
(
$GLOBALS
[
$var
]
);
// FIXME: safety check?
$ins
=
str_replace
(
'{$'
.
$var
.
'}'
,
$val
,
$ins
);
$ins
=
str_replace
(
'/*$'
.
$var
.
'*/`'
,
'`'
.
$val
,
$ins
);
$ins
=
str_replace
(
'/*$'
.
$var
.
'*/'
,
$val
,
$ins
);
}
}
// Table prefixes
$ins
=
preg_replace_callback
(
'/
\/\*
(?:
\$
wgDBprefix|_)
\*\/
([a-zA-Z0-9_]*)/'
,
array
(
&
$this
,
'tableNameCallback'
),
$ins
);
return
$ins
;
}
function
getDBname
()
{
return
$this
->
mDBname
;
}
function
getServer
()
{
return
$this
->
mServer
;
}
/*
* Build a concatenation list to feed into a SQL query
*/
function
buildConcat
(
$stringList
)
{
//return join($stringList, ' + ');
return
implode
(
' + '
,
$stringList
);
}
function
estimateRowCount
(
$table
,
$vars
=
'*'
,
$conds
=
''
,
$fname
=
'DatabaseMSPDO::estimateRowCount'
,
$options
=
array
()
)
{
//$options['EXPLAIN']=true;
$res
=
$this
->
select
(
$table
,
$vars
,
$conds
,
$fname
,
$options
);
if
(
$res
===
false
)
return
false
;
$rows
=
$this
->
numRows
(
$res
);
$this
->
freeResult
(
$res
);
if
(!
$rows
)
{
return
0
;
}
return
$rows
;
}
/**
* Returns true if this database does an implicit sort when doing GROUP BY
*/
function
implicitGroupby
()
{
return
false
;
}
public
function
setBigSelects
(
$value
=
true
)
{
// no-op
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3356
Default Alt Text
DatabaseMSPDO.php (46 KB)
Attached To
Mode
T11767: [DO NOT USE] Microsoft SQL Server/MSSQL support (tracking)
Attached
Detach File
Event Timeline
Log In to Comment