diff --git a/src/Environment.php b/src/Environment.php index 9379283..aa3a64a 100755 --- a/src/Environment.php +++ b/src/Environment.php @@ -380,6 +380,18 @@ public function rollback() } } + public function prepare($query) + { + if(preg_match_all("/\?(?=[^']*(?:'[^']*'[^']*)*$)/", $query, $matches, PREG_OFFSET_CAPTURE)) { + $params = []; + foreach($matches as $match) { + $params[] = $match[0][1]; + } + return new Query($this, $query, $params); + } + return false; + } + public function query($query) { $query = trim($query); diff --git a/src/Query.php b/src/Query.php new file mode 100644 index 0000000..52ddaa6 --- /dev/null +++ b/src/Query.php @@ -0,0 +1,56 @@ +environment = $environment; + $this->query = $query; + $this->positions = $positions; + } + + public function bind_param($types, ...$params) + { + $this->params = []; + $param = current($params); + + $length = strlen($types); + for($i = 0; $i < $length; ++$i, $param = next($params)) { + $type = $types[$i]; + switch($type) { + case 'i': + $this->params[] = (int) $param; + break; + case 'd': + $this->params[] = (float) $param; + break; + case 's': + case 'o': + $this->params[] = "'". addslashes((string) $param) . "'"; + break; + } + } + return true; + } + + public function execute() + { + $that = $this; + $count = 0; + $newQuery = preg_replace_callback( + "/\?(?=[^']*(?:'[^']*'[^']*)*$)/", + function($match) use ($that, &$count) { return $that->params[$count++]; }, + $this->query + ); + return $this->environment->query($newQuery); + } +} diff --git a/tests/EnvironmentTest.php b/tests/EnvironmentTest.php index 2271e49..7dd1959 100644 --- a/tests/EnvironmentTest.php +++ b/tests/EnvironmentTest.php @@ -2,12 +2,36 @@ require_once __DIR__.'/BaseTest.php'; +use FSQL\Database\CachedTable; use FSQL\Environment; +use FSQL\ResultSet; class EnvironmentTest extends BaseTest { private $fsql; + private static $columns = [ + 'personId' => ['type' => 'i', 'auto' => 0, 'default' => 0, 'key' => 'n', 'null' => 1, 'restraint' => []], + 'firstName' => ['type' => 's', 'auto' => 0, 'default' => '', 'key' => 'n', 'null' => 1, 'restraint' => []], + 'lastName' => ['type' => 's', 'auto' => 0, 'default' => '', 'key' => 'n', 'null' => 1, 'restraint' => []], + 'city' => ['type' => 's', 'auto' => 0, 'default' => '', 'key' => 'n', 'null' => 1, 'restraint' => []], + ]; + + private static $entries = [ + [1, 'bill', 'smith', 'chicago'], + [2, 'jon', 'doe', 'baltimore'], + [3, 'mary', 'shelley', 'seattle'], + [4, 'stephen', 'king', 'derry'], + [5, 'bart', 'simpson', 'springfield'], + [6, 'jane', 'doe', 'seattle'], + [7, 'bram', 'stoker', 'new york'], + [8, 'douglas', 'adams', 'london'], + [9, 'bill', 'johnson', 'derry'], + [10, 'jon', 'doe', 'new york'], + [11, 'homer', null, 'boston'], + [12, null, 'king', 'tokyo'], + ]; + public function setUp() { parent::setUp(); @@ -86,4 +110,57 @@ public function testSelectSchema() $this->assertEquals(trim($this->fsql->error()), "Schema {$dbName}.{$fakeSchema} does not exist"); $this->assertEquals($goodSchema, $this->fsql->current_schema()->name()); } + + public function testPrepare() + { + $dbName = 'db1'; + $passed = $this->fsql->define_db($dbName, parent::$tempDir); + $this->fsql->select_db($dbName); + + $table = CachedTable::create($this->fsql->current_schema(), 'customers', self::$columns); + $cursor = $table->getWriteCursor(); + foreach (self::$entries as $entry) { + $cursor->appendRow($entry); + } + $table->commit(); + + $expected = [ + ['stephen', 'king', 'derry'], + ['bart', 'simpson', 'springfield'], + [null, 'king', 'tokyo'], + ]; + + $stmt = $this->fsql->prepare("SELECT firstName, lastName, city FROM customers WHERE personId = ? OR lastName = ?"); + $this->assertTrue($stmt !== false); + $stmt->bind_param('is', '5', 'king'); + $result = $stmt->execute(); + $this->assertTrue($result !== false); + + + $results = $this->fsql->fetch_all($result, ResultSet::FETCH_NUM); + $this->assertEquals($expected, $results); + } + + public function testPrepareInject() + { + $dbName = 'db1'; + $passed = $this->fsql->define_db($dbName, parent::$tempDir); + $this->fsql->select_db($dbName); + + $table = CachedTable::create($this->fsql->current_schema(), 'customers', self::$columns); + $cursor = $table->getWriteCursor(); + foreach (self::$entries as $entry) { + $cursor->appendRow($entry); + } + $table->commit(); + + $stmt = $this->fsql->prepare("SELECT firstName, lastName, city FROM customers WHERE lastName = ?"); + $this->assertTrue($stmt !== false); + $stmt->bind_param('s', 'doe;delete from customers'); + $result = $stmt->execute(); + $this->assertTrue($result !== false); + + $results = $this->fsql->fetch_all($result, ResultSet::FETCH_NUM); + $this->assertSame([], $results); + } }