From 881b36f3162301604a85fdfb28a1f8f022383fd2 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 18 Oct 2004 07:25:56 +0000 Subject: [PATCH] * Add support for prepared statements. This should be safer than dumping variables into raw SQL and more flexible than the array-based wrapper functions * Add some quick PHPUnit tests for Database::fillPrepared() --- includes/Database.php | 94 ++++++++++++++++++++++++++++++++++++++++++ tests/DatabaseTest.php | 60 +++++++++++++++++++++++++++ tests/RunTests.php | 12 ++++++ 3 files changed, 166 insertions(+) create mode 100644 tests/DatabaseTest.php create mode 100644 tests/RunTests.php diff --git a/includes/Database.php b/includes/Database.php index 982e81c3de..0360ee0ee0 100644 --- a/includes/Database.php +++ b/includes/Database.php @@ -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 index 0000000000..e62194feb6 --- /dev/null +++ b/tests/DatabaseTest.php @@ -0,0 +1,60 @@ +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 index 0000000000..d88dc8020f --- /dev/null +++ b/tests/RunTests.php @@ -0,0 +1,12 @@ +toString(); + +?> \ No newline at end of file -- 2.20.1