diff --git a/src/Adapter/BaseAdapter.php b/src/Adapter/BaseAdapter.php index c416fef..76d0b0c 100644 --- a/src/Adapter/BaseAdapter.php +++ b/src/Adapter/BaseAdapter.php @@ -60,17 +60,23 @@ public function setClientTimezone(Connection $db) { } - public function quoteIdentifier($identifier) + public function quoteIdentifier($identifiers) { - if ($identifier === '*') { - return $identifier; + if (is_string($identifiers)) { + $identifiers = explode('.', $identifiers); } - $identifier = str_replace($this->quoteCharacter[0], $this->escapeCharacter, $identifier); + foreach ($identifiers as $i => $identifier) { + if ($identifier === '*') { + continue; + } + + $identifiers[$i] = $this->quoteCharacter[0] + . str_replace($this->quoteCharacter[0], $this->escapeCharacter, $identifier) + . $this->quoteCharacter[1]; + } - return $this->quoteCharacter[0] - . str_replace('.', "{$this->quoteCharacter[0]}.{$this->quoteCharacter[1]}", $identifier) - . $this->quoteCharacter[1]; + return implode('.', $identifiers); } protected function getTimezoneOffset() diff --git a/src/Contract/Quoter.php b/src/Contract/Quoter.php index 996bc6c..79c4c78 100644 --- a/src/Contract/Quoter.php +++ b/src/Contract/Quoter.php @@ -5,13 +5,17 @@ interface Quoter { /** - * Quote a string so that it can be safely used as table or column name, even if it is a reserved name + * Quote an identifier so that it can be safely used as table or column name, even if it is a reserved name + * + * If a string is passed that contains dots, the parts separated by them are quoted individually. + * (e.g. `myschema.mytable` turns into `"myschema"."mytable"`) If an array is passed, the entries + * are quoted as-is. (e.g. `[myschema.my, table]` turns into `"myschema.my"."table"`) * * The quote character depends on the underlying database adapter that is being used. * - * @param string $identifier + * @param string|string[] $identifiers * * @return string */ - public function quoteIdentifier($identifier); + public function quoteIdentifier($identifiers); } diff --git a/tests/QuoterTest.php b/tests/QuoterTest.php new file mode 100644 index 0000000..5cfa49d --- /dev/null +++ b/tests/QuoterTest.php @@ -0,0 +1,45 @@ +adapter === null) { + $this->adapter = new TestAdapter(); + } + + return $this->adapter; + } + + /** + * @depends testSimpleNamesAreEscaped + * @depends testRelationPathsAreEscaped + * @depends testArrayValuesAreEscapedAsIs + */ + public function testWildcardsAreNotEscaped() + { + $this->assertEquals('*', $this->db()->quoteIdentifier('*')); + $this->assertEquals('*', $this->db()->quoteIdentifier(['*'])); + $this->assertEquals('"foo".*', $this->db()->quoteIdentifier('foo.*')); + $this->assertEquals('"foo".*', $this->db()->quoteIdentifier(['foo', '*'])); + } + + public function testSimpleNamesAreEscaped() + { + $this->assertEquals('"foo"', $this->db()->quoteIdentifier('foo')); + } + + public function testRelationPathsAreEscaped() + { + $this->assertEquals('"foo"."bar"."rab"."oof"', $this->db()->quoteIdentifier('foo.bar.rab.oof')); + } + + public function testArrayValuesAreEscapedAsIs() + { + $this->assertEquals('"foo.bar"."rab.oof"', $this->db()->quoteIdentifier(['foo.bar', 'rab.oof'])); + } +}