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