mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-09-28 20:29:15 +02:00
Merge remote-tracking branch 'origin/3.2' into 3.3
# Conflicts: # tests/model/DataObjectLazyLoadingTest.php # tests/model/VersionedTest.yml
This commit is contained in:
commit
7c448bb4a2
@ -59,6 +59,48 @@ The relationship can also be navigated in [templates](../templates).
|
||||
<% end_if %>
|
||||
<% end_with %>
|
||||
|
||||
## Polymorphic has_one
|
||||
|
||||
A has_one can also be polymorphic, which allows any type of object to be associated.
|
||||
This is useful where there could be many use cases for a particular data structure.
|
||||
|
||||
An additional column is created called "`<relationship-name>`Class", which along
|
||||
with the ID column identifies the object.
|
||||
|
||||
To specify that a has_one relation is polymorphic set the type to 'DataObject'.
|
||||
Ideally, the associated has_many (or belongs_to) should be specified with dot notation.
|
||||
|
||||
::php
|
||||
|
||||
class Player extends DataObject {
|
||||
private static $has_many = array(
|
||||
"Fans" => "Fan.FanOf"
|
||||
);
|
||||
}
|
||||
|
||||
class Team extends DataObject {
|
||||
private static $has_many = array(
|
||||
"Fans" => "Fan.FanOf"
|
||||
);
|
||||
}
|
||||
|
||||
// Type of object returned by $fan->FanOf() will vary
|
||||
class Fan extends DataObject {
|
||||
|
||||
// Generates columns FanOfID and FanOfClass
|
||||
private static $has_one = array(
|
||||
"FanOf" => "DataObject"
|
||||
);
|
||||
}
|
||||
|
||||
<div class="warning" markdown='1'>
|
||||
Note: The use of polymorphic relationships can affect query performance, especially
|
||||
on joins, and also increases the complexity of the database and necessary user code.
|
||||
They should be used sparingly, and only where additional complexity would otherwise
|
||||
be necessary. E.g. Additional parent classes for each respective relationship, or
|
||||
duplication of code.
|
||||
</div>
|
||||
|
||||
## has_many
|
||||
|
||||
Defines 1-to-many joins. As you can see from the previous example, `$has_many` goes hand in hand with `$has_one`.
|
||||
|
@ -1,13 +1,22 @@
|
||||
title: SQLQuery
|
||||
summary: Write and modify direct database queries through SQLQuery.
|
||||
title: SQL Queries
|
||||
summary: Write and modify direct database queries through SQLExpression subclasses.
|
||||
|
||||
# SQLQuery
|
||||
# SQLSelect
|
||||
|
||||
A [api:SQLQuery] object represents a SQL query, which can be serialized into a SQL statement. Dealing with low-level
|
||||
SQL such as `mysql_query()` is not encouraged, since the ORM provides powerful abstraction API's.
|
||||
## Introduction
|
||||
|
||||
For example, if you want to run a simple `COUNT` SQL statement, the following three statements are functionally
|
||||
equivalent:
|
||||
An object representing a SQL select query, which can be serialized into a SQL statement.
|
||||
It is easier to deal with object-wrappers than string-parsing a raw SQL-query.
|
||||
This object is used by the SilverStripe ORM internally.
|
||||
|
||||
Dealing with low-level SQL is not encouraged, since the ORM provides
|
||||
powerful abstraction APIs (see [datamodel](/developer_guides/data_model_and_orm).
|
||||
Starting with SilverStripe 3, records in collections are lazy loaded,
|
||||
and these collections have the ability to run efficient SQL
|
||||
such as counts or returning a single column.
|
||||
|
||||
For example, if you want to run a simple `COUNT` SQL statement,
|
||||
the following three statements are functionally equivalent:
|
||||
|
||||
:::php
|
||||
// Through raw SQL.
|
||||
@ -20,95 +29,254 @@ equivalent:
|
||||
// Through the ORM.
|
||||
$count = Member::get()->count();
|
||||
|
||||
If you do use raw SQL, you'll run the risk of breaking
|
||||
various assumptions the ORM and code based on it have:
|
||||
|
||||
<div class="info">
|
||||
The SQLQuery object is used by the SilverStripe ORM internally. By understanding SQLQuery, you can modify the SQL that
|
||||
the ORM creates.
|
||||
* Custom getters/setters (object property can differ from database column)
|
||||
* DataObject hooks like onBeforeWrite() and onBeforeDelete()
|
||||
* Automatic casting
|
||||
* Default values set through objects
|
||||
* Database abstraction
|
||||
|
||||
We'll explain some ways to use *SELECT* with the full power of SQL,
|
||||
but still maintain a connection to the ORM where possible.
|
||||
|
||||
<div class="warning" markdown="1">
|
||||
Please read our [security topic](/developer_guides/security) to find out
|
||||
how to properly prepare user input and variables for use in queries
|
||||
</div>
|
||||
|
||||
## Usage
|
||||
|
||||
### Select
|
||||
### SELECT
|
||||
|
||||
Selection can be done by creating an instance of `SQLSelect`, which allows
|
||||
management of all elements of a SQL SELECT query, including columns, joined tables,
|
||||
conditional filters, grouping, limiting, and sorting.
|
||||
|
||||
E.g.
|
||||
|
||||
:::php
|
||||
$sqlQuery = new SQLQuery();
|
||||
<?php
|
||||
|
||||
$sqlQuery = new SQLSelect();
|
||||
$sqlQuery->setFrom('Player');
|
||||
$sqlQuery->selectField('FieldName', 'Name');
|
||||
$sqlQuery->selectField('YEAR("Birthday")', 'Birthyear');
|
||||
$sqlQuery->addLeftJoin('Team','"Player"."TeamID" = "Team"."ID"');
|
||||
$sqlQuery->addWhere('YEAR("Birthday") = 1982');
|
||||
|
||||
$sqlQuery->addWhere(array('YEAR("Birthday") = ?' => 1982));
|
||||
// $sqlQuery->setOrderBy(...);
|
||||
// $sqlQuery->setGroupBy(...);
|
||||
// $sqlQuery->setHaving(...);
|
||||
// $sqlQuery->setLimit(...);
|
||||
// $sqlQuery->setDistinct(true);
|
||||
|
||||
// Get the raw SQL (optional)
|
||||
$rawSQL = $sqlQuery->sql();
|
||||
// Get the raw SQL (optional) and parameters
|
||||
$rawSQL = $sqlQuery->sql($parameters);
|
||||
|
||||
// Execute and return a Query object
|
||||
$result = $sqlQuery->execute();
|
||||
|
||||
// Iterate over results
|
||||
foreach($result as $row) {
|
||||
echo $row['BirthYear'];
|
||||
echo $row['BirthYear'];
|
||||
}
|
||||
|
||||
The `$result` is an array lightly wrapped in a database-specific subclass of `[api:Query]`. This class implements the
|
||||
*Iterator*-interface, and provides convenience-methods for accessing the data.
|
||||
The result of `SQLSelect::execute()` is an array lightly wrapped in a database-specific subclass of `[api:SS_Query]`.
|
||||
This class implements the *Iterator*-interface, and provides convenience-methods for accessing the data.
|
||||
|
||||
### Delete
|
||||
### DELETE
|
||||
|
||||
Deletion can be done either by calling `DB::query`/`DB::prepared_query` directly,
|
||||
by creating a `SQLDelete` object, or by transforming a `SQLQuery` into a `SQLDelete`
|
||||
object instead.
|
||||
|
||||
For example, creating a `SQLDelete` object
|
||||
|
||||
:::php
|
||||
$sqlQuery->setDelete(true);
|
||||
<?php
|
||||
|
||||
### Insert / Update
|
||||
$query = SQLDelete::create()
|
||||
->setFrom('"SiteTree"')
|
||||
->setWhere(array('"SiteTree"."ShowInMenus"' => 0));
|
||||
$query->execute();
|
||||
|
||||
<div class="alert" markdown="1">
|
||||
Currently not supported through the `SQLQuery` class, please use raw `DB::query()` calls instead.
|
||||
</div>
|
||||
Alternatively, turning an existing `SQLQuery` into a delete
|
||||
|
||||
:::php
|
||||
DB::query('UPDATE "Player" SET "Status"=\'Active\'');
|
||||
<?php
|
||||
|
||||
### Joins
|
||||
$query = SQLQuery::create()
|
||||
->setFrom('"SiteTree"')
|
||||
->setWhere(array('"SiteTree"."ShowInMenus"' => 0))
|
||||
->toDelete();
|
||||
$query->execute();
|
||||
|
||||
Directly querying the database
|
||||
|
||||
:::php
|
||||
<?php
|
||||
|
||||
DB::prepared_query('DELETE FROM "SiteTree" WHERE "SiteTree"."ShowInMenus" = ?', array(0));
|
||||
|
||||
### INSERT/UPDATE
|
||||
|
||||
INSERT and UPDATE can be performed using the `SQLInsert` and `SQLUpdate` classes.
|
||||
These both have similar aspects in that they can modify content in
|
||||
the database, but each are different in the way in which they behave.
|
||||
|
||||
Previously, similar operations could be performed by using the `DB::manipulate`
|
||||
function which would build the INSERT and UPDATE queries on the fly. This method
|
||||
still exists, but internally uses `SQLUpdate` / `SQLInsert`, although the actual
|
||||
query construction is now done by the `DBQueryBuilder` object.
|
||||
|
||||
Each of these classes implements the interface `SQLWriteExpression`, noting that each
|
||||
accepts write key/value pairs in a number of similar ways. These include the following
|
||||
api methods:
|
||||
|
||||
* `addAssignments` - Takes a list of assignments as an associative array of key -> value pairs,
|
||||
but also supports SQL expressions as values if necessary.
|
||||
* `setAssignments` - Replaces all existing assignments with the specified list
|
||||
* `getAssignments` - Returns all currently given assignments, as an associative array
|
||||
in the format `array('Column' => array('SQL' => array('parameters)))`
|
||||
* `assign` - Singular form of addAssignments, but only assigns a single column value.
|
||||
* `assignSQL` - Assigns a column the value of a specified SQL expression without parameters
|
||||
`assignSQL('Column', 'SQL)` is shorthand for `assign('Column', array('SQL' => array()))`
|
||||
|
||||
SQLUpdate also includes the following api methods:
|
||||
|
||||
* `clear` - Clears all assignments
|
||||
* `getTable` - Gets the table to update
|
||||
* `setTable` - Sets the table to update. This should be ANSI quoted.
|
||||
E.g. `$query->setTable('"SiteTree"');`
|
||||
|
||||
SQLInsert also includes the following api methods:
|
||||
* `clear` - Clears all rows
|
||||
* `clearRow` - Clears all assignments on the current row
|
||||
* `addRow` - Adds another row of assignments, and sets the current row to the new row
|
||||
* `addRows` - Adds a number of arrays, each representing a list of assignment rows,
|
||||
and sets the current row to the last one.
|
||||
* `getColumns` - Gets the names of all distinct columns assigned
|
||||
* `getInto` - Gets the table to insert into
|
||||
* `setInto` - Sets the table to insert into. This should be ANSI quoted.
|
||||
E.g. `$query->setInto('"SiteTree"');`
|
||||
|
||||
E.g.
|
||||
|
||||
:::php
|
||||
<?php
|
||||
$update = SQLUpdate::create('"SiteTree"')->where(array('ID' => 3));
|
||||
|
||||
// assigning a list of items
|
||||
$update->addAssignments(array(
|
||||
'"Title"' => 'Our Products',
|
||||
'"MenuTitle"' => 'Products'
|
||||
));
|
||||
|
||||
// Assigning a single value
|
||||
$update->assign('"MenuTitle"', 'Products');
|
||||
|
||||
// Assigning a value using parameterised expression
|
||||
$title = 'Products';
|
||||
$update->assign('"MenuTitle"', array(
|
||||
'CASE WHEN LENGTH("MenuTitle") > LENGTH(?) THEN "MenuTitle" ELSE ? END' =>
|
||||
array($title, $title)
|
||||
));
|
||||
|
||||
// Assigning a value using a pure SQL expression
|
||||
$update->assignSQL('"Date"', 'NOW()');
|
||||
|
||||
// Perform the update
|
||||
$update->execute();
|
||||
|
||||
In addition to assigning values, the SQLInsert object also supports multi-row
|
||||
inserts. For database connectors and API that don't have multi-row insert support
|
||||
these are translated internally as multiple single row inserts.
|
||||
|
||||
For example,
|
||||
|
||||
:::php
|
||||
<?php
|
||||
$insert = SQLInsert::create('"SiteTree"');
|
||||
|
||||
// Add multiple rows in a single call. Note that column names do not need
|
||||
// to be symmetric
|
||||
$insert->addRows(array(
|
||||
array('"Title"' => 'Home', '"Content"' => '<p>This is our home page</p>'),
|
||||
array('"Title"' => 'About Us', '"ClassName"' => 'AboutPage')
|
||||
));
|
||||
|
||||
// Adjust an assignment on the last row
|
||||
$insert->assign('"Content"', '<p>This is about us</p>');
|
||||
|
||||
// Add another row
|
||||
$insert->addRow(array('"Title"' => 'Contact Us'));
|
||||
|
||||
$columns = $insert->getColumns();
|
||||
// $columns will be array('"Title"', '"Content"', '"ClassName"');
|
||||
|
||||
$insert->execute();
|
||||
|
||||
### Value Checks
|
||||
|
||||
Raw SQL is handy for performance-optimized calls,
|
||||
e.g. when you want a single column rather than a full-blown object representation.
|
||||
|
||||
Example: Get the count from a relationship.
|
||||
|
||||
:::php
|
||||
$sqlQuery = new SQLQuery();
|
||||
$sqlQuery->setFrom('Player');
|
||||
$sqlQuery->addSelect('COUNT("Player"."ID")');
|
||||
$sqlQuery->addWhere('"Team"."ID" = 99');
|
||||
$sqlQuery->addWhere(array('"Team"."ID"' => 99));
|
||||
$sqlQuery->addLeftJoin('Team', '"Team"."ID" = "Player"."TeamID"');
|
||||
|
||||
$count = $sqlQuery->execute()->value();
|
||||
|
||||
Note that in the ORM, this call would be executed in an efficient manner as well:
|
||||
|
||||
:::php
|
||||
$count = $myTeam->Players()->count();
|
||||
|
||||
### Mapping
|
||||
|
||||
Creates a map based on the first two columns of the query result.
|
||||
This can be useful for creating dropdowns.
|
||||
|
||||
Example: Show player names with their birth year, but set their birth dates as values.
|
||||
|
||||
:::php
|
||||
$sqlQuery = new SQLQuery();
|
||||
$sqlQuery = new SQLSelect();
|
||||
$sqlQuery->setFrom('Player');
|
||||
$sqlQuery->setSelect('ID');
|
||||
$sqlQuery->setSelect('Birthdate');
|
||||
$sqlQuery->selectField('CONCAT("Name", ' - ', YEAR("Birthdate")', 'NameWithBirthyear');
|
||||
$map = $sqlQuery->execute()->map();
|
||||
$field = new DropdownField('Birthdates', 'Birthdates', $map);
|
||||
|
||||
echo $map;
|
||||
Note that going through SQLSelect is just necessary here
|
||||
because of the custom SQL value transformation (`YEAR()`).
|
||||
An alternative approach would be a custom getter in the object definition.
|
||||
|
||||
// returns array(
|
||||
// 1 => "Foo - 1920",
|
||||
// 2 => "Bar - 1936"
|
||||
// );
|
||||
:::php
|
||||
class Player extends DataObject {
|
||||
private static $db = array(
|
||||
'Name' => 'Varchar',
|
||||
'Birthdate' => 'Date'
|
||||
);
|
||||
function getNameWithBirthyear() {
|
||||
return date('y', $this->Birthdate);
|
||||
}
|
||||
}
|
||||
$players = Player::get();
|
||||
$map = $players->map('Name', 'NameWithBirthyear');
|
||||
|
||||
## Related Documentation
|
||||
## Related
|
||||
|
||||
* [Introduction to the Data Model and ORM](data_model_and_orm)
|
||||
|
||||
## API Documentation
|
||||
|
||||
* [api:DataObject]
|
||||
* [api:SQLQuery]
|
||||
* [api:SQLSelect]
|
||||
* [api:DB]
|
||||
* [api:Query]
|
||||
* [api:Database]
|
@ -27,6 +27,11 @@ The extension is automatically applied to `SiteTree` class. For more information
|
||||
[Extending](../extending) and the [Configuration](../configuration) documentation.
|
||||
</div>
|
||||
|
||||
<div class="warning" markdown="1">
|
||||
Versioning only works if you are adding the extension to the base class. That is, the first subclass
|
||||
of `DataObject`. Adding this extension to children of the base class will have unpredictable behaviour.
|
||||
</div>
|
||||
|
||||
## Database Structure
|
||||
|
||||
Depending on how many stages you configured, two or more new tables will be created for your records. In the above, this
|
||||
|
@ -126,8 +126,8 @@ If you're familiar with it, here's the short version of what you need to know. O
|
||||
* **Squash your commits, so that each commit addresses a single issue.** After you rebase your work on top of the upstream master, you can squash multiple commits into one. Say, for instance, you've got three commits in related to Issue #100. Squash all three into one with the message "Description of the issue here (fixes #100)" We won't accept pull requests for multiple commits related to a single issue; it's up to you to squash and clean your commit tree. (Remember, if you squash commits you've already pushed to GitHub, you won't be able to push that same branch again. Create a new local branch, squash, and push the new squashed branch.)
|
||||
|
||||
* **Choose the correct branch**: Assume the current release is 3.0.3, and 3.1.0 is in beta state.
|
||||
Most pull requests should go against the `3.1.x-dev` *pre-release branch*, only critical bugfixes
|
||||
against the `3.0.x-dev` *release branch*. If you're changing an API or introducing a major feature,
|
||||
Most pull requests should go against the `3.1` *pre-release branch*, only critical bugfixes
|
||||
against the `3.0` *release branch*. If you're changing an API or introducing a major feature,
|
||||
the pull request should go against `master` (read more about our [release process](release_process)). Branches are periodically merged "upwards" (3.0 into 3.1, 3.1 into master).
|
||||
|
||||
### Editing files directly on GitHub.com
|
||||
|
@ -36,7 +36,7 @@ Make sure you know the basic concepts of PHP5 before attempting to follow the tu
|
||||
|
||||
## SilverStripe Concepts
|
||||
|
||||
The [Developer Gudes](/developer_guides) contain more detailed documentation on certain SilverStripe topics, 'how to'
|
||||
The [Developer Guides](/developer_guides) contain more detailed documentation on certain SilverStripe topics, 'how to'
|
||||
examples and reference documentation.
|
||||
|
||||
[CHILDREN Folder=02_Developer_Guides]
|
||||
|
@ -660,26 +660,31 @@ class Versioned extends DataExtension implements TemplateGlobalProvider {
|
||||
|
||||
// Get ID field
|
||||
$id = $manipulation[$table]['id'] ? $manipulation[$table]['id'] : $manipulation[$table]['fields']['ID'];
|
||||
if(!$id) user_error("Couldn't find ID in " . var_export($manipulation[$table], true), E_USER_ERROR);
|
||||
if(!$id) {
|
||||
user_error("Couldn't find ID in " . var_export($manipulation[$table], true), E_USER_ERROR);
|
||||
}
|
||||
|
||||
if($this->migratingVersion) {
|
||||
$manipulation[$table]['fields']['Version'] = $this->migratingVersion;
|
||||
}
|
||||
|
||||
// If we haven't got a version #, then we're creating a new version.
|
||||
// Otherwise, we're just copying a version to another table
|
||||
if(empty($manipulation[$table]['fields']['Version'])) {
|
||||
$version = isset($manipulation[$table]['fields']['Version'])
|
||||
? $manipulation[$table]['fields']['Version']
|
||||
: null;
|
||||
if($version < 0 || $this->_nextWriteWithoutVersion) {
|
||||
// Putting a Version of -1 is a signal to leave the version table alone, despite their being no version
|
||||
unset($manipulation[$table]['fields']['Version']);
|
||||
} elseif(empty($version)) {
|
||||
// If we haven't got a version #, then we're creating a new version.
|
||||
// Otherwise, we're just copying a version to another table
|
||||
$this->augmentWriteVersioned($manipulation, $table, $id);
|
||||
}
|
||||
|
||||
// Putting a Version of -1 is a signal to leave the version table alone, despite their being no version
|
||||
if($manipulation[$table]['fields']['Version'] < 0 || $this->_nextWriteWithoutVersion) {
|
||||
// For base classes of versioned data objects
|
||||
if(!$this->hasVersionField($table)) {
|
||||
unset($manipulation[$table]['fields']['Version']);
|
||||
}
|
||||
|
||||
// For base classes of versioned data objects
|
||||
if(!$this->hasVersionField($table)) unset($manipulation[$table]['fields']['Version']);
|
||||
|
||||
// Grab a version number - it should be the same across all tables.
|
||||
if(isset($manipulation[$table]['fields']['Version'])) {
|
||||
$thisVersion = $manipulation[$table]['fields']['Version'];
|
||||
|
@ -13,6 +13,7 @@ class DataObjectLazyLoadingTest extends SapphireTest {
|
||||
|
||||
// These are all defined in DataObjectTest.php and VersionedTest.php
|
||||
protected $extraDataObjects = array(
|
||||
// From DataObjectTest
|
||||
'DataObjectTest_Team',
|
||||
'DataObjectTest_Fixture',
|
||||
'DataObjectTest_SubTeam',
|
||||
@ -31,8 +32,10 @@ class DataObjectLazyLoadingTest extends SapphireTest {
|
||||
'DataObjectTest_Staff',
|
||||
'DataObjectTest_CEO',
|
||||
'DataObjectTest_Fan',
|
||||
'VersionedLazy_DataObject',
|
||||
'VersionedLazySub_DataObject',
|
||||
'DataObjectTest_Play',
|
||||
'DataObjectTest_Ploy',
|
||||
'DataObjectTest_Bogey',
|
||||
// From VersionedTest
|
||||
'VersionedTest_DataObject',
|
||||
'VersionedTest_Subclass',
|
||||
'VersionedTest_AnotherSubclass',
|
||||
@ -41,6 +44,9 @@ class DataObjectLazyLoadingTest extends SapphireTest {
|
||||
'VersionedTest_WithIndexes',
|
||||
'VersionedTest_PublicStage',
|
||||
'VersionedTest_PublicViaExtension',
|
||||
// From DataObjectLazyLoadingTest
|
||||
'VersionedLazy_DataObject',
|
||||
'VersionedLazySub_DataObject',
|
||||
);
|
||||
|
||||
public function testQueriedColumnsID() {
|
||||
|
@ -156,13 +156,13 @@ class VersionedTest extends SapphireTest {
|
||||
"\"VersionedTest_DataObject\".\"ID\" ASC");
|
||||
// Check that page 3 has gone
|
||||
$this->assertNotNull($remainingPages);
|
||||
$this->assertEquals(array("Page 1", "Page 2"), $remainingPages->column('Title'));
|
||||
$this->assertEquals(array("Page 1", "Page 2", "Subclass Page 1"), $remainingPages->column('Title'));
|
||||
|
||||
// Get all including deleted
|
||||
$allPages = Versioned::get_including_deleted("VersionedTest_DataObject", "\"ParentID\" = 0",
|
||||
"\"VersionedTest_DataObject\".\"ID\" ASC");
|
||||
// Check that page 3 is still there
|
||||
$this->assertEquals(array("Page 1", "Page 2", "Page 3"), $allPages->column('Title'));
|
||||
$this->assertEquals(array("Page 1", "Page 2", "Page 3", "Subclass Page 1"), $allPages->column('Title'));
|
||||
|
||||
// Check that the returned pages have the correct IDs
|
||||
$this->assertEquals($allPageIDs, $allPages->column('ID'));
|
||||
@ -171,7 +171,7 @@ class VersionedTest extends SapphireTest {
|
||||
Versioned::reading_stage("Live");
|
||||
$allPages = Versioned::get_including_deleted("VersionedTest_DataObject", "\"ParentID\" = 0",
|
||||
"\"VersionedTest_DataObject\".\"ID\" ASC");
|
||||
$this->assertEquals(array("Page 1", "Page 2", "Page 3"), $allPages->column('Title'));
|
||||
$this->assertEquals(array("Page 1", "Page 2", "Page 3", "Subclass Page 1"), $allPages->column('Title'));
|
||||
|
||||
// Check that the returned pages still have the correct IDs
|
||||
$this->assertEquals($allPageIDs, $allPages->column('ID'));
|
||||
@ -210,7 +210,7 @@ class VersionedTest extends SapphireTest {
|
||||
}
|
||||
|
||||
public function testRollbackTo() {
|
||||
$page1 = $this->objFromFixture('VersionedTest_DataObject', 'page1');
|
||||
$page1 = $this->objFromFixture('VersionedTest_AnotherSubclass', 'subclass1');
|
||||
$page1->Content = 'orig';
|
||||
$page1->write();
|
||||
$page1->publish('Stage', 'Live');
|
||||
@ -227,6 +227,17 @@ class VersionedTest extends SapphireTest {
|
||||
|
||||
$this->assertTrue($page1->Version > $changedVersion, 'Create a new higher version number');
|
||||
$this->assertEquals('orig', $page1->Content, 'Copies the content from the old version');
|
||||
|
||||
// check db entries
|
||||
$version = DB::prepared_query("SELECT MAX(\"Version\") FROM \"VersionedTest_DataObject_versions\" WHERE \"RecordID\" = ?",
|
||||
array($page1->ID)
|
||||
)->value();
|
||||
$this->assertEquals($page1->Version, $version, 'Correct entry in VersionedTest_DataObject_versions');
|
||||
|
||||
$version = DB::prepared_query("SELECT MAX(\"Version\") FROM \"VersionedTest_AnotherSubclass_versions\" WHERE \"RecordID\" = ?",
|
||||
array($page1->ID)
|
||||
)->value();
|
||||
$this->assertEquals($page1->Version, $version, 'Correct entry in VersionedTest_AnotherSubclass_versions');
|
||||
}
|
||||
|
||||
public function testDeleteFromStage() {
|
||||
@ -320,6 +331,7 @@ class VersionedTest extends SapphireTest {
|
||||
$noversion = new DataObject();
|
||||
$versioned = new VersionedTest_DataObject();
|
||||
$versionedSub = new VersionedTest_Subclass();
|
||||
$versionedAno = new VersionedTest_AnotherSubclass();
|
||||
$versionField = new VersionedTest_UnversionedWithField();
|
||||
|
||||
$this->assertFalse(
|
||||
@ -331,8 +343,14 @@ class VersionedTest extends SapphireTest {
|
||||
'The versioned ext adds an Int version field.'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'Int', $versionedSub->hasOwnTableDatabaseField('Version'),
|
||||
'Sub-classes of a versioned model have a Version field.'
|
||||
null,
|
||||
$versionedSub->hasOwnTableDatabaseField('Version'),
|
||||
'Sub-classes of a versioned model don\'t have a Version field.'
|
||||
);
|
||||
$this->assertEquals(
|
||||
null,
|
||||
$versionedAno->hasOwnTableDatabaseField('Version'),
|
||||
'Sub-classes of a versioned model don\'t have a Version field.'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'Varchar', $versionField->hasOwnTableDatabaseField('Version'),
|
||||
@ -924,10 +942,6 @@ class VersionedTest_Subclass extends VersionedTest_DataObject implements TestOnl
|
||||
private static $db = array(
|
||||
"ExtraField" => "Varchar",
|
||||
);
|
||||
|
||||
private static $extensions = array(
|
||||
"Versioned('Stage', 'Live')"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,25 +1,32 @@
|
||||
VersionedTest_DataObject:
|
||||
page1:
|
||||
Title: Page 1
|
||||
page2:
|
||||
Title: Page 2
|
||||
page3:
|
||||
Title: Page 3
|
||||
page2a:
|
||||
Parent: =>VersionedTest_DataObject.page2
|
||||
Title: Page 2a
|
||||
page2b:
|
||||
Parent: =>VersionedTest_DataObject.page2
|
||||
Title: Page 2b
|
||||
page3a:
|
||||
Parent: =>VersionedTest_DataObject.page3
|
||||
Title: Page 3a
|
||||
page3b:
|
||||
Parent: =>VersionedTest_DataObject.page3
|
||||
Title: Page 3b
|
||||
page1:
|
||||
Title: Page 1
|
||||
page2:
|
||||
Title: Page 2
|
||||
page3:
|
||||
Title: Page 3
|
||||
page2a:
|
||||
Parent: =>VersionedTest_DataObject.page2
|
||||
Title: Page 2a
|
||||
page2b:
|
||||
Parent: =>VersionedTest_DataObject.page2
|
||||
Title: Page 2b
|
||||
page3a:
|
||||
Parent: =>VersionedTest_DataObject.page3
|
||||
Title: Page 3a
|
||||
page3b:
|
||||
Parent: =>VersionedTest_DataObject.page3
|
||||
Title: Page 3b
|
||||
|
||||
VersionedTest_PublicStage:
|
||||
public1:
|
||||
Title: 'Some page'
|
||||
|
||||
VersionedTest_PublicViaExtension:
|
||||
public2:
|
||||
Title: 'Another page'
|
||||
|
||||
VersionedTest_AnotherSubclass:
|
||||
subclass1:
|
||||
Title: 'Subclass Page 1'
|
||||
AnotherField: 'Bob'
|
||||
|
Loading…
Reference in New Issue
Block a user