ENHANCEMENT: Change MySQLDatabase connection to operate in ANSI SQL mode, to ease the transition to DB abstraction

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@66399 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Sam Minnee 2008-11-22 03:51:04 +00:00
parent 19a891984f
commit 12e62c6329
8 changed files with 48 additions and 42 deletions

View File

@ -55,6 +55,8 @@ if(isset($_SERVER['argv'][2])) {
*/ */
require_once("core/Core.php"); require_once("core/Core.php");
global $databcaseConfig;
// We don't have a session in cli-script, but this prevents errors // We don't have a session in cli-script, but this prevents errors
$_SESSION = null; $_SESSION = null;

View File

@ -743,7 +743,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
if((!isset($this->record['ID']) || !$this->record['ID']) && isset($ancestry[0])) { if((!isset($this->record['ID']) || !$this->record['ID']) && isset($ancestry[0])) {
$baseTable = $ancestry[0]; $baseTable = $ancestry[0];
DB::query("INSERT INTO `{$baseTable}` SET Created = NOW()"); DB::query("INSERT INTO \"{$baseTable}\" SET Created = NOW()");
$this->record['ID'] = DB::getGeneratedID($baseTable); $this->record['ID'] = DB::getGeneratedID($baseTable);
$this->changed['ID'] = 2; $this->changed['ID'] = 2;
@ -880,7 +880,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
if(self::has_own_table($ancestor)) { if(self::has_own_table($ancestor)) {
$sql = new SQLQuery(); $sql = new SQLQuery();
$sql->delete = true; $sql->delete = true;
$sql->from[$ancestor] = "`$ancestor`"; $sql->from[$ancestor] = "\"$ancestor\"";
$sql->where[] = "ID = $this->ID"; $sql->where[] = "ID = $this->ID";
$this->extend('augmentSQL', $sql); $this->extend('augmentSQL', $sql);
$sql->execute(); $sql->execute();
@ -1171,12 +1171,12 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
$query = $componentObj->extendedSQL( $query = $componentObj->extendedSQL(
"`$table`.$parentField = $this->ID", // filter "\"$table\".$parentField = $this->ID", // filter
$sort, $sort,
$limit, $limit,
"INNER JOIN `$table` ON `$table`.$componentField = `$componentBaseClass`.ID" // join "INNER JOIN \"$table\" ON \"$table\".$componentField = \"$componentBaseClass\".ID" // join
); );
array_unshift($query->select, "`$table`.*"); array_unshift($query->select, "\"$table\".*");
if($filter) $query->where[] = $filter; if($filter) $query->where[] = $filter;
if($join) $query->from[] = $join; if($join) $query->from[] = $join;
@ -1218,16 +1218,16 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
$baseComponentClass = ClassInfo::baseDataClass($componentClass); $baseComponentClass = ClassInfo::baseDataClass($componentClass);
if($baseTable == $parentClass) { if($baseTable == $parentClass) {
return "LEFT JOIN `$table` ON (`$table`.`$parentField` = `$parentClass`.`ID` AND `$table`.`$componentField` = '{$this->ID}')"; return "LEFT JOIN \"$table\" ON (\"$table\".\"$parentField\" = \"$parentClass\".\"ID\" AND \"$table\".\"$componentField\" = '{$this->ID}')";
} else { } else {
return "LEFT JOIN `$table` ON (`$table`.`$componentField` = `$baseComponentClass`.`ID` AND `$table`.`$parentField` = '{$this->ID}')"; return "LEFT JOIN \"$table\" ON (\"$table\".\"$componentField\" = \"$baseComponentClass\".\"ID\" AND \"$table\".\"$parentField\" = '{$this->ID}')";
} }
} }
function getManyManyFilter($componentName, $baseTable) { function getManyManyFilter($componentName, $baseTable) {
list($parentClass, $componentClass, $parentField, $componentField, $table) = $this->many_many($componentName); list($parentClass, $componentClass, $parentField, $componentField, $table) = $this->many_many($componentName);
return "`$table`.`$parentField` = '{$this->ID}'"; return "\"$table\".\"$parentField\" = '{$this->ID}'";
} }
/** /**
@ -1865,17 +1865,17 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
$groupList = implode(', ', $groups->column("ID")); $groupList = implode(', ', $groups->column("ID"));
$query = new SQLQuery( $query = new SQLQuery(
"`Page_Can$perm`.PageID", "\"Page_Can$perm\".PageID",
array("`Page_Can$perm`"), array("\"Page_Can$perm\""),
"GroupID IN ($groupList)"); "GroupID IN ($groupList)");
$permissionCache[$memberID][$perm] = $query->execute()->column(); $permissionCache[$memberID][$perm] = $query->execute()->column();
if($perm == "View") { if($perm == "View") {
$query = new SQLQuery("`SiteTree`.ID", array( $query = new SQLQuery("\"SiteTree\".ID", array(
"`SiteTree`", "\"SiteTree\"",
"LEFT JOIN `Page_CanView` ON `Page_CanView`.PageID = `SiteTree`.ID" "LEFT JOIN \"Page_CanView\" ON \"Page_CanView\".PageID = \"SiteTree\".ID"
), "`Page_CanView`.PageID IS NULL"); ), "\"Page_CanView\".PageID IS NULL");
$unsecuredPages = $query->execute()->column(); $unsecuredPages = $query->execute()->column();
if($permissionCache[$memberID][$perm]) { if($permissionCache[$memberID][$perm]) {
@ -2070,11 +2070,11 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
} }
$baseClass = array_shift($tableClasses); $baseClass = array_shift($tableClasses);
$select = array("`$baseClass`.*"); $select = array("\"$baseClass\".*");
// Build our intial query // Build our intial query
$query = new SQLQuery($select); $query = new SQLQuery($select);
$query->from("`$baseClass`"); $query->from("\"$baseClass\"");
$query->where($filter); $query->where($filter);
$query->orderby($sort); $query->orderby($sort);
$query->limit($limit); $query->limit($limit);
@ -2091,8 +2091,8 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
// Join all the tables // Join all the tables
if($tableClasses && self::$subclass_access) { if($tableClasses && self::$subclass_access) {
foreach($tableClasses as $tableClass) { foreach($tableClasses as $tableClass) {
$query->from[$tableClass] = "LEFT JOIN `$tableClass` ON `$tableClass`.ID = `$baseClass`.ID"; $query->from[$tableClass] = "LEFT JOIN \"$tableClass\" ON \"$tableClass\".ID = \"$baseClass\".ID";
$query->select[] = "`$tableClass`.*"; $query->select[] = "\"$tableClass\".*";
// Add SQL for multi-value fields // Add SQL for multi-value fields
$SNG = singleton($tableClass); $SNG = singleton($tableClass);
@ -2107,8 +2107,8 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
} }
} }
$query->select[] = "`$baseClass`.ID"; $query->select[] = "\"$baseClass\".ID";
$query->select[] = "if(`$baseClass`.ClassName,`$baseClass`.ClassName,'$baseClass') AS RecordClassName"; $query->select[] = "if(\"$baseClass\".ClassName,\"$baseClass\".ClassName,'$baseClass') AS RecordClassName";
// Get the ClassName values to filter to // Get the ClassName values to filter to
$classNames = ClassInfo::subclassesFor($this->class); $classNames = ClassInfo::subclassesFor($this->class);
@ -2125,7 +2125,7 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
user_error("DataObject::get() Can't find data sub-classes for '$callerClass'"); user_error("DataObject::get() Can't find data sub-classes for '$callerClass'");
} }
$query->where[] = "`$baseClass`.ClassName IN ('" . implode("','", $classNames) . "')"; $query->where[] = "\"$baseClass\".ClassName IN ('" . implode("','", $classNames) . "')";
} }
if($having) { if($having) {
@ -2352,11 +2352,11 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
if(singleton($callerClass) instanceof DataObject) { if(singleton($callerClass) instanceof DataObject) {
$tableClasses = ClassInfo::dataClassesFor($callerClass); $tableClasses = ClassInfo::dataClassesFor($callerClass);
$baseClass = array_shift($tableClasses); $baseClass = array_shift($tableClasses);
return DataObject::get_one($callerClass,"`$baseClass`.`ID` = $id"); return DataObject::get_one($callerClass,"\"$baseClass\".\"ID\" = $id");
// This simpler code will be used by non-DataObject classes that implement DataObjectInterface // This simpler code will be used by non-DataObject classes that implement DataObjectInterface
} else { } else {
return DataObject::get_one($callerClass,"`ID` = $id"); return DataObject::get_one($callerClass,"\"ID\" = $id");
} }
} else { } else {
user_error("DataObject::get_by_id passed a non-numeric ID #$id", E_USER_WARNING); user_error("DataObject::get_by_id passed a non-numeric ID #$id", E_USER_WARNING);

View File

@ -39,6 +39,8 @@ class MySQLDatabase extends Database {
$this->databaseError("Couldn't connect to MySQL database"); $this->databaseError("Couldn't connect to MySQL database");
} }
$this->query("SET sql_mode = 'ANSI'");
parent::__construct(); parent::__construct();
} }

View File

@ -85,7 +85,7 @@ class SQLQuery extends Object {
function __construct($select = "*", $from = array(), $where = "", $orderby = "", $groupby = "", $having = "", $limit = "") { function __construct($select = "*", $from = array(), $where = "", $orderby = "", $groupby = "", $having = "", $limit = "") {
$this->select($select); $this->select($select);
// @todo // @todo
$this->from = is_array($from) ? $from : array(str_replace('`','',$from) => $from); $this->from = is_array($from) ? $from : array(str_replace(array('"','`'),'',$from) => $from);
$this->where($where); $this->where($where);
$this->orderby($orderby); $this->orderby($orderby);
$this->groupby($groupby); $this->groupby($groupby);
@ -130,7 +130,7 @@ class SQLQuery extends Object {
* @return SQLQuery This instance * @return SQLQuery This instance
*/ */
public function from($table) { public function from($table) {
$this->from[str_replace('`','',$table)] = $table; $this->from[str_replace(array('"','`'),'',$table)] = $table;
return $this; return $this;
} }
@ -141,7 +141,7 @@ class SQLQuery extends Object {
* @return SQLQuery This instance * @return SQLQuery This instance
*/ */
public function leftJoin($table, $onPredicate) { public function leftJoin($table, $onPredicate) {
$this->from[$table] = "LEFT JOIN `$table` ON $onPredicate"; $this->from[$table] = "LEFT JOIN \"$table\" ON $onPredicate";
return $this; return $this;
} }
@ -205,9 +205,9 @@ class SQLQuery extends Object {
if(!array_key_exists('sort', $orderby)) user_error('SQLQuery::orderby(): Wrong format for $orderby array', E_USER_ERROR); if(!array_key_exists('sort', $orderby)) user_error('SQLQuery::orderby(): Wrong format for $orderby array', E_USER_ERROR);
if(isset($orderby['sort']) && !empty($orderby['sort']) && isset($orderby['dir']) && !empty($orderby['dir'])) { if(isset($orderby['sort']) && !empty($orderby['sort']) && isset($orderby['dir']) && !empty($orderby['dir'])) {
$combinedOrderby = "`" . Convert::raw2sql($orderby['sort']) . "` " . Convert::raw2sql(strtoupper($orderby['dir'])); $combinedOrderby = "\"" . Convert::raw2sql($orderby['sort']) . "\" " . Convert::raw2sql(strtoupper($orderby['dir']));
} elseif(isset($orderby['sort']) && !empty($orderby['sort'])) { } elseif(isset($orderby['sort']) && !empty($orderby['sort'])) {
$combinedOrderby = "`" . Convert::raw2sql($orderby['sort']) . "`"; $combinedOrderby = "\"" . Convert::raw2sql($orderby['sort']) . "\"";
} else { } else {
$combinedOrderby = false; $combinedOrderby = false;
} }
@ -344,6 +344,7 @@ class SQLQuery extends Object {
*/ */
function renameTable($old, $new) { function renameTable($old, $new) {
$this->replaceText("`$old`", "`$new`"); $this->replaceText("`$old`", "`$new`");
$this->replaceText("\"$old\"", "\"$new\"");
} }
/** /**
@ -433,7 +434,7 @@ class SQLQuery extends Object {
*/ */
function filtersOnID() { function filtersOnID() {
return ($this->where && count($this->where) == 1 && return ($this->where && count($this->where) == 1 &&
(strpos($this->where[0], ".`ID` = ") || strpos($this->where[0], ".ID = ") || strpos($this->where[0], "ID = ") ) (strpos($query->where[0], ".\"ID\" = ") || strpos($query->where[0], ".`ID` = ") || strpos($query->where[0], ".ID = ") || strpos($query->where[0], "ID = ") )
); );
} }

View File

@ -57,7 +57,7 @@ class DataObjectTest extends SapphireTest {
$this->assertEquals(8, $comments->Count()); $this->assertEquals(8, $comments->Count());
// Test WHERE clause // Test WHERE clause
$comments = DataObject::get('PageComment', 'Name="Bob"'); $comments = DataObject::get('PageComment', "Name='Bob'");
$this->assertEquals(2, $comments->Count()); $this->assertEquals(2, $comments->Count());
foreach($comments as $comment) { foreach($comments as $comment) {
$this->assertEquals('Bob', $comment->Name); $this->assertEquals('Bob', $comment->Name);
@ -72,7 +72,7 @@ class DataObjectTest extends SapphireTest {
$this->assertEquals('Joe', $comments->First()->Name); $this->assertEquals('Joe', $comments->First()->Name);
// Test join // Test join
$comments = DataObject::get('PageComment', '`SiteTree`.Title="First Page"', '', 'INNER JOIN SiteTree ON PageComment.ParentID = SiteTree.ID'); $comments = DataObject::get('PageComment', "`SiteTree`.Title='First Page'", '', 'INNER JOIN SiteTree ON PageComment.ParentID = SiteTree.ID');
$this->assertEquals(2, $comments->Count()); $this->assertEquals(2, $comments->Count());
$this->assertEquals('Bob', $comments->First()->Name); $this->assertEquals('Bob', $comments->First()->Name);
$this->assertEquals('Bob', $comments->Last()->Name); $this->assertEquals('Bob', $comments->Last()->Name);
@ -100,15 +100,15 @@ class DataObjectTest extends SapphireTest {
$this->assertEquals($homepageID, $page->ID); $this->assertEquals($homepageID, $page->ID);
// Test get_one() without caching // Test get_one() without caching
$comment1 = DataObject::get_one('PageComment', 'Name="Joe"', false); $comment1 = DataObject::get_one('PageComment', "Name='Joe'", false);
$comment1->Comment = "Something Else"; $comment1->Comment = "Something Else";
$comment2 = DataObject::get_one('PageComment', 'Name="Joe"', false); $comment2 = DataObject::get_one('PageComment', "Name='Joe'", false);
$this->assertNotEquals($comment1->Comment, $comment2->Comment); $this->assertNotEquals($comment1->Comment, $comment2->Comment);
// Test get_one() with caching // Test get_one() with caching
$comment1 = DataObject::get_one('PageComment', 'Name="Jane"', true); $comment1 = DataObject::get_one('PageComment', "Name='Jane'", true);
$comment1->Comment = "Something Else"; $comment1->Comment = "Something Else";
$comment2 = DataObject::get_one('PageComment', 'Name="Jane"', true); $comment2 = DataObject::get_one('PageComment', "Name='Jane'", true);
$this->assertEquals((string)$comment1->Comment, (string)$comment2->Comment); $this->assertEquals((string)$comment1->Comment, (string)$comment2->Comment);
// Test get_one() with order by without caching // Test get_one() with order by without caching

View File

@ -100,13 +100,13 @@ class SQLQueryTest extends SapphireTest {
$query = new SQLQuery(); $query = new SQLQuery();
$query->from[] = "MyTable"; $query->from[] = "MyTable";
$query->orderby(array('sort'=>'MyName')); $query->orderby(array('sort'=>'MyName'));
$this->assertEquals("SELECT * FROM MyTable ORDER BY `MyName`", $query->sql()); $this->assertEquals('SELECT * FROM MyTable ORDER BY "MyName"', $query->sql());
// array limit with start (MySQL specific) // array limit with start (MySQL specific)
$query = new SQLQuery(); $query = new SQLQuery();
$query->from[] = "MyTable"; $query->from[] = "MyTable";
$query->orderby(array('sort'=>'MyName','dir'=>'desc')); $query->orderby(array('sort'=>'MyName','dir'=>'desc'));
$this->assertEquals("SELECT * FROM MyTable ORDER BY `MyName` DESC", $query->sql()); $this->assertEquals('SELECT * FROM MyTable ORDER BY "MyName" DESC', $query->sql());
} }
function testSelectWithComplexOrderbyClause() { function testSelectWithComplexOrderbyClause() {

View File

@ -44,6 +44,7 @@ class RequirementsTest extends SapphireTest {
$html = Requirements::includeInHTML(false, self::$html_template); $html = Requirements::includeInHTML(false, self::$html_template);
/* COMBINED JAVASCRIPT FILE IS INCLUDED IN HTML HEADER */ /* COMBINED JAVASCRIPT FILE IS INCLUDED IN HTML HEADER */
Debug::message($html);
$this->assertTrue((bool)preg_match('/src=".*\/RequirementsTest_bc\.js/', $html), 'combined javascript file is included in html header'); $this->assertTrue((bool)preg_match('/src=".*\/RequirementsTest_bc\.js/', $html), 'combined javascript file is included in html header');
/* COMBINED JAVASCRIPT FILE EXISTS */ /* COMBINED JAVASCRIPT FILE EXISTS */

View File

@ -151,16 +151,16 @@ class SecurityTest extends FunctionalTest {
/* UNSUCCESSFUL ATTEMPTS WITH WRONG PASSWORD FOR EXISTING USER ARE LOGGED */ /* UNSUCCESSFUL ATTEMPTS WITH WRONG PASSWORD FOR EXISTING USER ARE LOGGED */
$this->doTestLoginForm('sam@silverstripe.com', 'wrongpassword'); $this->doTestLoginForm('sam@silverstripe.com', 'wrongpassword');
$attempt = DataObject::get_one('LoginAttempt', 'Email = "sam@silverstripe.com"'); $attempt = DataObject::get_one('LoginAttempt', "Email = 'sam@silverstripe.com'");
$this->assertTrue(is_object($attempt)); $this->assertTrue(is_object($attempt));
$member = DataObject::get_one('Member', 'Email = "sam@silverstripe.com"'); $member = DataObject::get_one('Member', "Email = 'sam@silverstripe.com'");
$this->assertEquals($attempt->Status, 'Failure'); $this->assertEquals($attempt->Status, 'Failure');
$this->assertEquals($attempt->Email, 'sam@silverstripe.com'); $this->assertEquals($attempt->Email, 'sam@silverstripe.com');
$this->assertEquals($attempt->Member(), $member); $this->assertEquals($attempt->Member(), $member);
/* UNSUCCESSFUL ATTEMPTS WITH NONEXISTING USER ARE LOGGED */ /* UNSUCCESSFUL ATTEMPTS WITH NONEXISTING USER ARE LOGGED */
$this->doTestLoginForm('wronguser@silverstripe.com', 'wrongpassword'); $this->doTestLoginForm('wronguser@silverstripe.com', 'wrongpassword');
$attempt = DataObject::get_one('LoginAttempt', 'Email = "wronguser@silverstripe.com"'); $attempt = DataObject::get_one('LoginAttempt', "Email = 'wronguser@silverstripe.com'");
$this->assertTrue(is_object($attempt)); $this->assertTrue(is_object($attempt));
$this->assertEquals($attempt->Status, 'Failure'); $this->assertEquals($attempt->Status, 'Failure');
$this->assertEquals($attempt->Email, 'wronguser@silverstripe.com'); $this->assertEquals($attempt->Email, 'wronguser@silverstripe.com');
@ -171,8 +171,8 @@ class SecurityTest extends FunctionalTest {
/* SUCCESSFUL ATTEMPTS ARE LOGGED */ /* SUCCESSFUL ATTEMPTS ARE LOGGED */
$this->doTestLoginForm('sam@silverstripe.com', '1nitialPassword'); $this->doTestLoginForm('sam@silverstripe.com', '1nitialPassword');
$attempt = DataObject::get_one('LoginAttempt', 'Email = "sam@silverstripe.com"'); $attempt = DataObject::get_one('LoginAttempt', "Email = 'sam@silverstripe.com'");
$member = DataObject::get_one('Member', 'Email = "sam@silverstripe.com"'); $member = DataObject::get_one('Member', "Email = 'sam@silverstripe.com'");
$this->assertTrue(is_object($attempt)); $this->assertTrue(is_object($attempt));
$this->assertEquals($attempt->Status, 'Success'); $this->assertEquals($attempt->Status, 'Success');
$this->assertEquals($attempt->Email, 'sam@silverstripe.com'); $this->assertEquals($attempt->Email, 'sam@silverstripe.com');