<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-3619783675451947013</id><updated>2011-11-27T15:57:07.983-08:00</updated><category term='flash'/><category term='postgresql'/><category term='transactions'/><category term='mysql'/><category term='easymock'/><category term='database design'/><category term='spring'/><category term='mac'/><category term='web service'/><category term='junit'/><category term='aop'/><category term='superduper'/><category term='axis'/><category term='flex'/><category term='replication'/><category term='triggers'/><category term='database'/><category term='memcache'/><title type='text'>nathan's noggin</title><subtitle type='html'>mainly thoughts on:

Java,
MySQL,
Spring,
Anything else I'm working on</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://nathansnoggin.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://nathansnoggin.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>nathan</name><uri>http://www.blogger.com/profile/03220491542181069595</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='33' height='21' src='http://bp1.blogger.com/_t6Rtr5x_xKE/SCRKaCKTdeI/AAAAAAAAAIc/jMngfcb9Vlo/S220/sarthe.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>13</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-3619783675451947013.post-408934782487714718</id><published>2011-03-09T18:47:00.000-08:00</published><updated>2011-03-09T18:49:53.699-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='transactions'/><category scheme='http://www.blogger.com/atom/ns#' term='spring'/><title type='text'>Spring Nested Transactions and problems with "Session is closed" Exceptions</title><content type='html'>I was really pulling my hair out over some code that did programmatic transaction handling using Spring's PlatformTransactionManager on top of Hibernate.  It is fairly complicated, with up to 4 different transactions running concurrently.  At one point it needs to process 3 different result sets, like this:&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;&lt;br /&gt;TransactionStatus status1 = transactionManager.getTransaction(new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW));&lt;br /&gt;PreparedStatement statement1 = sessionFactory.getCurrentSession().connection().prepareStatement(...);&lt;br /&gt;&lt;br /&gt;TransactionStatus status2 = transactionManager.getTransaction(new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW));&lt;br /&gt;PreparedStatement statement2 = sessionFactory.getCurrentSession().connection().prepareStatement(...);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;TransactionStatus status3 = transactionManager.getTransaction(new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW));&lt;br /&gt;PreparedStatement statement3 = sessionFactory.getCurrentSession().connection().prepareStatement(...);&lt;br /&gt;&lt;br /&gt;... // set parameters on the statements&lt;br /&gt;&lt;br /&gt;ResultSet rs1 = statement1.executeQuery();&lt;br /&gt;ResultSet rs2 = statement1.executeQuery();&lt;br /&gt;ResultSet rs3 = statement1.executeQuery();&lt;br /&gt;&lt;br /&gt;... // process the result sets&lt;br /&gt;&lt;br /&gt;... // close the result sets and statements&lt;br /&gt;&lt;br /&gt;transactionManager.commit(status1);&lt;br /&gt;transactionManager.commit(status2);&lt;br /&gt;transactionManager.commit(status3);&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This part of the code ran fine the first time, but the next time, it would throw an exception saying "Session is closed", when trying to get the first transaction.  Do you see the problem?  I didn't see it for way too long.  I spent time removing sections of code, staring at logs, and pulling lots of hair out, before I finally noticed that in the spring logging, after finishing that section of code, it was falling back to trying to use the wrong session.&lt;br /&gt;&lt;br /&gt;Finally it dawned on me that Spring must be using a stack to keep track of the current session.  When a transaction is started with PROPAGATION_REQUIRES_NEW, the current session is pushed on the stack, and a new session is created and becomes the current session.  When that transaction finishes, the current session is closed, and the previous session is popped off of the stack to resume as the now current session.&lt;br /&gt;&lt;br /&gt;Looking at the &lt;a href='http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/transaction.html#tx-propagation'&gt;documentation about transaction propagation&lt;/a&gt;, it does talk about "inner" and "outer" transactions, but I don't think it's quite explicit enough at explaining the nesting relationship.  And my problem is that I wasn't really considering them to be nested, thinking of them more as simply independent transactions (in my defense, the docs do say that they are "completely independent" transactions).  That is why it took me so long to realize my mistake.&lt;br /&gt;&lt;br /&gt;My problem was that I was creating transaction 1, then transaction 2, then 3.  But I was trying to commit transaction 1 first, then 2, then 3.  Doing this messed up the stack, and in the end Spring was left with a session that had been closed as the current session.  So the next attempt to use the session would cause an exception to be thrown telling me that the "Session is closed".&lt;br /&gt;&lt;br /&gt;Rearranging the code to commit transaction 3 first, then 2, then 1, fixed the problem.  I now have a better sense of how Spring works, working code, and less hair. I thought I'd write this up in case it helps someone else avoid this simple mistake.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3619783675451947013-408934782487714718?l=nathansnoggin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nathansnoggin.blogspot.com/feeds/408934782487714718/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nathansnoggin.blogspot.com/2011/03/spring-nested-transactions-and-problems.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/408934782487714718'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/408934782487714718'/><link rel='alternate' type='text/html' href='http://nathansnoggin.blogspot.com/2011/03/spring-nested-transactions-and-problems.html' title='Spring Nested Transactions and problems with &quot;Session is closed&quot; Exceptions'/><author><name>nathan</name><uri>http://www.blogger.com/profile/03220491542181069595</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='33' height='21' src='http://bp1.blogger.com/_t6Rtr5x_xKE/SCRKaCKTdeI/AAAAAAAAAIc/jMngfcb9Vlo/S220/sarthe.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3619783675451947013.post-5322834517122162135</id><published>2010-09-15T10:04:00.000-07:00</published><updated>2010-09-15T10:04:16.959-07:00</updated><title type='text'>CruiseControl to Hudson</title><content type='html'>At work we recently switched from using &lt;a href="http://cruisecontrol.sourceforge.net/"&gt;CruiseControl&lt;/a&gt; to &lt;a href="http://hudson-ci.org/"&gt;Hudson&lt;/a&gt; for continuous builds.&lt;br /&gt;&lt;br /&gt;Originally I was just trying to upgrade CruiseControl so that we could add in a plugin to support Mercurial (we also recently switched from Subversion to Mercurial for version control).  We were on a fairly old version of CruiseControl.  After upgrading, we were having lots of headaches with the web interface freezing up, and I had to write a Mercurial label incrementer plugin, and I didn't really like the new web interface anyway.  I finally got frustrated enough to try something new.&lt;br /&gt;&lt;br /&gt;I threw Hudson on the machine, and had builds up and running so much more quickly than in CruiseControl that I was sold almost immediately.  Some of the differences that I really liked were:&lt;br /&gt;&lt;br /&gt;* No editing XML.  All configuration in Hudson can be done through the web interface.  It's also easy to create build configs for new clones/branches.  When you create a new Job, you can create it based on an existing Job, and then just change a few names and paths.&lt;br /&gt;* Plugins are listed and installed through the web interface.  This was probably the single best part of Hudson compared to CruiseControl.   With CruiseControl, I was left searching for plugins to see what existed, and going through the hassle of researching and installing plugins to try to get things working.  Hudson lets you see all the available plugins in one place.  That was really handy.&lt;br /&gt;* Hudson automatically detects if test cases fail, and will mark the build as "unstable".  It's also really good at showing you the test case failures within the Hudson web interface.&lt;br /&gt;* More features.  I could probably get all of these things in CruiseControl, but it's so hard to find plugins, that I didn't try very hard.  In Hudson it was easy to get it to host/expose my javadocs.  It's easy to link to the most recent artifacts with static urls.  It's also possible to have "slave" build machines so that long builds don't hold everything up.&lt;br /&gt;* The email notifications are better.  I only get emailed the first time a build breaks, and not for every subsequent failure.  It also emails me when the build is fixed.&lt;br /&gt;&lt;br /&gt;Now I'm guessing that I could probably get CruiseControl to do most or maybe all of those things, but when Hudson makes it so easy, why try to figure CruiseControl out?&lt;br /&gt;&lt;br /&gt;The only trick I had to work out was getting Hudson to show the build revision.  We use build revisions in our bug tracking system, so that developers can let testers know what revision a bug fix was made in.  The solution I found was to use the &lt;a href="http://wiki.hudson-ci.org/display/HUDSON/Description+Setter+Plugin"&gt;"Hudson Description Setter Plugin"&lt;/a&gt;.  I installed the plugin in Hudson, then modified my build script to output the Mercurial revision.  I created an ant task that I run as a dependency in my build task:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;&lt;br /&gt; &amp;lt;target name="revision" description="Stores the latest revision number in revision property"&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;echo message="os: ${os.name}"/&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;condition property="unix"&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;os family="unix"/&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/condition&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;echo message="is unix: ${unix}"/&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;if&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;equals arg1="${unix}" arg2="true"/&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;then&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;exec executable="sh" outputproperty="revision" errorproperty="revision-error" dir="${basedir}"&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;arg value="-c"/&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;arg line="hg identify -n | tr -d '+'"/&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/exec&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/then&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;else&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;property name="revision" value="unknown"/&amp;gt;&lt;br /&gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/else&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/if&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;echo message="rev: ${revision}"/&amp;gt;&lt;br /&gt; &amp;lt;/target&amp;gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Then I activated the plugin in my build configuration in Hudson, under "Post-build Actions" (the checkbox named "Set build description"), and set the regular expression to "rev: (.*)" and the Description to "[version] \1".  All subsequent builds will have a description with the Mercurial revision in it (e.g. "[version] 12345").&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3619783675451947013-5322834517122162135?l=nathansnoggin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nathansnoggin.blogspot.com/feeds/5322834517122162135/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nathansnoggin.blogspot.com/2010/09/cruisecontrol-to-hudson.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/5322834517122162135'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/5322834517122162135'/><link rel='alternate' type='text/html' href='http://nathansnoggin.blogspot.com/2010/09/cruisecontrol-to-hudson.html' title='CruiseControl to Hudson'/><author><name>nathan</name><uri>http://www.blogger.com/profile/03220491542181069595</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='33' height='21' src='http://bp1.blogger.com/_t6Rtr5x_xKE/SCRKaCKTdeI/AAAAAAAAAIc/jMngfcb9Vlo/S220/sarthe.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3619783675451947013.post-8949145683703535958</id><published>2009-10-27T21:05:00.000-07:00</published><updated>2011-08-16T09:06:42.215-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='database design'/><category scheme='http://www.blogger.com/atom/ns#' term='database'/><title type='text'>Storing a tree structure in a database</title><content type='html'>&lt;i&gt;&lt;b&gt;NOTE:&lt;/b&gt;  I fixed a bug in the node update trigger on 16 August 2011.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;I think that one of the more tough problems in database design is how to store tree data (arbitrary depth parent-child relationships, where a child has at most one parent).&lt;br /&gt;&lt;br /&gt;The two most common approaches are the Adjacency List model, and the Nested Set model.  Both are explained and compared &lt;a href="http://www.sitepoint.com/article/hierarchical-data-database/"&gt;here&lt;/a&gt;.  This &lt;a href="http://stackoverflow.com/questions/935098/database-structure-for-tree-data-structure"&gt;forum post&lt;/a&gt; also has some good links to information on the two models.&lt;br /&gt;&lt;br /&gt;In my opinion the major advantage and disadvantage of each are:&lt;br /&gt;&lt;br /&gt;Adjacency Lists:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Major advantage:&amp;nbsp; Simple and easy to understand.&lt;/li&gt;&lt;li&gt;Major disadvantage:&amp;nbsp; It takes multiple queries to find all ancestors or all descendants of a node.&lt;/li&gt;&lt;/ul&gt;Nested Sets:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Major advantage:&amp;nbsp; A single query can find all ancestors or all descendants of a node.&lt;/li&gt;&lt;li&gt;Major disadvantage:&amp;nbsp; Modifying the tree structure affects half the nodes in the tree, on average.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;What if you have a large tree (hundreds of millions of nodes), with fairly frequent changes to the tree structure, and you need to be able to run queries that access all ancestors or descendants of a node?&amp;nbsp; Since the nested set model would make it very difficult to make frequent modifications to the hierarchy, the other option is to do some denormalization of the adjacency list model so that we can query for ancestors and descendants of a node.&lt;br /&gt;&lt;br /&gt;Let's say we have a simple node table:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;CREATE TABLE node (&lt;br /&gt;&amp;nbsp;node_id INTEGER PRIMARY KEY AUTO_INCREMENT,&lt;br /&gt;&amp;nbsp;parent_id INTEGER,&lt;br /&gt;&lt;br /&gt;&amp;nbsp;CONSTRAINT fk_node__parent FOREIGN KEY (parent_id) REFERENCES node (node_id) ON DELETE CASCADE&lt;br /&gt;) ENGINE=InnoDB;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Each node has at most one parent.&amp;nbsp; Root nodes have a parent of null.&amp;nbsp; Now we create an ancestor list, which is our denormalization table:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;CREATE TABLE node_ancestry_link (&lt;br /&gt;&amp;nbsp;node_id INTEGER UNSIGNED NOT NULL,&lt;br /&gt;&amp;nbsp;ancestor_id INTEGER UNSIGNED NOT NULL,&lt;br /&gt;&lt;br /&gt;&amp;nbsp;PRIMARY KEY(node_id, ancestor_id),&lt;br /&gt;&amp;nbsp;INDEX ix_node_anc__anc_node (ancestor_id, node_id),&lt;br /&gt;&amp;nbsp;CONSTRAINT fk_node_anc__node FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE,&lt;br /&gt;&amp;nbsp;CONSTRAINT fk_node_anc__anc FOREIGN KEY (ancestor_id) REFERENCES node(node_id) ON DELETE CASCADE&lt;br /&gt;) ENGINE=InnoDB;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Each node has an entry in the ancestry table for each of its ancestors.&amp;nbsp; This table will grow much more quickly than the node table, especially for deep trees.&amp;nbsp; This denormalization allows us to write queries to get all ancestors of a node:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;SELECT l.ancestor_id FROM node n&lt;br /&gt;JOIN node_ancestry_link l on n.node_id = l.node_id&lt;br /&gt;WHERE n.node_id = :nodeid;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;and all descendants of a node:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;SELECT l.ancestor_id FROM node n&lt;br /&gt;JOIN node_ancestry_link l on n.node_id = l.ancestor_id&lt;br /&gt;WHERE n.node_id = :nodeid;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Now, to help us keep the ancestry table in sync as changes are made in the node table, we define some triggers:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;DELIMITER |&lt;br /&gt;&lt;br /&gt;CREATE TRIGGER tr_node_ins AFTER INSERT ON node&lt;br /&gt;FOR EACH ROW&lt;br /&gt;BEGIN&lt;br /&gt;&amp;nbsp;&amp;nbsp;INSERT INTO node_ancestry_link (node_id, ancestor_id) VALUES (NEW.node_id, NEW.node_id);&lt;br /&gt;&amp;nbsp;&amp;nbsp;IF NEW.parent_id IS NOT NULL THEN&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INSERT INTO node_ancestry_link (node_id, ancestor_id) SELECT NEW.node_id, l.ancestor_id FROM node_ancestry_link l WHERE l.node_id = NEW.parent_id;&lt;br /&gt;&amp;nbsp;&amp;nbsp;END IF;&lt;br /&gt;END&lt;br /&gt;|&lt;br /&gt;&lt;br /&gt;CREATE TRIGGER tr_node_upd AFTER UPDATE ON node&lt;br /&gt;FOR EACH ROW&lt;br /&gt;BEGIN&lt;br /&gt;&amp;nbsp;&amp;nbsp;IF NEW.parent_id &lt;&gt; OLD.parent_id OR ((NEW.parent_id IS NULL) &lt;&gt; (OLD.parent_id IS NULL)) THEN&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;IF OLD.parent_id IS NOT NULL THEN&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DELETE FROM links USING node_ancestry_link links&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;JOIN node_ancestry_link anclinks ON links.ancestor_id = anclinks.ancestor_id &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;JOIN node_ancestry_link deslinks ON links.node_id = deslinks.node_id&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHERE anclinks.node_id = OLD.parent_id &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AND deslinks.ancestor_id = NEW.node_id;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;END IF;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;IF NEW.parent_id IS NOT NULL THEN&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INSERT INTO node_ancestry_link (node_id, ancestor_id) &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SELECT desnodes.node_id, ancnodes.ancestor_id &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FROM node_ancestry_link ancnodes &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CROSS JOIN node_ancestry_link desnodes&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHERE ancnodes.node_id = NEW.parent_id&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AND desnodes.ancestor_id = NEW.node_id;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;END IF;&lt;br /&gt;&amp;nbsp;&amp;nbsp;END IF;&lt;br /&gt;END&lt;br /&gt;|&lt;br /&gt;&lt;br /&gt;DELIMITER ;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;With the triggers in place, we can edit the node hierarchy without having to worry about the ancestry table.&amp;nbsp; We only need to worry about inserts and updates because of the CASCADE DELETEs on the ancestry table foreign keys.&lt;br /&gt;&lt;br /&gt;If you need to know how many descendants a particular node has, you may want to track the descendant count in the node table, since the count queries will be expensive for large trees.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;You could augment the triggers to update the descendant counts when nodes are inserted, updated, or deleted.&amp;nbsp; This would require adding a delete trigger.&amp;nbsp; The one gotcha with doing this in MySQL is that you can't depend on cascade deletes when you delete nodes.&amp;nbsp; MySQL has a bug/feature that cascade deletes don't fire delete triggers.&amp;nbsp; For keeping descendant counts up-to-date this isn't a problem, as long as you take it into account, and when a node is deleted, subtract its descendant count from its ancestors.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3619783675451947013-8949145683703535958?l=nathansnoggin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nathansnoggin.blogspot.com/feeds/8949145683703535958/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nathansnoggin.blogspot.com/2009/10/storing-tree-structure-in-database.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/8949145683703535958'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/8949145683703535958'/><link rel='alternate' type='text/html' href='http://nathansnoggin.blogspot.com/2009/10/storing-tree-structure-in-database.html' title='Storing a tree structure in a database'/><author><name>nathan</name><uri>http://www.blogger.com/profile/03220491542181069595</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='33' height='21' src='http://bp1.blogger.com/_t6Rtr5x_xKE/SCRKaCKTdeI/AAAAAAAAAIc/jMngfcb9Vlo/S220/sarthe.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3619783675451947013.post-4730466844339618210</id><published>2009-09-01T14:43:00.000-07:00</published><updated>2009-09-01T14:43:04.677-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='flex'/><category scheme='http://www.blogger.com/atom/ns#' term='flash'/><title type='text'>re-dispatching events in Flash</title><content type='html'>I recently ran into a head-scratcher, and couldn't find any help online, so I thought I'd post my solution.&lt;br /&gt;&lt;br /&gt;I had a custom flex component, ZoomControl:&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;&amp;lt;mx:VBox&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;lt;mx:Metadata&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[Event(name="zoomChanged", type="ZoomEvent")]&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;lt;/mx:Metadata&amp;gt;&lt;br /&gt;...&lt;br /&gt;&amp;lt;/mx:VBox&amp;gt&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;ZoomControl can throw a "zoomChanged" event, of a custom class ZoomEvent.  I then created a Toolbar custom component that contained the ZoomControl:&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;&amp;lt;mx:VBox&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;lt;mx:Metadata&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[Event(name="zoomChanged", type="ZoomEvent")]&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;lt;/mx:Metadata&amp;gt;&lt;br /&gt;...&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;lt;ZoomControl&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;zoomChanged="dispatchEvent(event)"&lt;br /&gt;&amp;nbsp;&amp;nbsp;/&amp;gt;&lt;br /&gt;&amp;lt;/mx:VBox&amp;gt&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;When the ZoomControl dispatches a ZoomEvent, I want my Toolbar to re-dispatch the event.  Seems simple, right?  But when the Toolbar calls dispatchEvent, it throws an exception:&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;&lt;blockquote&gt;TypeError: Error #1034: Type Coercion failed: cannot convert flash.events::Event@19ea2df1 to footnote.imageviewer.events.ZooomEvent.&lt;br /&gt; at flash.events::EventDispatcher/dispatchEventFunction()&lt;br /&gt; at flash.events::EventDispatcher/dispatchEvent()&lt;br /&gt; at mx.core::UIComponent/dispatchEvent()[E:\dev\3.0.x\frameworks\projects\framework\src\mx\core\UIComponent.as:9051]&lt;br /&gt; at Toolbar/__zoomControl_zoomChanged()...&lt;br /&gt;&lt;/blockquote&gt;&lt;/span&gt;&lt;br /&gt;After some wasted time trying to make sure that it really was a ZoomEvent being passed to dispatchEvent, I finally took time to read the documentation on UIComponent.dispatchEvent:&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;&lt;blockquote&gt;     *  If the event is being redispatched, a clone of the event is created automatically. &lt;br /&gt;     *  After an event is dispatched, its &lt;code&gt;target&lt;/code&gt; property cannot be changed, &lt;br /&gt;     *  so you must create a new copy of the event for redispatching to work.&lt;br /&gt;&lt;/blockquote&gt;&lt;/span&gt;&lt;br /&gt;My problem was that I needed to override the clone method in my ZoomEvent.  The dispatchEvent method was calling the base Event.clone(), which was returning an Event, of course.  Overriding the clone method to return a ZoomEvent solved the problem.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3619783675451947013-4730466844339618210?l=nathansnoggin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nathansnoggin.blogspot.com/feeds/4730466844339618210/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nathansnoggin.blogspot.com/2009/09/re-dispatching-events-in-flash.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/4730466844339618210'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/4730466844339618210'/><link rel='alternate' type='text/html' href='http://nathansnoggin.blogspot.com/2009/09/re-dispatching-events-in-flash.html' title='re-dispatching events in Flash'/><author><name>nathan</name><uri>http://www.blogger.com/profile/03220491542181069595</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='33' height='21' src='http://bp1.blogger.com/_t6Rtr5x_xKE/SCRKaCKTdeI/AAAAAAAAAIc/jMngfcb9Vlo/S220/sarthe.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3619783675451947013.post-3603147080065398947</id><published>2009-06-12T13:43:00.000-07:00</published><updated>2009-06-12T14:04:56.338-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mac'/><category scheme='http://www.blogger.com/atom/ns#' term='superduper'/><title type='text'>SuperDuper "Smart Update" doesn't stay smart</title><content type='html'>I use &lt;a href="http://www.shirt-pocket.com/SuperDuper/SuperDuperDescription.html"&gt;SuperDuper&lt;/a&gt; for backups on my work machine (MacBook Pro), and I have it set up to backup daily to an external drive using "Smart Update", which is supposed to be fast and only copy things that have changed.  I really have no idea how it works, nor do I care, as long as it's working.&lt;br /&gt;&lt;br /&gt;The problem is that it has started to take longer and longer to run.  It had reached the point where it was taking 2 hours or more to finish (my drive is 120G and I don't back all of it up).  I found very little help by searching on Google, so I asked the Sysadmin, who also uses SuperDuper, if he had seen the same problem.  He said "I don't know, I only do full backups once a week".&lt;br /&gt;&lt;br /&gt;That made me think that maybe SuperDuper just doesn't handle backing up repeatedly using Smart Update only.  So I revised my schedule to do a full backup once a week, and Smart Updates daily.  Success!  My daily backups are back down to 15 minutes or less!  I thought I'd post this in case it helps someone else.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3619783675451947013-3603147080065398947?l=nathansnoggin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nathansnoggin.blogspot.com/feeds/3603147080065398947/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nathansnoggin.blogspot.com/2009/06/superduper-smart-update-doesnt-stay.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/3603147080065398947'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/3603147080065398947'/><link rel='alternate' type='text/html' href='http://nathansnoggin.blogspot.com/2009/06/superduper-smart-update-doesnt-stay.html' title='SuperDuper &quot;Smart Update&quot; doesn&apos;t stay smart'/><author><name>nathan</name><uri>http://www.blogger.com/profile/03220491542181069595</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='33' height='21' src='http://bp1.blogger.com/_t6Rtr5x_xKE/SCRKaCKTdeI/AAAAAAAAAIc/jMngfcb9Vlo/S220/sarthe.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3619783675451947013.post-1966727949425406525</id><published>2009-04-24T15:52:00.000-07:00</published><updated>2009-04-24T17:18:32.121-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='postgresql'/><title type='text'>SELECT DISTINCT with ORDER BY</title><content type='html'>I recently wrote a query in MySQL that didn't seem to be returning the right results, and at first I couldn't figure out why.  Here is a toy example, where we are tracking pages and page views (one-to-many relationship):&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;create table page (&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;page_id integer unsigned primary key, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name varchar(32) not null, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;created datetime not null&lt;br /&gt;) engine=InnoDB;&lt;br /&gt;&lt;br /&gt;create table page_view (&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;page_view_id integer unsigned primary key, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;page_id integer unsigned not null, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;created datetime not null, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;foreign key (page_id) references page (page_id) on delete cascade&lt;br /&gt;) engine=InnoDB;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;What I want to get is the most recently viewed pages.  Let's say I have the following data in my tables:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;mysql&gt; select * from page;&lt;br /&gt;+---------+--------+---------------------+&lt;br /&gt;| page_id | name&amp;nbsp;&amp;nbsp;&amp;nbsp;| created&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;|&lt;br /&gt;+---------+--------+---------------------+&lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1 | page 1 | 2000-01-01 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2 | page 2 | 2000-01-02 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3 | page 3 | 2000-01-03 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4 | page 4 | 2000-01-04 00:00:00 | &lt;br /&gt;+---------+--------+---------------------+&lt;br /&gt;4 rows in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&gt; select * from page_view;&lt;br /&gt;+--------------+---------+---------------------+&lt;br /&gt;| page_view_id | page_id | created&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;|&lt;br /&gt;+--------------+---------+---------------------+&lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1 |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3 | 2000-01-01 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2 |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1 | 2000-01-02 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3 |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1 | 2000-01-03 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4 |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3 | 2000-01-04 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5 |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2 | 2000-01-05 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;6 |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4 | 2000-01-06 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;7 |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2 | 2000-01-07 00:00:00 | &lt;br /&gt;+--------------+---------+---------------------+&lt;br /&gt;7 rows in set (0.00 sec)&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;What I want to get back is page 2 (most recently viewed), then page 4, then page 3, then page 1.&lt;br /&gt;&lt;br /&gt;So I write my query:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;mysql&gt; select distinct p.page_id, p.name, p.created from page p join page_view pv on p.page_id = pv.page_id order by pv.created desc;&lt;br /&gt;+---------+--------+---------------------+&lt;br /&gt;| page_id | name&amp;nbsp;&amp;nbsp;&amp;nbsp;| created&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;|&lt;br /&gt;+---------+--------+---------------------+&lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4 | page 4 | 2000-01-04 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2 | page 2 | 2000-01-02 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1 | page 1 | 2000-01-01 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3 | page 3 | 2000-01-03 00:00:00 | &lt;br /&gt;+---------+--------+---------------------+&lt;br /&gt;4 rows in set (0.00 sec)&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;That's not right at all!  What's going on?  &lt;br /&gt;&lt;br /&gt;The problem is that I'm using distinct just on the page table, but ordering by the page_view table.  Since there is a many-to-one, what is the database supposed to do when a page has multiple views?  which view should it use for the order by?&lt;br /&gt;&lt;br /&gt;What I wanted the query to do is first join, then order, then apply the distinct.  That's not what MySQL does, though.  It first joins, then applies the distinct, then orders the results (or something like that).  You can think of it like MySQL going sequentially through the page_view table, finding rows with distinct page ids.  So it would pick rows 1,2,5,6:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;+--------------+---------+---------------------+&lt;br /&gt;| page_view_id | page_id | created&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;|&lt;br /&gt;+--------------+---------+---------------------+&lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1 |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3 | 2000-01-01 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2 |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1 | 2000-01-02 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5 |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2 | 2000-01-05 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;6 |&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4 | 2000-01-06 00:00:00 | &lt;br /&gt;+--------------+---------+---------------------+&lt;br /&gt;4 rows in set (0.00 sec)&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;You can see that if you order those by created, you get the page order that the (badly written) query returned (4,2,1,3).&lt;br /&gt;&lt;br /&gt;We can force MySQL to do things in the order we want by changing the query to:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;mysql&gt; select distinct p.page_id, p.name, p.created from (select p.page_id, p.name, p.created from page p join page_view pv on p.page_id = pv.page_id order by pv.created desc) as p;&lt;br /&gt;+---------+--------+---------------------+&lt;br /&gt;| page_id | name&amp;nbsp;&amp;nbsp;&amp;nbsp;| created&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;|&lt;br /&gt;+---------+--------+---------------------+&lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2 | page 2 | 2000-01-02 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4 | page 4 | 2000-01-04 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3 | page 3 | 2000-01-03 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1 | page 1 | 2000-01-01 00:00:00 | &lt;br /&gt;+---------+--------+---------------------+&lt;br /&gt;4 rows in set (0.00 sec)&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;But I think that's kind of a hack, and depends on MySQL doing the distinct in a certain order (I don't think order by in a subquery is standard sql, and shouldn't necessarily constraint the order of the entire query). So what's the "right" way to write this type of query?&lt;br /&gt;&lt;br /&gt;Before I tackled that, I thought, "What would a strict database like PostgreSQL do with this type of query?"  My hope was that it would throw it out altogether.  And it does.  Here's what I get:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;postgres=# select distinct t1.id, t1.name, t1.created from table1 t1 join table2 t2 on t1.id = t2.table1_id order by t2.created desc;&lt;br /&gt;ERROR:  for SELECT DISTINCT, ORDER BY expressions must appear in select list&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;That's much better, and the error message is very helpful, and makes sense.  So here's the query I came up with that will give the correct results, and is correct SQL, in MySQL...:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;mysql&gt; select p.page_id, p.name, p.created from page p join (select page_id, max(created) as created from page_view group by page_id) v on p.page_id = v.page_id order by v.created desc;&lt;br /&gt;+---------+--------+---------------------+&lt;br /&gt;| page_id | name&amp;nbsp;&amp;nbsp;&amp;nbsp;| created&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;|&lt;br /&gt;+---------+--------+---------------------+&lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2 | page 2 | 2000-01-02 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4 | page 4 | 2000-01-04 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3 | page 3 | 2000-01-03 00:00:00 | &lt;br /&gt;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1 | page 1 | 2000-01-01 00:00:00 | &lt;br /&gt;+---------+--------+---------------------+&lt;br /&gt;4 rows in set (0.00 sec)&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;...and in PostgreSQL:&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;postgres=# select p.page_id, p.name, p.created from page p join (select page_id, max(created) as created from page_view group by page_id) v on p.page_id = v.page_id order by v.created desc;&lt;br /&gt; page_id |&amp;nbsp;&amp;nbsp;name&amp;nbsp;&amp;nbsp;|&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;created&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;---------+--------+---------------------&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2 | page 2 | 2000-01-02 00:00:00&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4 | page 4 | 2000-01-04 00:00:00&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3 | page 3 | 2000-01-03 00:00:00&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1 | page 1 | 2000-01-01 00:00:00&lt;br /&gt;(4 rows)&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Is there a better performing query out there to do the same thing?  I'd love to know, please leave a comment! :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3619783675451947013-1966727949425406525?l=nathansnoggin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nathansnoggin.blogspot.com/feeds/1966727949425406525/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nathansnoggin.blogspot.com/2009/04/select-distinct-with-order-by.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/1966727949425406525'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/1966727949425406525'/><link rel='alternate' type='text/html' href='http://nathansnoggin.blogspot.com/2009/04/select-distinct-with-order-by.html' title='SELECT DISTINCT with ORDER BY'/><author><name>nathan</name><uri>http://www.blogger.com/profile/03220491542181069595</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='33' height='21' src='http://bp1.blogger.com/_t6Rtr5x_xKE/SCRKaCKTdeI/AAAAAAAAAIc/jMngfcb9Vlo/S220/sarthe.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3619783675451947013.post-5613735907168750861</id><published>2009-04-06T12:25:00.000-07:00</published><updated>2009-04-16T12:43:01.417-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='triggers'/><title type='text'>safely editing MySQL triggers in a production database</title><content type='html'>MySQL does not provide an atomic CREATE OR REPLACE TRIGGER, or an ALTER TRIGGER statement that will safely modify a trigger on a database while it is in use.  The only way to update a TRIGGER is with a DROP and then a CREATE.&lt;br /&gt;&lt;br /&gt;Why is that a big deal?  Say, for example, you are using triggers to keep row counts up-to-date in a summary table.  You may miss some inserts while you are issuing the DROP and then the CREATE.  To verify this, I used mysqlslap.  Here is my schema script:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;drop table if exists triggertest.record_count;&lt;br /&gt;create table triggertest.record_count&lt;br /&gt;(&lt;br /&gt;id INTEGER UNSIGNED PRIMARY KEY AUTO_INCREMENT,&lt;br /&gt;count_name VARCHAR(64) NOT NULL,&lt;br /&gt;count_value INTEGER UNSIGNED NOT NULL DEFAULT 1,&lt;br /&gt;UNIQUE (count_name)&lt;br /&gt;) ENGINE=InnoDB;&lt;br /&gt;&lt;br /&gt;drop table if exists triggertest.record_table;&lt;br /&gt;create table triggertest.record_table&lt;br /&gt;(&lt;br /&gt;id INTEGER UNSIGNED PRIMARY KEY AUTO_INCREMENT,&lt;br /&gt;some_value VARCHAR(64) NOT NULL&lt;br /&gt;) ENGINE=InnoDB;&lt;br /&gt;&lt;br /&gt;DROP PROCEDURE IF EXISTS triggertest.sp_increment_record_count;&lt;br /&gt;&lt;br /&gt;DELIMITER |&lt;br /&gt;&lt;br /&gt;CREATE PROCEDURE triggertest.sp_increment_record_count(IN countname VARCHAR(64))&lt;br /&gt;BEGIN&lt;br /&gt;   INSERT INTO triggertest.record_count(count_name, count_value) VALUES (countname,1) ON DUPLICATE KEY UPDATE count_value = count_value + 1;&lt;br /&gt;END&lt;br /&gt;|&lt;br /&gt;&lt;br /&gt;DELIMITER ;&lt;br /&gt;&lt;br /&gt;DROP TRIGGER IF EXISTS triggertest.tr_record_table_ins;&lt;br /&gt;&lt;br /&gt;CREATE TRIGGER triggertest.tr_record_table_ins AFTER INSERT ON triggertest.record_table&lt;br /&gt;FOR EACH ROW CALL triggertest.sp_increment_record_count('record_table');&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I used mysqlslap to run a lot of inserts against the record_table, and while that was running, I re-created the trigger by running this script a bunch of times:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;DROP TRIGGER IF EXISTS triggertest.tr_record_table_ins;&lt;br /&gt;CREATE TRIGGER triggertest.tr_record_table_ins AFTER INSERT ON triggertest.record_table&lt;br /&gt;FOR EACH ROW CALL triggertest.sp_increment_record_count('record_table');&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I then verified that the count_value in record_count was smaller than the number of records in record_table:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;mysql&gt; select * from record_count;&lt;br /&gt;+----+--------------+-------------+&lt;br /&gt;| id | count_name   | count_value |&lt;br /&gt;+----+--------------+-------------+&lt;br /&gt;|  1 | record_table |        9944 |&lt;br /&gt;+----+--------------+-------------+&lt;br /&gt;1 row in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&gt; select count(*) from record_table;&lt;br /&gt;+----------+&lt;br /&gt;| count(*) |&lt;br /&gt;+----------+&lt;br /&gt;|    10000 |&lt;br /&gt;+----------+&lt;br /&gt;1 row in set (0.00 sec)&lt;br /&gt;&lt;br /&gt;mysql&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;At first, I was not sure there would be a solution to this.  I realize that you can lock tables, but my first guess was that since ddl (DROP, CREATE, etc.) statements cause an implicit commit, that my locks would be released.&lt;br /&gt;&lt;br /&gt;Fortunately, as the &lt;a href="http://dev.mysql.com/doc/refman/5.0/en/lock-tables-and-transactions.html"&gt;MySQL documentation&lt;/a&gt; explains, if you use LOCK TABLES, implicit commits don't release your locks.  From the docs:&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;...statements that implicitly cause transactions to be committed do not release existing locks.&lt;/blockquote&gt;&lt;br /&gt;So the safe way to recreate my trigger is like this:&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;set autocommit=0;&lt;br /&gt;lock tables triggertest.record_table write;&lt;br /&gt;DROP TRIGGER IF EXISTS triggertest.tr_record_table_ins;&lt;br /&gt;CREATE TRIGGER triggertest.tr_record_table_ins AFTER INSERT ON triggertest.record_table FOR EACH ROW CALL triggertest.sp_increment_record_count('record_table');&lt;br /&gt;unlock tables;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I had some trouble testing this with mysqlslap, even with only one thread running inserts, because of some locking issues (the inserts would error out with a 'Lock wait timeout'), but I did get a few tests to make it through, so I could verify that the record_count matched the number of rows in the record_table. So the worst-case seems to be that some of the inserts on the production database may hit a lock wait timeout, but no inserts will miss firing the triggers!&lt;br /&gt;&lt;br /&gt;UPDATE:  Soon after writing this post, I came across this:  &lt;a href="http://code.openark.org/blog/mysql/why-of-the-week"&gt;http://code.openark.org/blog/mysql/why-of-the-week&lt;/a&gt;, which may explain why I was having so many problems with deadlocks when I tried to run against a database that was in use.  I'm not talking about deadlocks where MySQL detects it and rolls back a transaction.  Things would just lock up.  No deadlock detected, no lock wait timeout, just locked up, until I killed a query.  So while the solution above should work in theory, beware of MySQL locking bugs...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3619783675451947013-5613735907168750861?l=nathansnoggin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nathansnoggin.blogspot.com/feeds/5613735907168750861/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nathansnoggin.blogspot.com/2009/04/safely-editing-mysql-triggers-in.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/5613735907168750861'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/5613735907168750861'/><link rel='alternate' type='text/html' href='http://nathansnoggin.blogspot.com/2009/04/safely-editing-mysql-triggers-in.html' title='safely editing MySQL triggers in a production database'/><author><name>nathan</name><uri>http://www.blogger.com/profile/03220491542181069595</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='33' height='21' src='http://bp1.blogger.com/_t6Rtr5x_xKE/SCRKaCKTdeI/AAAAAAAAAIc/jMngfcb9Vlo/S220/sarthe.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3619783675451947013.post-5753706031688373762</id><published>2009-01-09T11:31:00.000-08:00</published><updated>2009-01-09T12:50:48.808-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='transactions'/><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='postgresql'/><title type='text'>PostgreSQL transactions and error handling</title><content type='html'>I'm taking our web app that runs on MySQL and seeing what it would take to get it running on PostgreSQL.  I just discovered one rather glaring difference between PostgreSQL and most other DBMSs.&lt;br /&gt;&lt;br /&gt;Here is an example to demonstrate:&lt;br /&gt;&lt;br /&gt;I have a table:  my_table, with the following row:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;----------------&lt;br /&gt;|&amp;nbsp;id&amp;nbsp;&amp;nbsp;|&amp;nbsp;&amp;nbsp;val&amp;nbsp;&amp;nbsp;&amp;nbsp;|&lt;br /&gt;----------------&lt;br /&gt;|&amp;nbsp;3&amp;nbsp;&amp;nbsp;&amp;nbsp;|&amp;nbsp;row&amp;nbsp;3&amp;nbsp;&amp;nbsp;|&lt;br /&gt;----------------&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Let's say I want to make sure I have rows for ids 1 through 5 in the table.  This is a case where I would use MySQL's 'INSERT IGNORE' statement, which doesn't exist in PostgreSQL.  I have at least two options:  I can create a stored procedure to do it, or I can do it in code.&lt;br /&gt;&lt;br /&gt;Let's say I create a procedure:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;CREATE OR REPLACE FUNCTION my_proc() RETURNS void AS&lt;br /&gt;$$&lt;br /&gt;BEGIN&lt;br /&gt;&amp;nbsp;&amp;nbsp;FOR i IN 1..5 LOOP&lt;br /&gt;&amp;nbsp;&amp;nbsp;BEGIN&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INSERT INTO my_table(id, val) VALUES (i, 'row ' || i);&lt;br /&gt;&amp;nbsp;&amp;nbsp;EXCEPTION WHEN unique_violation THEN&lt;br /&gt;&amp;nbsp;&amp;nbsp;-- do nothing&lt;br /&gt;&amp;nbsp;&amp;nbsp;END;&lt;br /&gt;&amp;nbsp;&amp;nbsp;END LOOP;&lt;br /&gt;END;&lt;br /&gt;$$ LANGUAGE plpgsql;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;All is well, this type of exception handling is standard in stored procedures.  Here's the entire test:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;postgres=#&amp;nbsp;create&amp;nbsp;database&amp;nbsp;my_test;&lt;br /&gt;CREATE&amp;nbsp;DATABASE&lt;br /&gt;postgres=#&amp;nbsp;\c&amp;nbsp;my_test;&lt;br /&gt;You&amp;nbsp;are&amp;nbsp;now&amp;nbsp;connected&amp;nbsp;to&amp;nbsp;database&amp;nbsp;"my_test".&lt;br /&gt;my_test=#&amp;nbsp;create&amp;nbsp;table&amp;nbsp;my_table&amp;nbsp;(id&amp;nbsp;INTEGER&amp;nbsp;PRIMARY&amp;nbsp;KEY,&amp;nbsp;val&amp;nbsp;VARCHAR(64));&lt;br /&gt;NOTICE:&amp;nbsp;&amp;nbsp;CREATE&amp;nbsp;TABLE&amp;nbsp;/&amp;nbsp;PRIMARY&amp;nbsp;KEY&amp;nbsp;will&amp;nbsp;create&amp;nbsp;implicit&amp;nbsp;index&amp;nbsp;"my_table_pkey"&amp;nbsp;for&amp;nbsp;table&amp;nbsp;"my_table"&lt;br /&gt;CREATE&amp;nbsp;TABLE&lt;br /&gt;my_test=#&amp;nbsp;CREATE&amp;nbsp;OR&amp;nbsp;REPLACE&amp;nbsp;FUNCTION&amp;nbsp;my_proc()&amp;nbsp;RETURNS&amp;nbsp;void&amp;nbsp;AS&lt;br /&gt;my_test-#&amp;nbsp;$$&lt;br /&gt;my_test$#&amp;nbsp;BEGIN&lt;br /&gt;my_test$#&amp;nbsp;&amp;nbsp;&amp;nbsp;FOR&amp;nbsp;i&amp;nbsp;IN&amp;nbsp;1..5&amp;nbsp;LOOP&lt;br /&gt;my_test$#&amp;nbsp;&amp;nbsp;&amp;nbsp;BEGIN&lt;br /&gt;my_test$#&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INSERT&amp;nbsp;INTO&amp;nbsp;my_table(id,&amp;nbsp;val)&amp;nbsp;VALUES&amp;nbsp;(i,&amp;nbsp;'row&amp;nbsp;'&amp;nbsp;||&amp;nbsp;i);&lt;br /&gt;my_test$#&amp;nbsp;&amp;nbsp;&amp;nbsp;EXCEPTION&amp;nbsp;WHEN&amp;nbsp;unique_violation&amp;nbsp;THEN&lt;br /&gt;my_test$#&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;do&amp;nbsp;nothing&lt;br /&gt;my_test$#&amp;nbsp;&amp;nbsp;&amp;nbsp;END;&lt;br /&gt;my_test$#&amp;nbsp;&amp;nbsp;&amp;nbsp;END&amp;nbsp;LOOP;&lt;br /&gt;my_test$#&amp;nbsp;END;&lt;br /&gt;my_test$#&amp;nbsp;$$&amp;nbsp;LANGUAGE&amp;nbsp;plpgsql;&lt;br /&gt;CREATE&amp;nbsp;FUNCTION&lt;br /&gt;my_test=#&amp;nbsp;insert&amp;nbsp;into&amp;nbsp;my_table&amp;nbsp;values&amp;nbsp;(3,'row&amp;nbsp;3');&lt;br /&gt;INSERT&amp;nbsp;0&amp;nbsp;1&lt;br /&gt;my_test=#&amp;nbsp;select&amp;nbsp;*&amp;nbsp;from&amp;nbsp;my_table;&lt;br /&gt;&amp;nbsp;id&amp;nbsp;|&amp;nbsp;&amp;nbsp;val&amp;nbsp;&amp;nbsp;&lt;br /&gt;----+-------&lt;br /&gt;&amp;nbsp;&amp;nbsp;3&amp;nbsp;|&amp;nbsp;row&amp;nbsp;3&lt;br /&gt;(1&amp;nbsp;row)&lt;br /&gt;&lt;br /&gt;my_test=#&amp;nbsp;select&amp;nbsp;my_proc();&lt;br /&gt;&amp;nbsp;my_proc&amp;nbsp;&lt;br /&gt;---------&lt;br /&gt;&amp;nbsp;&lt;br /&gt;(1&amp;nbsp;row)&lt;br /&gt;&lt;br /&gt;my_test=#&amp;nbsp;select&amp;nbsp;*&amp;nbsp;from&amp;nbsp;my_table;&lt;br /&gt;&amp;nbsp;id&amp;nbsp;|&amp;nbsp;&amp;nbsp;val&amp;nbsp;&amp;nbsp;&lt;br /&gt;----+-------&lt;br /&gt;&amp;nbsp;&amp;nbsp;3&amp;nbsp;|&amp;nbsp;row&amp;nbsp;3&lt;br /&gt;&amp;nbsp;&amp;nbsp;1&amp;nbsp;|&amp;nbsp;row&amp;nbsp;1&lt;br /&gt;&amp;nbsp;&amp;nbsp;2&amp;nbsp;|&amp;nbsp;row&amp;nbsp;2&lt;br /&gt;&amp;nbsp;&amp;nbsp;4&amp;nbsp;|&amp;nbsp;row&amp;nbsp;4&lt;br /&gt;&amp;nbsp;&amp;nbsp;5&amp;nbsp;|&amp;nbsp;row&amp;nbsp;5&lt;br /&gt;(5&amp;nbsp;rows)&lt;br /&gt;&lt;br /&gt;my_test=#&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Now here's the twist - you can't (really) do the same thing outside of a stored procedure (without using savepoints, as I'll get to later). Here's what happens:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;my_test=#&amp;nbsp;\set&amp;nbsp;AUTOCOMMIT&amp;nbsp;OFF&lt;br /&gt;my_test=#&amp;nbsp;delete&amp;nbsp;from&amp;nbsp;my_table&amp;nbsp;where&amp;nbsp;id&amp;nbsp;&lt;&gt;&amp;nbsp;3;&lt;br /&gt;DELETE&amp;nbsp;4&lt;br /&gt;my_test=#&amp;nbsp;commit;&lt;br /&gt;COMMIT&lt;br /&gt;my_test=#&amp;nbsp;insert&amp;nbsp;into&amp;nbsp;my_table&amp;nbsp;values&amp;nbsp;(1,&amp;nbsp;'row&amp;nbsp;1');&lt;br /&gt;INSERT&amp;nbsp;0&amp;nbsp;1&lt;br /&gt;my_test=#&amp;nbsp;insert&amp;nbsp;into&amp;nbsp;my_table&amp;nbsp;values&amp;nbsp;(2,&amp;nbsp;'row&amp;nbsp;2');&lt;br /&gt;INSERT&amp;nbsp;0&amp;nbsp;1&lt;br /&gt;my_test=#&amp;nbsp;insert&amp;nbsp;into&amp;nbsp;my_table&amp;nbsp;values&amp;nbsp;(3,&amp;nbsp;'row&amp;nbsp;3');&lt;br /&gt;ERROR:&amp;nbsp;&amp;nbsp;duplicate&amp;nbsp;key&amp;nbsp;value&amp;nbsp;violates&amp;nbsp;unique&amp;nbsp;constraint&amp;nbsp;"my_table_pkey"&lt;br /&gt;my_test=#&amp;nbsp;insert&amp;nbsp;into&amp;nbsp;my_table&amp;nbsp;values&amp;nbsp;(4,&amp;nbsp;'row&amp;nbsp;4');&lt;br /&gt;ERROR:&amp;nbsp;&amp;nbsp;current&amp;nbsp;transaction&amp;nbsp;is&amp;nbsp;aborted,&amp;nbsp;commands&amp;nbsp;ignored&amp;nbsp;until&amp;nbsp;end&amp;nbsp;of&amp;nbsp;transaction&amp;nbsp;block&lt;br /&gt;my_test=#&amp;nbsp;insert&amp;nbsp;into&amp;nbsp;my_table&amp;nbsp;values&amp;nbsp;(5,&amp;nbsp;'row&amp;nbsp;5');&lt;br /&gt;ERROR:&amp;nbsp;&amp;nbsp;current&amp;nbsp;transaction&amp;nbsp;is&amp;nbsp;aborted,&amp;nbsp;commands&amp;nbsp;ignored&amp;nbsp;until&amp;nbsp;end&amp;nbsp;of&amp;nbsp;transaction&amp;nbsp;block&lt;br /&gt;my_test=#&amp;nbsp;commit;&lt;br /&gt;ROLLBACK&lt;br /&gt;my_test=#&amp;nbsp;select&amp;nbsp;*&amp;nbsp;from&amp;nbsp;my_table;&lt;br /&gt;&amp;nbsp;id&amp;nbsp;|&amp;nbsp;&amp;nbsp;val&amp;nbsp;&amp;nbsp;&lt;br /&gt;----+-------&lt;br /&gt;&amp;nbsp;&amp;nbsp;3&amp;nbsp;|&amp;nbsp;row&amp;nbsp;3&lt;br /&gt;(1&amp;nbsp;row)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;PostgreSQL forces you to rollback a transaction that hits any error.  There is no exception handling!  Granted, you can get around this by using savepoints:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;my_test=#&amp;nbsp;insert&amp;nbsp;into&amp;nbsp;my_table&amp;nbsp;values&amp;nbsp;(1,&amp;nbsp;'row&amp;nbsp;1');&lt;br /&gt;INSERT&amp;nbsp;0&amp;nbsp;1&lt;br /&gt;my_test=#&amp;nbsp;insert&amp;nbsp;into&amp;nbsp;my_table&amp;nbsp;values&amp;nbsp;(2,&amp;nbsp;'row&amp;nbsp;2');&lt;br /&gt;INSERT&amp;nbsp;0&amp;nbsp;1&lt;br /&gt;my_test=#&amp;nbsp;SAVEPOINT&amp;nbsp;my_hack;&lt;br /&gt;SAVEPOINT&lt;br /&gt;my_test=#&amp;nbsp;insert&amp;nbsp;into&amp;nbsp;my_table&amp;nbsp;values&amp;nbsp;(3,&amp;nbsp;'row&amp;nbsp;3');&lt;br /&gt;ERROR:&amp;nbsp;&amp;nbsp;duplicate&amp;nbsp;key&amp;nbsp;value&amp;nbsp;violates&amp;nbsp;unique&amp;nbsp;constraint&amp;nbsp;"my_table_pkey"&lt;br /&gt;my_test=#&amp;nbsp;ROLLBACK&amp;nbsp;TO&amp;nbsp;SAVEPOINT&amp;nbsp;my_hack;&lt;br /&gt;ROLLBACK&lt;br /&gt;my_test=#&amp;nbsp;insert&amp;nbsp;into&amp;nbsp;my_table&amp;nbsp;values&amp;nbsp;(4,&amp;nbsp;'row&amp;nbsp;4');&lt;br /&gt;INSERT&amp;nbsp;0&amp;nbsp;1&lt;br /&gt;my_test=#&amp;nbsp;insert&amp;nbsp;into&amp;nbsp;my_table&amp;nbsp;values&amp;nbsp;(5,&amp;nbsp;'row&amp;nbsp;5');&lt;br /&gt;INSERT&amp;nbsp;0&amp;nbsp;1&lt;br /&gt;my_test=#&amp;nbsp;commit;&lt;br /&gt;COMMIT&lt;br /&gt;my_test=#&amp;nbsp;select&amp;nbsp;*&amp;nbsp;from&amp;nbsp;my_table;&lt;br /&gt;&amp;nbsp;id&amp;nbsp;|&amp;nbsp;&amp;nbsp;val&amp;nbsp;&amp;nbsp;&lt;br /&gt;----+-------&lt;br /&gt;&amp;nbsp;&amp;nbsp;3&amp;nbsp;|&amp;nbsp;row&amp;nbsp;3&lt;br /&gt;&amp;nbsp;&amp;nbsp;1&amp;nbsp;|&amp;nbsp;row&amp;nbsp;1&lt;br /&gt;&amp;nbsp;&amp;nbsp;2&amp;nbsp;|&amp;nbsp;row&amp;nbsp;2&lt;br /&gt;&amp;nbsp;&amp;nbsp;4&amp;nbsp;|&amp;nbsp;row&amp;nbsp;4&lt;br /&gt;&amp;nbsp;&amp;nbsp;5&amp;nbsp;|&amp;nbsp;row&amp;nbsp;5&lt;br /&gt;(5&amp;nbsp;rows)&lt;br /&gt;&lt;br /&gt;my_test=#&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;But I see this as more of a hack than a real solution.&lt;br /&gt;&lt;br /&gt;I can see arguments for both sides of this issue.  I really like this conversation about the issue: &lt;a href="http://www.nabble.com/25P02,-current-transaction-is-aborted,-commands-ignored-until-end-of-transaction-block-td3710080.html"&gt;http://www.nabble.com/25P02,-current-transaction-is-aborted,-commands-ignored-until-end-of-transaction-block-td3710080.html&lt;/a&gt; .  But what bugs me is that stored procedures can do exception handling, but no one else can.  Especially in my case, where I'm using Java and JDBC, which throws exceptions for any errors that come back.  I would rather do my own exception handling, or at least have the option of doing my own.&lt;br /&gt;&lt;br /&gt;So here's my vote for changing PostgreSQL to behave more like MySQL in this case.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3619783675451947013-5753706031688373762?l=nathansnoggin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nathansnoggin.blogspot.com/feeds/5753706031688373762/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nathansnoggin.blogspot.com/2009/01/postgresql-transactions-and-error.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/5753706031688373762'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/5753706031688373762'/><link rel='alternate' type='text/html' href='http://nathansnoggin.blogspot.com/2009/01/postgresql-transactions-and-error.html' title='PostgreSQL transactions and error handling'/><author><name>nathan</name><uri>http://www.blogger.com/profile/03220491542181069595</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='33' height='21' src='http://bp1.blogger.com/_t6Rtr5x_xKE/SCRKaCKTdeI/AAAAAAAAAIc/jMngfcb9Vlo/S220/sarthe.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3619783675451947013.post-4817773037455332041</id><published>2008-11-25T09:59:00.000-08:00</published><updated>2008-11-25T10:15:44.517-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><title type='text'>should you name foreign key constraints in MySQL?</title><content type='html'>In MySQL, if you don't name your foreign key constraints, the database generates a name for them automatically.  Foreign key constraint names must be globally unique within the database, so unless you have a reason to name them, it is probably more hassle than it is worth.&lt;br /&gt;&lt;br /&gt;I happen to have a reason to want to name them, and our setup is probably not that uncommon, so others may discover that it is advantageous to name them, too.  We have three different databases where I work - one for development ('dev'), a staging database for testing each iteration, and patches ('staging'), and, of course, our live database that the site runs on ('live').  Additionally, each developer has a local database on their personal machines, to develop against.&lt;br /&gt;&lt;br /&gt;For each iteration, we create a single schema migration script as we are developing.  It will likely get run in pieces on the dev database, and there may be multiple revisions to a table as the iteration develops.  Usually the script is very final by the time it is run against staging, but there is always the possibility of additional changes late in the game.&lt;br /&gt;&lt;br /&gt;So where foreign key constraint names come into play is when you want an alter statement that can be run against all the different databases without changing.  If you leave the naming up to the database, they are typically named in a sequential fashion (first foreign key constraint will probably have a '_1' at the end, the next will have '_2', etc.).&lt;br /&gt;&lt;br /&gt;The problem is that if you create and drop foreign keys in different orders on different databases, the names won't match up.  The foreign key named 'blah_1' on dev might be on a different column than the one with the same name on staging.  You have to alter them by name, so there is no way to have a single script that will run correctly on all of the databases.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3619783675451947013-4817773037455332041?l=nathansnoggin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nathansnoggin.blogspot.com/feeds/4817773037455332041/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nathansnoggin.blogspot.com/2008/11/should-you-name-foreign-key-constraints.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/4817773037455332041'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/4817773037455332041'/><link rel='alternate' type='text/html' href='http://nathansnoggin.blogspot.com/2008/11/should-you-name-foreign-key-constraints.html' title='should you name foreign key constraints in MySQL?'/><author><name>nathan</name><uri>http://www.blogger.com/profile/03220491542181069595</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='33' height='21' src='http://bp1.blogger.com/_t6Rtr5x_xKE/SCRKaCKTdeI/AAAAAAAAAIc/jMngfcb9Vlo/S220/sarthe.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3619783675451947013.post-4622286349979199296</id><published>2008-10-15T08:47:00.000-07:00</published><updated>2008-10-15T09:12:31.151-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='memcache'/><category scheme='http://www.blogger.com/atom/ns#' term='spring'/><category scheme='http://www.blogger.com/atom/ns#' term='web service'/><category scheme='http://www.blogger.com/atom/ns#' term='aop'/><title type='text'>Spring AOP and @annotation pointcuts</title><content type='html'>I'm working on a Spring-AOP and annotation-based solution for caching web service requests.  Here's the overview:&lt;br /&gt;&lt;br /&gt;I have created an Annotation named "Cacheable", that you use like this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;@Cacheable(seconds=60)&lt;br /&gt;public Data getData(int id)&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;...&lt;br /&gt;}&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Then using Spring's AOP functionality, I want to wrap every method that is annotated as @Cacheable in around advice that uses memcache to return cached results.&lt;br /&gt;&lt;br /&gt;The problem I ran into was getting access to the &lt;span style="font-family:courier new;"&gt;seconds&lt;/span&gt; attribute of the annotation in the advice (for setting the cache timeout).&lt;br /&gt;&lt;br /&gt;What I didn't understand, and wasn't clear to me from the Spring docs, was how to pass the annotation to the advice.&lt;br /&gt;&lt;br /&gt;(Note: I'm using schema-based aop configuration)&lt;br /&gt;&lt;br /&gt;Normally, if you are just trying to match methods that are annotated in a pointcut expression, you would make a pointcut definition with &lt;span style="font-family:courier new;"&gt;"@annotation(com.xyz.AnnotationName)"&lt;/span&gt;.  But what if you want to have access to the annotation in the advice?  Your advice looks like this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;public Object aroundCacheable(ProceedingJoinPoint pjp, Cacheable cachable) throws Throwable&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;int timeout = cacheable.seconds();&lt;br /&gt;&amp;nbsp;&amp;nbsp;...&lt;br /&gt;}&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Your pointcut expression has to specify what to pass as the &lt;span style="font-family:courier new;"&gt;cacheable&lt;/span&gt; parameter in the advice.  So you have to modify the pointcut definition and add &lt;span style="font-family:courier new;"&gt;arg-names&lt;/span&gt; like this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;&amp;lt;aop:around pointcut="@annotation(cacheable)" method="aroundCacheable" arg-names="cacheable"/&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;My understanding is that this tell Spring to look for an argument named &lt;span style="font-family:courier new;"&gt;cacheable&lt;/span&gt; in the advice method, and figure out the type from that.  This seems a little strange because the pointcut definition is dependent on the advice.  It seems like a pointcut should be self-contained, and not depend on how it is used.  But maybe I'm missing something.  I'll have to look into it more later, but for now, I'm just glad I got it working.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3619783675451947013-4622286349979199296?l=nathansnoggin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nathansnoggin.blogspot.com/feeds/4622286349979199296/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nathansnoggin.blogspot.com/2008/10/spring-aop-and-annotation-pointcuts.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/4622286349979199296'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/4622286349979199296'/><link rel='alternate' type='text/html' href='http://nathansnoggin.blogspot.com/2008/10/spring-aop-and-annotation-pointcuts.html' title='Spring AOP and @annotation pointcuts'/><author><name>nathan</name><uri>http://www.blogger.com/profile/03220491542181069595</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='33' height='21' src='http://bp1.blogger.com/_t6Rtr5x_xKE/SCRKaCKTdeI/AAAAAAAAAIc/jMngfcb9Vlo/S220/sarthe.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3619783675451947013.post-1180761861364934748</id><published>2008-07-31T12:54:00.000-07:00</published><updated>2008-07-31T14:36:11.788-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='replication'/><category scheme='http://www.blogger.com/atom/ns#' term='triggers'/><title type='text'>MySQL: triggers + replication = frustration</title><content type='html'>For the most part, I've been impressed with MySQL, but every once in a while I hit a problem that really surprises me.  It seems like MySQL has this mentality that if there is a bug that is hard to fix, just document it, and then it's a feature and not a bug.  Nice.&lt;br /&gt;&lt;br /&gt;MySQL claims to have most of the power features of a robust RDBMS like triggers, foreign keys (although I consider that the most basic of features), stored procedures, etc.  But it sure is frustrating to find out that most are incomplete.&lt;br /&gt;&lt;br /&gt;Sure MySQL has foreign keys, and cascade deletes, but don't expect cascade deletes to fire triggers.  That's a documented feature (bug).&lt;br /&gt;&lt;br /&gt;Sure MySQL has triggers, but don't try using them if you are also using replication.  We recently got bit by a feature (bug) where stored procedures or triggers that insert multiple records in tables with auto-increment don't work with replication.  The auto-increment values on the replica will get off, and replication will break.  &lt;br /&gt;&lt;br /&gt;The problem is that before each insert statement in the binlog, there is a statement to set the auto-increment value.  This is important to make sure that the values will always be the same between master and replica.  On the master you may have two transactions that run in parallel, and use interleaved auto-increment values:&lt;br /&gt;&lt;br /&gt;tx1:  insert into table1 ... (uses auto-increment value 1)&lt;br /&gt;tx2:  insert into table1 ... (uses auto-increment value 2)&lt;br /&gt;tx1:  insert into table1 ... (uses auto-increment value 3)&lt;br /&gt;tx1:  commit;&lt;br /&gt;tx2:  commit;&lt;br /&gt;&lt;br /&gt;In the binlogs, the transactions are serialized, so the auto-increment value has to be explicitly set:&lt;br /&gt;&lt;br /&gt;tx1:&lt;br /&gt;set auto-increment to 1;&lt;br /&gt;insert into table1...&lt;br /&gt;set auto-increment to 3;&lt;br /&gt;insert into table1...&lt;br /&gt;&lt;br /&gt;tx2:&lt;br /&gt;set auto-increment to 2;&lt;br /&gt;insert into table1...&lt;br /&gt;&lt;br /&gt;But consider the case where the insert is on a table with a trigger that inserts a record into a second table (like an auditing table).  The binlog only sets the auto-increment value for the actual insert statement.  The trigger's insert will use whatever the replica's auto-increment value for the second table is set to.  Since simultaneous transactions on the master are serialized in the binlogs, inserts on the second table may happen out of order, and auto-increment values will no longer match the master.&lt;br /&gt;&lt;br /&gt;It seems that MySQL is not planning on fixing this bug in 5.0.  My understanding is that in 5.1 the solution will be to use row-based replication.  Statement based replication will still be broken, from what I can tell.  I did see one bug report where someone said something about mixed mode replication, and switching to row-based temporarily for any statement or stored procedure call that will insert multiple records.  Sounds like a can of worms to me.&lt;br /&gt;&lt;br /&gt;Regardless of what happens in 5.1, there will be no solution for this in 5.0.  And from my experience, I'll be nervous to move to 5.1 anytime soon.  So it looks like I'll be stuck with this "feature" for a while to come.  Nice.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3619783675451947013-1180761861364934748?l=nathansnoggin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nathansnoggin.blogspot.com/feeds/1180761861364934748/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nathansnoggin.blogspot.com/2008/07/mysql-triggers-replication-frustration.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/1180761861364934748'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/1180761861364934748'/><link rel='alternate' type='text/html' href='http://nathansnoggin.blogspot.com/2008/07/mysql-triggers-replication-frustration.html' title='MySQL: triggers + replication = frustration'/><author><name>nathan</name><uri>http://www.blogger.com/profile/03220491542181069595</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='33' height='21' src='http://bp1.blogger.com/_t6Rtr5x_xKE/SCRKaCKTdeI/AAAAAAAAAIc/jMngfcb9Vlo/S220/sarthe.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3619783675451947013.post-1721951099754188976</id><published>2008-04-02T05:54:00.000-07:00</published><updated>2008-04-02T06:47:05.275-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='easymock'/><category scheme='http://www.blogger.com/atom/ns#' term='spring'/><category scheme='http://www.blogger.com/atom/ns#' term='junit'/><title type='text'>stubbing out java.util.Random with EasyMock and Spring</title><content type='html'>Assuming you are familiar with Spring and EasyMock, here is a little tutorial on how to stub out randomness for your JUnit tests.&lt;br /&gt;&lt;br /&gt;First I probably need to give a little context on how I have my test framework set up with Spring.  I have two spring config files, one of them is just for testing and injects stubs/mock objects where appropriate.  I have a base test class that extends &lt;span style="font-family:courier new;"&gt;AbstractDependencyInjectionSpringContextTests&lt;/span&gt;.  It has members (and setters) for all of my classes that I want to test, and all of the stubs that get injected.&lt;br /&gt;&lt;br /&gt;I make all of my classes to test &lt;span style="font-family:courier new;"&gt;scope="prototype"&lt;/span&gt; and my stubs are all singletons, so that I can easy get access to the same stub that will be injected into my class under test.  Before running a test, I set up the stubs appropriately, and get them ready to replay.  After the test, I call EasyMock.reset() to reset them for the  next test (since they are singleton).&lt;br /&gt;&lt;br /&gt;Ok, on to the example.  Let's say you have a method that uses java.util.Random:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;public class MyRandomClass&lt;br /&gt;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;private void Random rand;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;public int doSomethingRandom()&lt;br /&gt;&amp;nbsp;&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return rand.nextInt();&lt;br /&gt; &amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;public void setRand(Random rand)&lt;br /&gt;&amp;nbsp;&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;  this.rand = rand;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;In your real spring config file you would have something like this:&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;&amp;lt;bean id="myRandomClass" class="MyRandomClass"&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;    &amp;lt;property name="rand" ref="rand"/&amp;gt;&lt;br /&gt;&amp;lt;/bean&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;bean id="rand" scope="prototype" class="java.util.Random"&amp;gt;&amp;lt;/bean&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;And in your test spring config you would set up your 'rand' bean like this:&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;&amp;lt;bean id="rand" class="org.easymock.classextension.EasyMock" factory-method="createMock"&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;    &amp;lt;constructor-arg value="java.util.Random"/&amp;gt;&lt;br /&gt;&amp;lt;/bean&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Notice that I am using the &lt;a href="http://www.easymock.org/Downloads.html"&gt;EasyMock Class Extension&lt;/a&gt; because Random does not have an interface (Sun should add one, in my opinion).  The regular EasyMock library can only mock interfaces, and the class extension adds the ability to mock classes themselves.&lt;br /&gt;&lt;br /&gt;Now when setting up your test case, you can specify what 'random' values you want, like this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;  EasyMock.expect(rand.nextInt()).andReturn(0);&lt;br /&gt;  EasyMock.expect(rand.nextInt()).andReturn(1);&lt;br /&gt;  ...&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;And then you know what to expect:&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;assertEquals("returned wrong result", 0, myRandomClass.doSomethingRandom());&lt;br /&gt;assertEquals("returned wrong result", 1, myRandomClass.doSomethingRandom());&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;That's it!  (at least for this simple toy example)&lt;br /&gt;&lt;br /&gt;One thing I noticed is that when using the EasyMock Class Extension, you have to remember to use the &lt;span style="font-family:courier new;"&gt;org.easymock.classextension.EasyMock createMock(), reset(), replay(), verify()&lt;/span&gt; etc. methods on any actual classes that you stub/mock.  But I was able to use  &lt;span style="font-family:courier new;"&gt;org.easymock.EasyMock.expect()&lt;/span&gt;, etc. methods when setting up the stubs.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3619783675451947013-1721951099754188976?l=nathansnoggin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nathansnoggin.blogspot.com/feeds/1721951099754188976/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nathansnoggin.blogspot.com/2008/04/stubbing-out-javautilrandom-with.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/1721951099754188976'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/1721951099754188976'/><link rel='alternate' type='text/html' href='http://nathansnoggin.blogspot.com/2008/04/stubbing-out-javautilrandom-with.html' title='stubbing out java.util.Random with EasyMock and Spring'/><author><name>nathan</name><uri>http://www.blogger.com/profile/03220491542181069595</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='33' height='21' src='http://bp1.blogger.com/_t6Rtr5x_xKE/SCRKaCKTdeI/AAAAAAAAAIc/jMngfcb9Vlo/S220/sarthe.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3619783675451947013.post-7408164931424040656</id><published>2008-04-01T11:57:00.000-07:00</published><updated>2008-04-01T12:58:38.944-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='spring'/><category scheme='http://www.blogger.com/atom/ns#' term='web service'/><category scheme='http://www.blogger.com/atom/ns#' term='axis'/><title type='text'>using Spring in an Axis Web Service Impl</title><content type='html'>This is a re-post from another blog of mine:&lt;br /&gt;&lt;br /&gt;I have an existing code base using Axis for web services, and am working on integrating Spring (2.0) into the system, for transaction management.&lt;br /&gt;&lt;br /&gt;This is not straightforward, because Spring likes to be the one that creates your objects, so that it can inject dependencies.  But with web services, Axis creates the service implementation class, not the Spring container.  A 'hack' is necessary as an alternative to the standard Spring injection.  And while it is not straightforward, it's not difficult, either.  It just seemed that way to me because all the examples I found on the internet were confusing.&lt;br /&gt;&lt;br /&gt;Here's the short version of how to get things working:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;If you are generating a Skeleton class with wsdl2java (-skeletonDeploy), stop doing it.&lt;/li&gt;&lt;li&gt;Create a wrapper class for your service impl.  For me this meant renaming my &lt;span style="font-family:courier new;"&gt;MyServiceSoapBindingImpl&lt;/span&gt; class to &lt;span style="font-family:courier new;"&gt;MyServiceImpl&lt;/span&gt;, and then regenerating &lt;span style="font-family:courier new;"&gt;MyServiceSoapBindingImpl&lt;/span&gt;.  &lt;span style="font-family:courier new;"&gt;MyServiceSoapBindingImpl&lt;/span&gt; is now the wrapper class, and you have it contain a &lt;span style="font-family:courier new;"&gt;MyServiceImpl&lt;/span&gt; object and delegate all calls to that object.&lt;/li&gt;&lt;li&gt;Change the wrapper class to extend &lt;span style="font-family:courier new;"&gt;ServletEndpointSupport&lt;/span&gt;, and make sure that your wsdd points to that wrapper class.&lt;/li&gt;&lt;li&gt;Override &lt;span style="font-family:courier new;"&gt;ServletEndpointSupport.onInit()&lt;/span&gt;, and get the real impl (&lt;span style="font-family:courier new;"&gt;MyServiceImpl&lt;/span&gt;) as a bean using &lt;span style="font-family:courier new;"&gt;getWebApplicationContext().getBean("myServiceImplBean");&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;Now your impl class can use Spring for dependency injection just like any other class.&lt;br /&gt;&lt;br /&gt;Here's the long version:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;30,000 feet&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;What we want is to have a service interface (&lt;span style="font-family:courier new;"&gt;MyService&lt;/span&gt;), a service impl (&lt;span style="font-family:courier new;"&gt;MyServiceImpl&lt;/span&gt;) with the actual code, and be able to inject dependencies into &lt;span style="font-family:courier new;"&gt;MyServiceImpl&lt;/span&gt; (dao, etc.).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;public interface MyService&lt;br /&gt;{&lt;br /&gt;    public int doSomething();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public class MyServiceImpl implements MyService&lt;br /&gt;{&lt;br /&gt;    private HelperBean myHelperObj;&lt;br /&gt;&lt;br /&gt;    public void setMyHelperObj(HelperBean myHelperObj)&lt;br /&gt;    {&lt;br /&gt;        this.myHelperObj = myHelperObj;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public int doSomething()&lt;br /&gt;    {&lt;br /&gt;        return myHelperObj.doTheSomething();&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;We want to be able to set this up in Spring:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;&amp;lt;bean id="helperObj" class="HelperBean"&amp;gt;&amp;lt;/bean&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;bean id="myService" class="MyServiceImpl"&gt;&lt;br /&gt;    &amp;lt;property name="myHelperObj" ref="helperObj"/&amp;gt;&lt;br /&gt;&amp;lt;/bean&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;And then set that service class up as a web service.  This means that your wsdd would have something like this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;&lt;br /&gt;&amp;lt;service name="MyService" provider="java:RPC"&amp;gt;&amp;lt;/service&amp;gt;&lt;br /&gt;    ...&lt;br /&gt;    &amp;lt;parameter name="className" value="MyServiceImpl"/&amp;gt;&lt;br /&gt;    ...&lt;br /&gt;&amp;lt;/service&amp;gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;The problem is that since &lt;span style="font-family:courier new;"&gt;MyServiceImpl&lt;/span&gt; is your web service class, Axis is in charge of it, not Spring.  Since Spring does not create the class, it can't inject the &lt;span style="font-family:courier new;"&gt;HelperObj&lt;/span&gt;!&lt;br /&gt;&lt;br /&gt;So let's see what the workaround is.  First, I need to explain how I have things set up.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Axis/service class setup&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I generate the wsdl from the service interface using the ant &lt;span style="font-family:courier new;"&gt;java2wsdl&lt;/span&gt; task, and then generate the stub, locator, etc. classes from the wsdl using the ant &lt;span style="font-family:courier new;"&gt;wsdl2java&lt;/span&gt; task.  Because of this, my service impl class is actually named &lt;span style="font-family:courier new;"&gt;MyServiceSoapBindingImpl&lt;/span&gt;, just because that's what the ant task generates.  The classes generated are:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-family:courier new;"&gt;MyServiceSoapBindingStub&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:courier new;"&gt;MyServiceService&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:courier new;"&gt;MyServiceServiceLocator&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family:courier new;"&gt;MyServiceSoapBindingImpl&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;Originally we were also generating a Skeleton class for the service (&lt;span style="font-family:courier new;"&gt;skeletonDeploy="yes"&lt;/span&gt;).  This ended up causing me headaches, and I'm not really sure what the purpose of the Skeleton class is, anyway.  Bottom line: don't do it.&lt;br /&gt;&lt;br /&gt;To get around Spring not being able to inject dependencies into your service impl, it's necessary to make your service impl class (the one that your deploy.wsdd points to) a wrapper around the &lt;span style="font-style: italic;"&gt;real&lt;/span&gt; impl class that you want to inject dependencies into.   The wrapper class extends Spring's  &lt;span style="font-family:courier new;"&gt;ServletEndpointSupport&lt;/span&gt; class, which provides an &lt;span style="font-family:courier new;"&gt;onInit&lt;/span&gt; method that you can override to get access to the spring application context:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;public class MyServiceImpl implements MyService&lt;br /&gt;{&lt;br /&gt;    private HelperBean myHelperObj;&lt;br /&gt;&lt;br /&gt;    public void setMyHelperObj(HelperBean myHelperObj)&lt;br /&gt;    {&lt;br /&gt;        this.myHelperObj = myHelperObj;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public int doSomething()&lt;br /&gt;    {&lt;br /&gt;        return myHelperObj.doTheSomething();&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;public class MyServiceSoapBindingImpl extends ServletEndpointSupport implements MyService&lt;br /&gt;{&lt;br /&gt;    private MyService impl;&lt;br /&gt;&lt;br /&gt;    protected void onInit() throws ServiceException&lt;br /&gt;    {&lt;br /&gt;        impl = (MyService) getWebApplicationContext().getBean("myService");&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public int doSomething()&lt;br /&gt;    {&lt;br /&gt;        return impl.doSomething();&lt;br /&gt;    }&lt;br /&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;So the 'hack' is that you have to use &lt;span style="font-family:courier new;"&gt;ServletEndpointSupport&lt;/span&gt; and access the Spring context directly, but from that point on, your real impl class can behave like any other Spring bean.&lt;br /&gt;&lt;br /&gt;The part that messed me up was the Skeleton class.  Since the wsdd pointed to the Skeleton class instead of the Impl, Spring for some reason didn't call my &lt;span style="font-family:courier new;"&gt;onInit()&lt;/span&gt; callback method.  Hopefully this helps someone else having the same problem.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;web.xml&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Your web.xml doesn't need to have anything special.  Just the normal configation.  I got confused because all the examples I found on the web made it seem that you had to register a &lt;span style="font-family:courier new;"&gt;DispatcherServlet&lt;/span&gt;, which is not necessary just for what we are trying to do here.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3619783675451947013-7408164931424040656?l=nathansnoggin.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://nathansnoggin.blogspot.com/feeds/7408164931424040656/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://nathansnoggin.blogspot.com/2008/04/using-spring-in-axis-web-service-impl.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/7408164931424040656'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3619783675451947013/posts/default/7408164931424040656'/><link rel='alternate' type='text/html' href='http://nathansnoggin.blogspot.com/2008/04/using-spring-in-axis-web-service-impl.html' title='using Spring in an Axis Web Service Impl'/><author><name>nathan</name><uri>http://www.blogger.com/profile/03220491542181069595</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='33' height='21' src='http://bp1.blogger.com/_t6Rtr5x_xKE/SCRKaCKTdeI/AAAAAAAAAIc/jMngfcb9Vlo/S220/sarthe.jpg'/></author><thr:total>0</thr:total></entry></feed>
