* Add support for prepared statements. This should be safer than dumping variables...
authorBrion Vibber <brion@users.mediawiki.org>
Mon, 18 Oct 2004 07:25:56 +0000 (07:25 +0000)
committerBrion Vibber <brion@users.mediawiki.org>
Mon, 18 Oct 2004 07:25:56 +0000 (07:25 +0000)
* Add some quick PHPUnit tests for Database::fillPrepared()

includes/Database.php
tests/DatabaseTest.php [new file with mode: 0644]
tests/RunTests.php [new file with mode: 0644]

index 982e81c..0360ee0 100644 (file)
@@ -352,6 +352,100 @@ class Database {
        }
 
 
+       /**
+        * Intended to be compatible with the PEAR::DB wrapper functions.
+        * http://pear.php.net/manual/en/package.database.db.intro-execute.php
+        *
+        * ? = scalar value, quoted as necessary
+        * ! = raw SQL bit (a function for instance)
+        * & = filename; reads the file and inserts as a blob
+        *     (we don't use this though...)
+        */
+       function prepare( $sql, $func = 'Database::prepare' ) {
+               /* MySQL doesn't support prepared statements (yet), so just
+                  pack up the query for reference. We'll manually replace
+                  the bits later. */
+               return array( 'query' => $sql, 'func' => $func );
+       }
+       
+       function freePrepared( $prepared ) {
+               /* No-op for MySQL */
+       }
+       
+       /**
+        * Execute a prepared query with the various arguments
+        * @param string $prepared the prepared sql
+        * @param mixed $args Either an array here, or put scalars as varargs
+        */
+       function execute( $prepared, $args = null ) {
+               if( !is_array( $args ) ) {
+                       # Pull the var args
+                       $args = func_get_args();
+                       array_shift( $args );
+               }
+               $sql = $this->fillPrepared( $prepared['query'], $args );
+               return $this->query( $sql, $prepared['func'] );
+       }
+       
+       /**
+        * Prepare & execute an SQL statement, quoting and inserting arguments
+        * in the appropriate places.
+        * @param 
+        */
+       function safeQuery( $query, $args = null ) {
+               $prepared = $this->prepare( $query, 'Database::safeQuery' );
+               if( !is_array( $args ) ) {
+                       # Pull the var args
+                       $args = func_get_args();
+                       array_shift( $args );
+               }
+               $retval = $this->execute( $prepared, $args );
+               $this->freePrepared( $prepared );
+               return $retval;
+       }
+       
+       /**
+        * For faking prepared SQL statements on DBs that don't support
+        * it directly.
+        * @param string $preparedSql - a 'preparable' SQL statement
+        * @param array $args - array of arguments to fill it with
+        * @return string executable SQL
+        */
+       function fillPrepared( $preparedQuery, $args ) {
+               $n = 0;
+               reset( $args );
+               $this->preparedArgs =& $args;
+               return preg_replace_callback( '/(\\\\[?!&]|[?!&])/',
+                       array( &$this, 'fillPreparedArg' ), $preparedQuery );
+       }
+       
+       /**
+        * preg_callback func for fillPrepared()
+        * The arguments should be in $this->preparedArgs and must not be touched
+        * while we're doing this.
+        * 
+        * @param array $matches
+        * @return string
+        * @access private
+        */
+       function fillPreparedArg( $matches ) {
+               switch( $matches[1] ) {
+                       case '\\?': return '?';
+                       case '\\!': return '!';
+                       case '\\&': return '&';
+               }
+               list( $n, $arg ) = each( $this->preparedArgs );
+               switch( $matches[1] ) {
+                       case '?': return $this->addQuotes( $arg );
+                       case '!': return $arg;
+                       case '&':
+                               # return $this->addQuotes( file_get_contents( $arg ) );
+                               wfDebugDieBacktrace( '& mode is not implemented. If it\'s really needed, uncomment the line above.' );
+                       default:
+                               wfDebugDieBacktrace( 'Received invalid match. This should never happen!' );
+               }
+       }
+       
        /**#@+
         * @param mixed $res A SQL result
         */
diff --git a/tests/DatabaseTest.php b/tests/DatabaseTest.php
new file mode 100644 (file)
index 0000000..e62194f
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+
+require_once( 'PHPUnit.php' );
+require_once( '../includes/Defines.php' );
+require_once( '../includes/Database.php' );
+require_once( '../includes/GlobalFunctions.php' );
+
+class DatabaseTest extends PHPUnit_TestCase {
+       var $db;
+       
+       function DatabaseTest( $name ) {
+               $this->PHPUnit_TestCase( $name );
+       }
+       
+       function setUp() {
+               $this->db =& new Database();
+       }
+       
+       function tearDown() {
+               unset( $this->db );
+       }
+       
+       function testFillPreparedEmpty() {
+               $sql = $this->db->fillPrepared(
+                       'SELECT * FROM interwiki', array() );
+               $this->assertEquals(
+                       "SELECT * FROM interwiki",
+                       $sql);
+       }
+       
+       function testFillPreparedQuestion() {
+               $sql = $this->db->fillPrepared(
+                       'SELECT * FROM cur WHERE cur_namespace=? AND cur_title=?',
+                       array( 4, "Snicker's_paradox" ) );
+               $this->assertEquals(
+                       "SELECT * FROM cur WHERE cur_namespace='4' AND cur_title='Snicker\'s_paradox'",
+                       $sql);
+       }
+       
+       function testFillPreparedBang() {
+               $sql = $this->db->fillPrepared(
+                       'SELECT user_id FROM ! WHERE user_name=?',
+                       array( '"user"', "Slash's Dot" ) );
+               $this->assertEquals(
+                       "SELECT user_id FROM \"user\" WHERE user_name='Slash\'s Dot'",
+                       $sql);
+       }
+
+       function testFillPreparedRaw() {
+               $sql = $this->db->fillPrepared(
+                       "SELECT * FROM cur WHERE cur_title='This_\\&_that,_WTF\\?\\!'",
+                       array( '"user"', "Slash's Dot" ) );
+               $this->assertEquals(
+                       "SELECT * FROM cur WHERE cur_title='This_&_that,_WTF?!'",
+                       $sql);
+       }
+
+}
+
+?>
\ No newline at end of file
diff --git a/tests/RunTests.php b/tests/RunTests.php
new file mode 100644 (file)
index 0000000..d88dc80
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+error_reporting( E_ALL );
+define( "MEDIAWIKI", true );
+
+require_once( 'PHPUnit.php' );
+require_once( 'DatabaseTest.php' );
+
+$suite = new PHPUnit_TestSuite( "DatabaseTest" );
+$result = PHPUnit::run( $suite );
+echo $result->toString();
+
+?>
\ No newline at end of file