* Refactored DatabaseBase::sourceStream(), made it possible for descendant classes to alter its behaviour w/o having to redo it completely like Oracle does.
* MySQL class now supports specifying DELIMITER.
* Thrown away the mess of catering for double semicolon. If it's a problem, fix your .sql files!
* Haven't actually touched Oracle.
* Tests!
protected $htmlErrors;
+ protected $delimiter = ';';
+
# ------------------------------------------------------------------------------
# Accessors
# ------------------------------------------------------------------------------
function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
$fname = 'DatabaseBase::sourceStream' )
{
- $cmd = "";
+ $cmd = '';
$done = false;
- $dollarquote = false;
- while ( ! feof( $fp ) ) {
+ while ( !feof( $fp ) ) {
if ( $lineCallback ) {
call_user_func( $lineCallback );
}
$line = trim( fgets( $fp ) );
- $sl = strlen( $line ) - 1;
- if ( $sl < 0 ) {
+ if ( $line == '' ) {
continue;
}
continue;
}
- # # Allow dollar quoting for function declarations
- if ( substr( $line, 0, 4 ) == '$mw$' ) {
- if ( $dollarquote ) {
- $dollarquote = false;
- $done = true;
- }
- else {
- $dollarquote = true;
- }
- }
- elseif ( !$dollarquote ) {
- if ( ';' == $line[$sl] && ( $sl < 2 || ';' != $line[$sl - 1] ) ) {
- $done = true;
- $line = substr( $line, 0, $sl );
- }
- }
-
if ( $cmd != '' ) {
$cmd .= ' ';
}
+ $done = $this->streamStatementEnd( $cmd, $line );
+
$cmd .= "$line\n";
- if ( $done ) {
- $cmd = str_replace( ';;', ";", $cmd );
+ if ( $done || feof( $fp ) ) {
$cmd = $this->replaceVars( $cmd );
$res = $this->query( $cmd, $fname );
return true;
}
+ /**
+ * Called by sourceStream() to check if we've reached a statement end
+ *
+ * @param $sql String: SQL assembled so far
+ * @param $newLine String: New line about to be added to $sql
+ * @returns Bool: Whether $newLine contains end of the statement
+ */
+ protected function streamStatementEnd( &$sql, &$newLine ) {
+ if ( $this->delimiter ) {
+ $prev = $newLine;
+ $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
+ if ( $newLine != $prev ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Database independent variable replacement. Replaces a set of variables
* in an SQL statement with their contents as given by $this->getSchemaVars().
}
}
+ protected function streamStatementEnd( &$sql, &$newLine ) {
+ if ( strtoupper( substr( $newLine, 0, 9 ) ) == 'DELIMITER' ) {
+ preg_match( '/^DELIMITER\s+(\S+)/' , $newLine, $m );
+ $this->delimiter = $m[1];
+ $newLine = '';
+ }
+ return parent::streamStatementEnd( $sql, $newLine );
+ }
+
/**
* @param $lockName string
* @param $method string
public function getSearchEngine() {
return 'SearchPostgres';
}
+
+ protected function streamStatementEnd( &$sql, &$newLine ) {
+ # Allow dollar quoting for function declarations
+ if ( substr( $newLine, 0, 4 ) == '$mw$' ) {
+ if ( $this->delimiter ) {
+ $this->delimiter = false;
+ }
+ else {
+ $this->delimiter = ';';
+ }
+ }
+ return parent::streamStatementEnd( $sql, $newLine );
+ }
} // end DatabasePostgres class
--- /dev/null
+-- MySQL test file for DatabaseTest::testStoredFunctions()
+
+DELIMITER //
+
+CREATE FUNCTION mw_test_function()
+RETURNS int DETERMINISTIC
+BEGIN
+ SET @foo = 21;
+ RETURN @foo * 2;
+END//
+
+DELIMITER //
--- /dev/null
+-- Postgres test file for DatabaseTest::testStoredFunctions()
+
+CREATE FUNCTION mw_test_function()
+RETURNS INTEGER
+LANGUAGE plpgsql AS
+$mw$
+DECLARE foo INTEGER;
+BEGIN
+ foo := 21;
+ RETURN foo * 2;
+END
+$mw$;
/**
* @group Database
+ * @group DatabaseBase
*/
class DatabaseTest extends MediaWikiTestCase {
- var $db;
+ var $db, $functionTest = false;
function setUp() {
- $this->db = wfGetDB( DB_SLAVE );
+ $this->db = wfGetDB( DB_MASTER );
+ }
+
+ function tearDown() {
+ if ( $this->functionTest ) {
+ $this->dropFunctions();
+ $this->functionTest = false;
+ }
}
function testAddQuotesNull() {
$sql );
}
+ function testStoredFunctions() {
+ if ( !in_array( wfGetDB( DB_MASTER )->getType(), array( 'mysql', 'postgres' ) ) ) {
+ $this->markTestSkipped( 'MySQL or Postgres required' );
+ }
+ global $IP;
+ $this->dropFunctions();
+ $this->functionTest = true;
+ $this->assertTrue( $this->db->sourceFile( "$IP/tests/phpunit/data/db/{$this->db->getType()}/functions.sql" ) );
+ $res = $this->db->query( 'SELECT mw_test_function() AS test', __METHOD__ );
+ $this->assertEquals( 42, $res->fetchObject()->test );
+ }
+
+ private function dropFunctions() {
+ $this->db->query( 'DROP FUNCTION IF EXISTS mw_test_function'
+ . ( $this->db->getType() == 'postgres' ? '()' : '' )
+ );
+ }
}