From: Peter Rabbitson Date: Sun, 4 Mar 2012 07:34:01 +0000 (+0100) Subject: Another round of notes X-Git-Tag: v1.0~2^2~2 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=2f41b1a9c1bacddd35687faf0af5cfaf11fd852a;p=dbsrgits%2FDBIx-Class-Manual-SQLHackers.git Another round of notes --- diff --git a/lib/DBIx/Class/Manual/SQLHackers/CREATE.pod b/lib/DBIx/Class/Manual/SQLHackers/CREATE.pod index 27ed269..7220b5d 100644 --- a/lib/DBIx/Class/Manual/SQLHackers/CREATE.pod +++ b/lib/DBIx/Class/Manual/SQLHackers/CREATE.pod @@ -24,8 +24,14 @@ DBIx::Class::Manual::SQLHackers::CREATE - DBIx::Class for SQL Hackers - CREATE =head1 Database structure -To use DBIx::Class, we need to teach it about the layout of the underlying database. Several methods are supported. The built-in method involves defining your database structure as a set of perl classes, one per resultsource (table, view, etc). The other oft used method is to import the definitions from an existing database using the module -L. +### complete paragraph rewording suggestion +### +To use DBIx::Class, we need to teach it about the layout of the underlying database. Several methods of doing this are available. +If you have an existing database the most straightforward way is to use the module L, which +will introspect your database and generate individual classes representing every table and view in your database. +For new projects one usually writes these classes by hand as described below. If you find the methods provided by +L core overly verbose, you can try to define your result classes via the more concise syntax of +L (the result is fully compatible with L). Once a DBIx::Class schema (set of classes describing the database) has been created, built-in methods can be used to export it as SQL DDL using L. @@ -43,6 +49,7 @@ Run the included L script. =head2 Manual Result class creation (and understanding Loader results) +# note - CREATE INDEX is a bitch these days (requires a deploy hook) - perhaps not mentioning it at all is wise-ish? This section covers the common and oft used CREATE DDL statements that DBIx::Class can replaces with Perl classes: B, B and B. The classes can be used to write the actual SQL DDL to the database or disc, if required. =head3 CREATE TABLE @@ -95,6 +102,7 @@ The recommended version: The fully descriptive version is required if you want to have DBIx::Class create your CREATE TABLE sql for you later. Many DBIC components also use settings in the column info hashrefs to decide how to treat the data for those columns. +# perhaps "... with declared relations / Declaring relationships" ? "references" doesn't sound right in the context imho =head4 Table creation with references: A relational database isn't worth much if we don't actually use references and constraints, so here is an example which constrains the B column to only contain B values from the *users* table. @@ -142,10 +150,11 @@ In DBIx::Class this is achieved by adding Lbelongs_to('user', 'MyDatabase::Schema::Result::User', 'user_id'); 1; -The B relation creates a B method which returns the user object, as well as storing JOIN information to be used when querying with JOINs. The joined colums on the remote side are taken from the remote PRIMARY KEY value, if not specified, as in this example. +The B relation creates a B method which returns the user object, as well as storing JOIN information to be used when querying with JOINs. When not explicitly specified (as in this example), the columns for the JOIN clause default to the remote PRIMARY KEY column set. Relationships may also be specified with completely custom JOIN conditions, using any columns, whether the database has them defined as constraints or not, or literal values. +Each relationship declaration in DBIC is one-way only. To allow access from the B object back to the posts they have written, we need to define another relationship in the User class: __PACKAGE__->has_many('posts', 'MyDatabase::Schema::Result::Post', 'user_id'); @@ -240,6 +249,9 @@ Create a schema object with the a database connection (any will do), and call th If called with no arguments, this method will create an SQL file each for MySQL, PostgreSQL and SQLite. More databases are supported by L if necessary. +### IIRC this is not true - one can not do diffs without Schema::Versioned +### which is not loaded by default (and will soon be deprecated anyway, given how far frew and jnap have gone) + =head4 SQL files for upgrades (ALTER TABLE) DBIC can also make use of L to write out ALTER TABLE statements when the schema classes are changed. diff --git a/lib/DBIx/Class/Manual/SQLHackers/DELETE.pod b/lib/DBIx/Class/Manual/SQLHackers/DELETE.pod index a1fb5c3..09f5463 100644 --- a/lib/DBIx/Class/Manual/SQLHackers/DELETE.pod +++ b/lib/DBIx/Class/Manual/SQLHackers/DELETE.pod @@ -111,12 +111,15 @@ created by the user), should also be removed. NOTE: This is a rather drastic action, to prevent problems in your application, consider de-activating accounts instead of removing them! -By default, DBIx::Class will cascade deletes for the following types +For the time being DBIx::Class defaults to cascade deletion for the following types of relationships: B, B, B. That is, it -will automatically issue the above statements. See -L for how to set these up. +will automatically issue the above statements. It is recommended not to rely +on this implicit behavior, as it will be deprecated in a later version of DBIC. +Instead declare proper cascading constraints in your RDBMS as described in +L. -Ideally, your database should cascade deletes for you, and will if references are correctly set up. In this case, you can turn off DBIx::Class' extra cascading statements: +If your database is already properly set up to cascade deletes for you, +you can noop DBIx::Class' extra cascading statements: __PACKAGE__->has_many('posts', 'MyDatabase::Schema::Result::Post', diff --git a/lib/DBIx/Class/Manual/SQLHackers/INSERT.pod b/lib/DBIx/Class/Manual/SQLHackers/INSERT.pod index 87742a9..b705bec 100644 --- a/lib/DBIx/Class/Manual/SQLHackers/INSERT.pod +++ b/lib/DBIx/Class/Manual/SQLHackers/INSERT.pod @@ -25,9 +25,9 @@ DBIx::Class::Manual::SQLHackers::INSERT - DBIx::Class for SQL Hackers - INSERT INSERT INTO users (id, username, dob, realname, password) VALUES (1, 'fredbloggs', '1910-02-01', 'Fred Bloggs', 'secretpass'); -=head2 Simple insertion, populating rows +=head2 Simple bulk insertion, populating rows -The B method is for inserting chunks of data to pre-populate / initialise a database with a set of known values. In void context it uses DBI's fast "execute_array" method. +The B method is for inserting chunks of data to pre-populate / initialise a database with a set of known values. In void context it uses DBI's optimized "execute_for_fetch" method. In scalar or list context populate is a proxy to the B method (on which more below), and returns Row objects. @@ -40,15 +40,15 @@ In scalar or list context populate is a proxy to the B method (on which =item 2. Call the B method for the ResultSource you wish to insert data into: $schema->populate('User', [ - [ qw/id username dob realname password/ ], - [ 1, 'fredbloggs', '1910-02-01', - 'Fred Bloggs', 'secretpass'], - ]); + [qw(id username dob realname password )], + [ 1, 'fredbloggs', '1910-02-01', 'Fred Bloggs', 'secretpass' ], + ]); -=back +=back Note that in void context you can skip passing primary key values that will be supplied by the database, and any other values that are allowed to DEFAULT. However no code in your Result classes will be run (eg InflateColumn components). +# perhaps "Constructing and inserting Row objects" ? =head2 Inserting with Row objects INSERT INTO users (username, dob, realname, password) @@ -58,12 +58,14 @@ In the course of your application, you will often want to retrieve data from a u =over +# perhaps s/Create/Obtain/ ? =item 1. Create a Schema object: my $schema = MyDatabase::Schema->connect('dbi:SQLite:my.db'); (ideally you will always have one of these handy, no need to make many connections to the database) +# perhaps s/Create/Obtain/ ? =item 2. Create a User object: my $newuser = $schema->resultset('User')->new({ @@ -164,6 +166,8 @@ This also can be shortcut using B: =back +### mattp is slowly working on this, do we need to mention it at all? + =head2 Insert using a SELECT as input: INSERT INTO users (id, username, dob, realname, password) diff --git a/lib/DBIx/Class/Manual/SQLHackers/Introduction.pod b/lib/DBIx/Class/Manual/SQLHackers/Introduction.pod index 4edbdf7..81344e8 100644 --- a/lib/DBIx/Class/Manual/SQLHackers/Introduction.pod +++ b/lib/DBIx/Class/Manual/SQLHackers/Introduction.pod @@ -5,6 +5,7 @@ DBIx::Class::Manual::SQLHackers::Introduction =head2 Introduction (Why ORMs and DBIx::Class) Almost every sizable Perl application these days needs a method of long term data storage. When the data needs to be easily retrieved as well as stored, we often use a database. Most databases can be comfortably accessed using SQL. Using the DBI module, and a DBD for the particular database, we can write SQL in our Perl code, and retrieve the results as arrays or hashes. +^^ perhaps s/database/relational database/ ? just to save you from idiots "OOOOHHHH BUT MONGO IS A DB TOO!!!!" ## Example my $dbh = DBI->connect("dbi:SQLite:mydb.db"); @@ -16,13 +17,14 @@ Almost every sizable Perl application these days needs a method of long term dat print $row->{name}; } -There are several things we can do to make this code more usable, for example store the database connect string (DSN) in a configuration file so that users of the code can use different databases without editing the code. We can also write a separate method or module for creating and returning the $dbh, so that we don't create a lot of db connections unnecessarily. +There are several things we can do to make this code more usable, for example store the database connect string (DSN) in a configuration file so that users of the code can use different databases by simply changing the config. We can also write a separate method or module for creating and returning the $dbh, so that we don't create a lot of db connections unnecessarily. The part we can't do much about is the SQL in the code. We can move it around, put it in libraries, but it's still there, somewhere. -Why would you not want SQL in your Perl code? For a start, it's just a string to pass to the database interpreter, there is no syntax checking at the Perl compilation level. Thus it fails late, not early. Your editor will also not syntax check what it just sees as strings of text. +Why is having SQL mixed with your Perl code not very optimal? For a start, it's just a string to pass to the database interpreter, there is no syntax checking at the Perl compilation level. Thus it fails late, not early. Your editor will also not syntax check what it just sees as strings of text. -Modern Perl should also leverage code reuse and OO where it can. DBIx::Class promotes code reuse by allowing you to add methods for common queries, fetch related data in one query and cache data, also without refetching. DBIC uses the DBI library underneath, which gets things right, but returns plain hashes and arrays, not objects. +Modern Perl should also leverage code reuse and OO where it can. DBIx::Class promotes code reuse by allowing you to add methods for common queries, fetch related data in one query and cache data, also without refetching. DBIC still uses the DBI library underneath, so it gets things right while presenting the results in a more manageable way. DBIx::Class solves these issues, you write your SQL in perl instead of plain text. The syntax will be checked for you, existance of columns, catching typos and so on. It uses objects so that you can write re-usable queries, and string methods together to create complex queries. You define the database layout once, or you export it from your actual database (with ability to re-export on update). - +^^ +this last paragraph is kinda misleading - we do not do any syntax checking of stuff - we just let the database throw. It would be waaaay to expensive to do otherwise. diff --git a/lib/DBIx/Class/Manual/SQLHackers/SELECT.pod b/lib/DBIx/Class/Manual/SQLHackers/SELECT.pod index 0571795..8e87800 100644 --- a/lib/DBIx/Class/Manual/SQLHackers/SELECT.pod +++ b/lib/DBIx/Class/Manual/SQLHackers/SELECT.pod @@ -27,7 +27,7 @@ DBIx::Class::Manual::SQLHackers::SELECT - DBIx::Class for SQL Hackers - SELECT SELECT id, username, dob, realname, password FROM users; -In DBIx::Class queries are represented by ResultSet objects. These are created by calling B on existing resultsets, passing new search conditions. A query is not run against the database until data is explicitly requested. +In DBIx::Class queries (or more specifically query plans) are represented by ResultSet objects. These are created by calling B on existing resultsets, while passing new search conditions or attributes. A query is not run against the database until data is explicitly requested. You can either fetch all the data at once, or iterate over the results: @@ -37,7 +37,7 @@ You can either fetch all the data at once, or iterate over the results: my $schema = MyDatabase::Schema->connect('dbi:SQLite:my.db'); -=item 2. The B method returns a ResultSet representing a query with no conditions on the given B: +=item 2. The B method returns a ResultSet representing a query retrieving all columns of the given B without conditions: my $user_resultset = $schema->resultset('User'); @@ -54,7 +54,7 @@ You can either fetch all the data at once, or iterate over the results: =head2 Fetching column values from a Row object -The Row object represents the results from a single data source table in the query. The column values can be retrieved by using the accessor methods named after the column names. (By default that is, accessors can be changed in the L if needed). +The Row object represents the results from a single data source in the query. The column values can be retrieved by using the accessor methods named after the column names. (By default that is, accessors can be changed in the L if needed). print $user->username; @@ -208,6 +208,7 @@ There's usually little reason to do this sort of query, as fetching all the data # this is completely false, is there a doc that states this that we need to fix? # find() takes all search() attributes, including things like prefetch +^^ you never fixed this piece from last time B will always pull all the columns for the found row, so use the *search* method for this. =over @@ -225,7 +226,7 @@ B will always pull all the columns for the found row, so use the *search* =back -Note that accessors for other columns not fetched will return B, which is also the perl equivalent of the SQL C value. To discover whether a columns data has been loaded or not, use L. +Note that accessors for other columns not fetched will return B, which is also the perl equivalent of the SQL C value. To disambiguate between an C meaning "this column is set null" and "we never retrieved the value of this column" use L. =head2 SELECT with aggregates @@ -381,6 +382,8 @@ To sort the results, use the B attributes on a B method. Conte =item 2. Call the B method on the resultset of the L you wish to sort data on: # this will not collapse results (you will get users * (amount of posts || 1) as a result, sure you want to showcase this? +^^ this is still true for what the DBIC on CPAN, I recommend pod-commenting the example out, +^^ and reenabling it when I finally ship the blasted thing my $sorted_users = $schema->resultset('User')->search( { }, { '+columns' => [ qw/posts.id posts.title/ ], @@ -439,6 +442,7 @@ Here "posts" refers to the name of the L The results will be filtered by the HAVING clause. =head2 SELECT with DISTINCT +^^ you may or may not want to mention the distinct => 1 flag, which is an automatic "group by the selection" thing. SELECT DISTINCT(posts.title) FROM posts diff --git a/lib/DBIx/Class/Manual/SQLHackers/Transactions.pod b/lib/DBIx/Class/Manual/SQLHackers/Transactions.pod index 40accb3..c7bedb2 100644 --- a/lib/DBIx/Class/Manual/SQLHackers/Transactions.pod +++ b/lib/DBIx/Class/Manual/SQLHackers/Transactions.pod @@ -27,7 +27,8 @@ DBIx::Class::Manual::SQLHackers::DELETE - DBIx::Class for SQL Hackers - DELETE UPDATE users SET username = 'fred' WHERE id = 1; COMMIT; -To create a transaction, put all your changes inside a coderef, and pass it to the B method on the Schema object. Transactions can also be safely nested +To create a transaction, put all your changes inside a coderef, and pass it to the B method on the Schema object. Transactions can also be safely nested, +in which case all but the top level transaction blocks become noops. =over diff --git a/lib/DBIx/Class/Manual/SQLHackers/UPDATE.pod b/lib/DBIx/Class/Manual/SQLHackers/UPDATE.pod index 4e88e24..202ec06 100644 --- a/lib/DBIx/Class/Manual/SQLHackers/UPDATE.pod +++ b/lib/DBIx/Class/Manual/SQLHackers/UPDATE.pod @@ -166,6 +166,8 @@ the object, and send an UPDATE query to the database. # https://github.com/dbsrgits/dbix-class/commit/0e773352 $fred_user->update({ username => \['username || ?', '.uk'] }); +^^ you never got around to this + # the DBIC syntax is a tad different from te thing above (i.e. we no longer encourage 'dummy' crap) The \[ .. ] syntax here is described in L documentation, used for passing bind parameters. @@ -202,6 +204,8 @@ joining them for a select query and using data from both. $posts->update({ 'me.title' => \[ 'user.username || me.title' ] }); +^^ I am 95% sure this won't actually work, please try it (ideally as a passing or failing test) + =back =head2 Update or create a row @@ -221,7 +225,7 @@ joining them for a select query and using data from both. SET id = ?, username = ?, dob = ?, realname = ?, password = ?; COMMIT; -DBIx::Class does not produce the non-standard MySQL "ON DUPLICATE KEY +DBIx::Class does not yet produce the non-standard MySQL "ON DUPLICATE KEY UPDATE", instead it has a shortcut for combining *find* and *update*. To avoid race conditions, this should be done in a transaction. @@ -233,7 +237,8 @@ To avoid race conditions, this should be done in a transaction. my $schema = MyDatabase::Schema->connect('dbi:SQLite:my.db'); =item 2. Call the B method on the schema object, passing it a coderef to execute inside the transaction: - +^^ ouch! I didn't realize we don't do that automatically, this is a bug +^^ probably a good idea not to mention it - I'll fix it @ GPW $schema->txn_do( sub { =item 3. Call the B method on the resultset for the L you wish to update data in: