diff --git a/docs/en/reference/sqlquery.md b/docs/en/reference/sqlquery.md
index 0e1b79360..83f2684c6 100644
--- a/docs/en/reference/sqlquery.md
+++ b/docs/en/reference/sqlquery.md
@@ -2,11 +2,30 @@
## Introduction
-An object representing a SQL query. It is easier to deal with object-wrappers than string-parsing a raw SQL-query. This object is used by the SilverStripe ORM internally.
+An object representing a SQL 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](/topics/datamodel).
+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.
-You'll run the risk of breaking various assumptions the ORM and code based on it have:
+For example, if you want to run a simple `COUNT` SQL statement,
+the following three statements are functionally equivalent:
+
+ :::php
+ // Through raw SQL
+ $count = DB::query('SELECT COUNT(*) FROM "Member"')->value();
+ // Through SQLQuery abstraction layer
+ $query = new SQLQuery();
+ $count = $query->setFrom('Member')->setSelect('COUNT(*)')->value();
+ // 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:
* Custom getters/setters (object property can differ from database column)
* DataObject hooks like onBeforeWrite() and onBeforeDelete()
@@ -17,6 +36,11 @@ You'll run the risk of breaking various assumptions the ORM and code based on it
We'll explain some ways to use *SELECT* with the full power of SQL,
but still maintain a connection to the ORM where possible.
+
+ Please read our ["security" topic](/topics/security) to find out
+ how to sanitize user input before using it in SQL queries.
+
+
## Usage
### SELECT
@@ -25,11 +49,8 @@ but still maintain a connection to the ORM where possible.
$sqlQuery = new SQLQuery();
$sqlQuery->setFrom('Player');
$sqlQuery->selectField('FieldName', 'Name');
- $sqlQuery->selectField('YEAR("Birthday")', 'BirthYear');
- $sqlQuery->addLeftJoin(
- 'Team',
- '"Player"."TeamID" = "Team"."ID"'
- );
+ $sqlQuery->selectField('YEAR("Birthday")', 'Birthyear');
+ $sqlQuery->addLeftJoin('Team','"Player"."TeamID" = "Team"."ID"');
$sqlQuery->addWhere('YEAR("Birthday") = 1982');
// $sqlQuery->setOrderBy(...);
// $sqlQuery->setGroupBy(...);
@@ -37,164 +58,86 @@ but still maintain a connection to the ORM where possible.
// $sqlQuery->setLimit(...);
// $sqlQuery->setDistinct(true);
- // get the raw SQL
+ // Get the raw SQL (optional)
$rawSQL = $sqlQuery->sql();
- // execute and return a Query-object
+ // Execute and return a Query object
$result = $sqlQuery->execute();
-
-### DELETE
-
- :::php
- // ...
- $sqlQuery->setDelete(true);
-
-
-### INSERT/UPDATE
-
-(currently not supported -see below for alternative solutions)
-
-## Working with results
-
-The result is an array lightly wrapped in a database-specific subclass of `[api:Query]`. This class implements the
-*Iterator*-interface defined in PHP5, and provides convenience-methods for accessing the data.
-
-### Iterating
-
- :::php
+ // Iterate over results
foreach($result as $row) {
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.
-### Quick value checking
-
-Raw SQL is handy for performance-optimized calls.
+### DELETE
:::php
- class Team extends DataObject {
- public function getPlayerCount() {
- $sqlQuery = new SQLQuery();
- $sqlQuery->setFrom('Player');
- $sqlQuery->addSelect('COUNT("Player"."ID")');
- $sqlQuery->addLeftJoin('Team', '"Team"."ID" = "Player"."TeamID"');
- return $sqlQuery->execute()->value();
- }
+ $sqlQuery->setDelete(true);
-Way faster than dealing with `[api:DataObject]`s, but watch out for premature optimisation:
+### INSERT/UPDATE
- :::php
- $players = $myTeam->Players();
- echo $players->Count();
-
-
-### Mapping
-
-Useful for creating dropdowns.
-
- :::php
- $sqlQuery = new SQLQuery();
- $sqlQuery->setFrom('Player');
- $sqlQuery->selectField('YEAR("Birthdate")', 'Birthdate');
- $map = $sqlQuery->execute()->map();
- $field = new DropdownField('Birthdates', 'Birthdates', $map);
-
-
-### "Raw" SQL with DB::query()
-
-This is not recommended for most cases, but you can also use the SilverStripe database-layer to fire off a raw query:
+Currently not supported through the `SQLQuery` class, please use raw `DB::query()` calls instead.
:::php
DB::query('UPDATE "Player" SET "Status"=\'Active\'');
-<<<<<<< Updated upstream
-### Transforming a result to `[api:ArrayList]`
-=======
-One example for using a raw DB::query is when you are wanting to order twice in the database:
+### Value Checks
- :::php
- $records = DB::query('SELECT *, CASE WHEN "ThumbnailID" = 0 THEN 2 ELSE 1 END AS "HasThumbnail" FROM "TempDoc" ORDER BY "HasThumbnail", "Name" ASC');
- $items = singleton('TempDoc')->buildDataObjectSet($records);
+Raw SQL is handy for performance-optimized calls,
+e.g. when you want a single column rather than a full-blown object representation.
-This CASE SQL creates a second field "HasThumbnail" depending if "ThumbnailID" exists in the database which you can then
-order by "HasThumbnail" to make sure the thumbnails are at the top of the list and then order by another field "Name"
-separately for both the items that have a thumbnail and then for those that don't have thumbnails.
-
-### "Semi-raw" SQL with buildSQL()
-
-You can gain some ground on the datamodel-side when involving the selected class for querying. You don't necessarily
-need to call *buildSQL* from a specific object-instance, a *singleton* will do just fine.
-
- :::php
- $sqlQuery = singleton('Player')->buildSQL('YEAR("Birthdate") = 1982');
-
-
-This form of building a query has the following advantages:
-
-* Respects DataObject::$default_sort
-* Automatically `LEFT JOIN` on all base-tables (see [database-structure](database-structure))
-* Selection of *ID*, *ClassName*, *RecordClassName*, which are necessary to use *buildDataObjectSet* later on
-* Filtering records for correct *ClassName*
-
-### Transforming a result to `[api:DataObjectSet]`
->>>>>>> Stashed changes
-
-This is a commonly used technique inside SilverStripe: Use raw SQL, but transfer the resulting rows back into
-`[api:DataObject]`s.
+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->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->setSelect(array(
- '"Firstname" AS "Name"',
- 'YEAR("Birthday") AS "BirthYear"',
- // IMPORTANT: Needs to be set after other selects to avoid overlays
- '"Player"."ClassName" AS "ClassName"',
- '"Player"."ClassName" AS "RecordClassName"',
- '"Player"."ID" AS "ID"'
- ));
$sqlQuery->setFrom('Player');
- $sqlQuery->addLeftJoin('Team', '"Player"."TeamID" = "Team"."ID"');
- $sqlQuery->addWhere("YEAR("Player"."Birthday") = 1982");
-
- $result = $sqlQuery->execute();
- var_dump($result->first()); // array
-
- // let Silverstripe work the magic
- $myList = singleton('Player')->buildDataObjectSet($result);
- var_dump($myDataObjectSet->First()); // DataObject
-
- // this is where it gets tricky
- $myFirstPlayer = $myDataObjectSet->First();
- var_dump($myFirstPlayer->Name); // 'John'
- var_dump($myFirstPlayer->Firstname); // undefined, as it was not part of the SELECT-clause;
- var_dump($myFirstPlayer->Surname); // undefined, as it was not part of the SELECT-clause
-
- // lets assume that class Player extends BasePlayer,
- // and BasePlayer has a database-column "Status"
- var_dump($myFirstPlayer->Status); // undefined, as we didn't LEFT JOIN the BasePlayer-table
+ $sqlQuery->setSelect('Birthdate');
+ $sqlQuery->selectField('CONCAT("Name", ' - ', YEAR("Birthdate")', 'NameWithBirthyear');
+ $map = $sqlQuery->execute()->map();
+ $field = new DropdownField('Birthdates', 'Birthdates', $map);
+Note that going through SQLQuery is just necessary here
+because of the custom SQL value transformation (`YEAR()`).
+An alternative approach would be a custom getter in the object definition.
-**CAUTION:** Depending on the selected columns in your query, you might get into one of the following scenarios:
-
-* Not all object-properties accessible: You need to take care of selecting the right stuff yourself
-* Overlayed object-properties: If you *LEFT JOIN* a table which also has a column 'Birthdate' and do a global select on
-this table, you might not be able to access original object-properties.
-* You can't create `[api:DataObject]`s where no scalar record-data is available, e.g. when using *GROUP BY*
-* Naming conflicts with custom getters: A getter like Player->getName() will overlay the column-data selected in the
-above example
-
-Be careful when saving back `[api:DataObject]`s created through *buildDataObjectSet*, you might get strange side-effects due to
-the issues noted above.
-## Using FormFields with custom SQL
-
-Some subclasses of `[api:FormField]` for ways to create sophisticated report-tables based on SQL.
+ :::php
+ class Player extends DataObject {
+ static $db = array(
+ 'Name' =>
+ 'Birthdate' => 'Date'
+ );
+ function getNameWithBirthyear() {
+ return date('y', $this->Birthdate);
+ }
+ }
+ $players = Player::get();
+ $map = $players->map('Name', 'NameWithBirthyear');
## Related
* [datamodel](/topics/datamodel)
* `[api:DataObject]`
-* [database-structure](database-structure)
-
-## API Documentation
-`[api:SQLQuery]`
\ No newline at end of file
+* [database-structure](database-structure)
\ No newline at end of file
diff --git a/docs/en/topics/datamodel.md b/docs/en/topics/datamodel.md
index 6633690a3..76429d5ae 100755
--- a/docs/en/topics/datamodel.md
+++ b/docs/en/topics/datamodel.md
@@ -14,7 +14,8 @@ logic is handled by SilverStripe, you don't need to worry about writing SQL most
Most of the ORM customizations are possible through [PHP5 Object
Overloading](http://www.onlamp.com/pub/a/php/2005/06/16/overloading.html) handled in the `[api:Object]`-class.
-See [database-structure](/reference/database-structure) for in-depth information on the database-schema.
+See [database-structure](/reference/database-structure) for in-depth information on the database-schema,
+and the ["sql queries" topic](/reference/sqlquery) in case you need to drop down to the bare metal.
## Generating the Database Schema
@@ -523,8 +524,10 @@ accessors available on both ends.
### Adding relations
-Inside SilverStripe it doesn't matter if you're editing a *has_many*- or a *many_many*-relationship. You need to get a
-`[api:ComponentSet]`.
+Adding new items to a relations works the same,
+regardless if you're editing a *has_many*- or a *many_many*.
+They are encapsulated by `[api:HasManyList]` and `[api:ManyManyList]`,
+both of which provide very similar APIs, e.g. an `add()` and `remove()` method.
:::php
class Team extends DataObject {
@@ -533,20 +536,8 @@ Inside SilverStripe it doesn't matter if you're editing a *has_many*- or a *many
"Categories" => "Category",
);
- /**
-
- * @param DataObjectSet
- */
- public function addCategories($additionalCategories) {
- $existingCategories = $this->Categories();
-
- // method 1: Add many by iteration
- foreach($additionalCategories as $category) {
- $existingCategories->add($category);
- }
-
- // method 2: Add many by ID-List
- $existingCategories->addMany(array(1,2,45,745));
+ public function addCategories(SS_List $cats) {
+ foreach($cats as $cat) $this->Categories()->add($cat);
}
}
@@ -554,8 +545,8 @@ Inside SilverStripe it doesn't matter if you're editing a *has_many*- or a *many
### Custom Relations
You can use the flexible datamodel to get a filtered result-list without writing any SQL. For example, this snippet gets
-you the "Players"-relation on a team, but only containing active players. (See `[api:DataObject::$has_many]` for more info on
-the described relations).
+you the "Players"-relation on a team, but only containing active players.
+See `[api:DataObject::$has_many]` for more info on the described relations.
:::php
class Team extends DataObject {
@@ -569,6 +560,9 @@ the described relations).
}
}
+Note: Adding new records to a filtered `RelationList` like in the example above
+doesn't automatically set the filtered criteria on the added record.
+
## Validation and Constraints
Traditionally, validation in SilverStripe has been mostly handled on the controller
@@ -742,19 +736,13 @@ It checks if a member is logged in who belongs to a group containing the permiss
}
}
-
-
-
### Saving data with forms
See [forms](/topics/forms).
### Saving data with custom SQL
-See `[api:SQLQuery]` for custom *INSERT*, *UPDATE*, *DELETE* queries.
-
-
-
+See the ["sql queries" topic](/reference/sqlquery) for custom *INSERT*, *UPDATE*, *DELETE* queries.
## Extending DataObjects
@@ -763,18 +751,14 @@ code or subclassing.
Please see `[api:DataExtension]` for a general description, and `[api:Hierarchy]` for our most
popular examples.
-
-
## FAQ
### Whats the difference between DataObject::get() and a relation-getter?
-You can work with both in pretty much the same way, but relationship-getters return a special type of collection:
-A `[api:ComponentSet]` with relation-specific functionality.
+
+You can work with both in pretty much the same way,
+but relationship-getters return a special type of collection:
+A `[api:HasManyList]` or a `[api:ManyManyList]` with relation-specific functionality.
:::php
- $myTeam = DataObject::get_by_id('Team',$myPlayer->TeamID); // returns DataObject
- $myTeam->add(new Player()); // fails
-
- $myTeam = $myPlayer->Team(); // returns Componentset
- $myTeam->add(new Player()); // works
-
+ $myTeams = $myPlayer->Team(); // returns HasManyList
+ $myTeam->add($myOtherPlayer);
\ No newline at end of file