From cca7129385dbb3be1001a8861423c2cf490f02d4 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Fri, 22 Jan 2016 11:45:00 +1300 Subject: [PATCH] BUG Revert lost documentation Resolves some regressions from #3735 --- .../00_Model/02_Relations.md | 42 +++ .../00_Model/08_SQL_Query.md | 244 +++++++++++++++--- 2 files changed, 248 insertions(+), 38 deletions(-) diff --git a/docs/en/02_Developer_Guides/00_Model/02_Relations.md b/docs/en/02_Developer_Guides/00_Model/02_Relations.md index 73d50d9f9..2480448b8 100644 --- a/docs/en/02_Developer_Guides/00_Model/02_Relations.md +++ b/docs/en/02_Developer_Guides/00_Model/02_Relations.md @@ -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 "``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" + ); + } + +
+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. +
+ ## has_many Defines 1-to-many joins. As you can see from the previous example, `$has_many` goes hand in hand with `$has_one`. diff --git a/docs/en/02_Developer_Guides/00_Model/08_SQL_Query.md b/docs/en/02_Developer_Guides/00_Model/08_SQL_Query.md index 74dc687ad..cd907887c 100644 --- a/docs/en/02_Developer_Guides/00_Model/08_SQL_Query.md +++ b/docs/en/02_Developer_Guides/00_Model/08_SQL_Query.md @@ -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: -
-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. + +
+Please read our [security topic](/developer_guides/security) to find out +how to properly prepare user input and variables for use in queries
## 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(); + 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); + setFrom('"SiteTree"') + ->setWhere(array('"SiteTree"."ShowInMenus"' => 0)); + $query->execute(); -
-Currently not supported through the `SQLQuery` class, please use raw `DB::query()` calls instead. -
+Alternatively, turning an existing `SQLQuery` into a delete :::php - DB::query('UPDATE "Player" SET "Status"=\'Active\''); + setFrom('"SiteTree"') + ->setWhere(array('"SiteTree"."ShowInMenus"' => 0)) + ->toDelete(); + $query->execute(); + +Directly querying the database + + :::php + 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 + 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 + addRows(array( + array('"Title"' => 'Home', '"Content"' => '

This is our home page

'), + array('"Title"' => 'About Us', '"ClassName"' => 'AboutPage') + )); + + // Adjust an assignment on the last row + $insert->assign('"Content"', '

This is about us

'); + + // 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] \ No newline at end of file