diff --git a/src/docs/draft/dom_datastore.txt b/src/docs/draft/dom_datastore.txt deleted file mode 100644 index 1693de9..0000000 --- a/src/docs/draft/dom_datastore.txt +++ /dev/null @@ -1,71 +0,0 @@ -This document describes what you should put in the DOM (display) and what you shouldn't (data). It is adapted from an e-mail to javascript@lists. - -=The DOM is Not a Datastore. Don't Store Application State in the DOM.= - -I don't know why this is so compelling, but it happens pretty often and everyone who is doing it really needs to stop doing it. What I mean by "application state" is that the DOM elements representing the page you're producing should be a rendered view of some state which you're storing internally in a Javascript object or datastructure. They should NOT themselves be the datastructure. Suppose you have a WidgetCounter, which counts widgets. Here's a reasonable implementation: - - function /* class */ WidgetCounter(display_field) { - this.display = $(display_field); - this.widgetCount = 0; - this._redraw(); - } - - WidgetCounter.prototype.getWidgetCount = function() { - return this.widgetCount; - } - - WidgetCounter.prototype.addWidget = function() { - this.widgetCount++; - this._redraw(); - } - - WidgetCounter.prototype._redraw = function() { - DOM.setContent(this.display, 'Widgets: '+this.widgetCount); - } - -Sometimes, though, we'll get a design that looks like this: - - COUNTEREXAMPLE - function /* class */ HorribleWidgetCounter(display_field) { - this.display = $(display_field); - DOM.setContent(this.display, 'Widgets: 0'); - } - - HorribleWidgetCounter.prototype.getWigetCount = function() { - return this.display.innerHTML.match(/\d+/)[0]; - } - - HorribleWidgetCounter.prototype.addWidget = function() { - DOM.setContent(this.display, 'Widgets: '+(this.getWidgetCount()+1)); - } - -Even ignoring internationalization concerns, I hope this example is so egregiously bad that it speaks for itself. I don't think anyone would actually implement this, but we get many more subtle flavors of it. For example, the photo tagging code limits the number of tags to 30; it does this by counting the number of childNodes: - - COUNTEREXAMPLE - if (tagsDiv.childNodes.length < 30 && ge(tagsID+'_error')) { - $(tagsID+'_error').style.display = 'none'; - } - -This practice is pervasive. A recent bug (circa July 2008) came down to a system storing its state not only in the DOM but in the "className" field. Someone changed some CSS on another page, which necessitated a followup CSS fix to deal with long field names, which cascaded into a minor CSS fix, which broke a portion of the underlying system. If a third-degree cascade of CSS-only changes can break your feature -- not by changing the display, but by changing actual execution paths -- you're doing it wrong. - -Two tangents here: - -First, it's also bad to rely on DOM context, like this (paraphrased but not really exaggerated) line that used to exist somewhere in poke: - - COUNTEREXAMPLE - hide(pokeContainer.parentNode.parentNode.parentNode.parentNode.parentNode); - -"If there are no pokes left, hide the whole poke panel." (Someone removed a couple divs and this started hiding the BODY tag.) - -You should generally acquire references to nodes either by using $() or by using DOM.scry() from some parent container you've used $() on. The advantage of using DOM.scry() over DOM-walking chains is that the nodes can be moved around in a lot of ways without breaking you code. You should generally avoid using and particularly avoid chaining parentNode, childNodes, firstChild, nextSibling, etc. This isn't completely hard-and-fast, but almost all cases of "element.childNodes[3].firstChild" are bad and all of them are fragile. - -Second, it's way way less bad to use non-DOM properties of DOM objects to hold appropriate state. For instance, code like this (related to the earlier bug) is inherently fragile: - - COUNTEREXAMPLE - if (container.childNodes[i].className == 'friends_'+entity) - -It relies on unchanging DOM relationships, it relies on nothing and no one ever touching className, and it constructs a classname programmatically which makes it more difficult to debug when it breaks or find when it you're changing things. A far less bad implementation might use :DOMStorage: - - if (DOMStorage.getData(container.childNodes[i], 'privacyType') == entity) - -This is still kind of nasty and I'd argue that a better design would see the code iterating over objects instead of over DOM nodes, using getters, and calling rendering methods to make changes reflect in the DOM. But, at the least, this is much less fragile and usually a practical alternative which doesn't require all that time-consuming "software engineering" associated with using classes and objects. diff --git a/src/docs/draft/javascript_scope_resolution.txt b/src/docs/draft/javascript_scope_resolution.txt deleted file mode 100644 index 187abb7..0000000 --- a/src/docs/draft/javascript_scope_resolution.txt +++ /dev/null @@ -1,148 +0,0 @@ -This article describes how scope resolution works in Javascript. Note that Firebug creates a fake global scope, so if you try some of these examples in Firebug they may behave differently than if you put them in a .js file. - -=window= - -Javascript's global scope is a variable named window. At global scope, these statements are all equivalent: - - u = 1; - var u = 1; - window.u = 1; - -Everything you define at global scope exists at global scope, so you can iterate over window to get a list of all the functions and classes, plus a whole pile of browser builtins. For instance, to test if a function exists at global scope: - - if (window['function']) { ... } - - -=Scope= - -There are two ways to create a new scope: function and with. Never use with -- it's a cute idea, but some of the browser implementations suck and it's more trouble than it's worth (see [[Articles/Javascript Design Flaws]]). Other statements do not introduce scope in Javascript. - -So there's one way to create a new scope that you are actually allowed to use: creating a function. A function definition creates a new scope: - - function f(param) { - var local; - param = 'param'; - local = 'local'; - global = 'global'; - } - -Local variables and parameter variables have resolution precedence over global variables, so param is resolved to the parameter and local is resolved to the function-local. However, global is not declared anywhere in this scope, so it will resolve to the global global. - -Equivalently, you could write window.global. In general, you can force global resolution with window. - -Two notes: first, don't ever give a local and a parameter the same name. You will be decidedly unhappy about the result. Second, the var keyword applies regardless of where in the function scope it appears, so this block also creates a local called local, even though the line var local; will never be executed. That is, this function will always return undefined regardless of the value of window.local. - - function f() { - return local; - if (false) { - var local; - } - } - -It may be helpful to think of this as: one pass to find all the var keywords, then a second pass to actually execute the code. - -=Advanced Scope= - -Of course, you can define functions inside functions. In this case, identifiers which are not locals or parameters will be resolved at the parent scope, and so on until window is reached. - - function f() { - var l = 35; - function g() { - return l; - } - g(); // 35 - } - -Here, l is resolved at f's scope. Note that g is a ``closure'' -- it encloses a variable from its containing scope. This reference does not dissolve when the scope dissolves: - - function buildBeancounterFunctions() { - var bean_count = 0; - return { - inc : function() { bean_count++; }, - get : function() { return bean_count; } - }; - } - - var b = buildBeancounterFunctions(); - b.inc(); - b.inc(); - b.get(); // 2 - -This works as expected; the reference to the bean_count local is preserved by the closures even though the scope created by buildBeancounterFunctions has dissolved. If you call buildBeancounterFunctions again, you'll get a different set of functions enclosing a different variable. (But don't build classes like this, see [[Articles/Object-Oriented Javascript]] instead.) - -=Really Advanced Scope= - -One caveat is that this goes the other way, too: - - function assignButtonHandlers(buttons) { - for (var ii = 0; ii < buttons.length; ii++) { - buttons[ii].onclick = function() { - alert(ii); - } - } - } - -Suppose you pass in an array of three buttons. Since all of the closures are referencing the same variable, the buttons will not alert 0, 1, and 2, but 3, 3, and 3. - -The solution to this is to reference different variables; to do this, you need to introduce more scope. - - function assignButtonHandlers(buttons) { - for (var ii = 0; ii < buttons.length; ii++) { - buttons[ii].onclick = (function(n) { - return function() { - alert(n); - })(ii); - } - } - } - -This creates and calls a function which takes the current value of the iterator and returns a function which encloses that value. This is difficult to understand. But generally you can just learn how bind() works and use that instead, far more simply: - - function assignButtonHandlers(buttons) { - for (var ii = 0; ii < buttons.length; ii++) { - buttons[ii].onclick = alert.bind(buttons[ii], ii); - } - } - - -=arguments and this= - -When you create a function scope, two magic variables are automatically injected: this and arguments. arguments is an Array-like object with the function's arguments (so you can write functions which take a variable number of arguments, like sprintf()). arguments also has some other properties, like callee, which are occasionally useful. Look it up on the internet if you really care so very much. - - function howManyArguments() { - return arguments.length; - } - - howManyArguments(1, 2, 3, 4, 5); // 5 - -this is a variable that contains the function's calling context. What this means is that if a function is invoked as a property of an object, this will be the object which it is a property of. Basically, what you would expect from other object-oriented languages. - - o.f(); // inside the scope of f(), `this' is `o' - o['f'](); // inside the scope of f(), `this' is `o' - a.b.c.f(); // inside the scope of f(), `this' is `c' - -But! If a function has no calling context, this will be defined but set to window. So, this always exists inside a function scope, but sometimes it is window which is 100% Bad News Bears and almost certainly not what you want. - - f(); // `this' is window! That is terrible! - -This is particularly tricky because it is the immediate calling context that becomes this. - - o.f(); // inside the scope of f(), `this' is `o', but... - var g = o.f; - g(); // ...now it's window, sucker. - -Fortunately, you can inject a specific calling context using Function.call() or Function.apply(). - -=call and apply= - -Function.call() takes one or more arguments; the first is the object to inject as the function's calling context and the rest (if any) are arguments to pass. - - function add(n) { return this + n; } - add.call(3, 5); // 8 - -Function.apply() works the same way, but it takes two arguments: the object to inject as this and an array of arguments to pass. So these are equivalent: - - add.call(3, 5); - add.apply(3, [5]); - -But, there's generally an easier way than call or apply: bind() (which you should probably read next). diff --git a/src/docs/draft/using_exceptions.txt b/src/docs/draft/using_exceptions.txt deleted file mode 100644 index 4bcef94..0000000 --- a/src/docs/draft/using_exceptions.txt +++ /dev/null @@ -1,116 +0,0 @@ -This document explains how to use exceptions to handle error conditions in PHP. You should also read the newer guidelines at: -http://www.intern.facebook.com/intern/wiki/index.php/PHPErrorHandling. - -= Use Exceptions = - -Use Exceptions: Error conditions in our PHP stack should be communicated through exceptions, which provide a simple, robust mechanism for handling errors. - -In some cases, extenuating circumstances (like legacy code which is difficult to untangle or needs to be phased in) may prevent immediate enactment, but exception-based error handling should be strongly preferred wherever reasonable, and particularly in new features. - -= Overview = - -Traditionally error handling, including much of the error handling in our codebase, revolves around returning error codes. Return codes are straightforward, but they need to be manually returned, and then manually passed up the stack. They need to be handled at every level. If they aren't, they silently vanish. - -The behavior of error codes is pathological: they resist doing the right thing. Unless they are handled at every level they will leap headlong into the void, never to be heard from again. Exceptions are a more powerful error handling mechanism than error codes. They have the right default behaviors: they can effect function return, and they make their way up the stack unaided by default. - -As a top-level caller using an API that throws exceptions, you must handle error conditions, because ignoring them means program termination. This relationship between callers (which are in a position to handle errors) and lower level APIs (which are not), which is explored in greater detail below, is both correct and desirable. Only callers can be responsible for error handling, and exceptions ensure they are. - -Using exceptions will make your life easier and your code better, because: - -* exceptions simplify error handling because they can effect function return and unwind the stack without coddling -* exceptions make your code robust by preventing errors from silently vanishing -* exceptions carry more information about error conditions, so you can better react to them -* exceptions make error conditions explicit by providing a single, unambiguous way to communicate errors -* exceptions simplify APIs by allowing you to use return values for returning data only - -However, handling error conditions with exceptions instead of error codes requires you to change your approach somewhat in order to reap the benefits: - -* you should catch exceptions at the highest level that can deal with them, often the top level -* you should use exceptions to indicate exceptional conditions, not to affect control flow -* you should not catch the base class Exception (except in very special circumstances) - -The remainder of this document explains these points in greater detail. - -=Simplified Error Handling= - -Exceptions simplify error handling by reducing the number of checks you need to make against return codes. Generally, exceptions allow you to omit many checks against return values and all the boilerplate code responsible for pushing error codes up the stack. Exceptions are explicitly able to effect function return and make their way up the stack without help. - -=Robustness= - -Exceptions make your code more robust because they prevent error codes from being silently dropped. With traditional error codes, any caller can forget to check them. To counteract this, we've developed the "debug_rlog()-and-return" idiom: - - COUNTEREXAMPLE - $ok = some_function(); - if (!$ok) { - debug_rlog('Something didn't work!'); - // Now I'm covered if my caller is lazy! Also, if I'm the caller, I don't - // have to handle this error because it has already been "handled"! Great! - return false; - } - -This idiom arose as a compromise between two concerns: you can't handle errors properly from deep in the stack, but you need to make sure they get handled. This idiom mitigates both but solves neither: callers feel okay about ignoring errors because they've already been ``handled'' through logging, and it's easy to ignore them because there are no real consequences. - -The right way to resolve this is to throw an exception. - -Throwing sends an exception scurrying toward top level where it can be dispatched properly, and forces it to be handled or the program will terminate. This means no log spew and a guarantee that callers aren't ignoring error conditions. - -A concern sometimes raised about exceptions is that an uncaught exception that escapes the stack causes program termination. But, this is an extremely desirable behavior. Stated another way, exceptions mean that when your program is wrong it stops. Error codes mean that when your program is wrong, it keeps going, it just does the wrong thing. Doing the wrong thing is a much worse behavior. - -Unexpected program termination is not particularly bad. It's obvious, it is less likely to make it through testing in the first place, and even if it does it will show up in the logs and get fixed quickly. Most importantly, its badness is bounded: even in the worst case, it can't do more than bring the site down for a few minutes. This is already a condition we have to deal with even without exceptions, because program termination can be caused in a number of other ways (like calling a function which is not defined). - -Conversely, silent failure is very difficult to detect so it is more likely to make it to production in the first place. It may not show up in the logs once it's live. Most importantly, continuing execution when the program is wrong is unboundedly bad. Data corruption and privacy violations are much, much worse than bringing the site down. They can take weeks to clean up, or be impossible to completely revert. It takes only minutes to revert a bad push that caused program termination via uncaught exception. - -Although the risk of these severe outcomes is small, the risk of taking the site down is also small and these severe outcomes are much worse than the worst possible case of uncaught exceptions. Using exceptions and coding defensively is essentially insurance: you are accepting an increased risk of program termination and other well-defined failures that are easy to deal with because you want to lower the risk of nebulous failures that are difficult or impossible to deal with. This is a highly desirable tradeoff which you should be eager to make. - -We already have a number of libraries that communicate error conditions through exceptions or react gracefully in the presence of exceptions: for examples, see :AsyncResponse, :CodedException, :queryfx() ([[Articles/Using queryfx()]]), :Filesystem, Hypershell ([[Articles/Hypershell Architecture]]), Hyperpush ([[Articles/Hyperpush Architecture]]), Sitevars ([[Articles/Sitevar Architecture]]), and :execx() ([[Articles/System Commands]]). Far from being harbingers of uncaught-exception doom, these systems have resulted in simplified code and increased reliability. - -=Catch At Top Level= - -When you catch exceptions, you should generally catch them at the highest level where you can correctly handle the error. Often, this is at or very near the top level. It is usually wrong to convert an :Exception to a return code: - - COUNTEREXAMPLE - try { - $this->flip(); - } catch (PancakeException $ex) { - return false; - } - -Converting an exception to a return code throws away much of the power of exceptions (e.g., automatic navigation up the stack, guaranteed handling, additional information). However, you may sometimes need to do this as an interim step in the process of converting a legacy API into an exception-oriented API. - -It is almost certainly wrong to convert an Exception into a debug_rlog(): - - COUNTEREXAMPLE - try { - $this->flip(); - } catch (PancakeException $ex) { - debug_rlog('Couldn't flip pancake.'); - } - -This is basically equivalent to: - - COUNTEREXAMPLE - $err = pancake_flip(); - if ($err) { - debug_rlog('Hurf durf I am absolving myself of responsibility.'); - $err = false; // No more error! Magic! - } - -Instead, you should catch and handle exceptions at the top level, where they can be meaningfully acted upon. - - try { - $pancake = new Pancake(); - $pancake->cook(); - - $breakfast = $pancake; // Yum! - } catch (PancakeException $ex) { - $breakfast = $cereal; // Ick. :( - } - - -=Don't Catch "Exception"= - -You should usually avoid catching :Exception unless you are implementing a very general, top-level exception handling mechanism like the one in :AsyncResponse. Instead, catch a specific exception or exception subtree, like :CodedException, :QueryException, :CommandException, or :FilesystemException. - -A corollary to this is that you should avoid throwing :Exception unless you do not expect any caller to handle the exception. Essentially, throwing Exception is guaranteeing program termination (albeit via a graceful stack unwind and sensible top-level behavior rather than abrupt exit). The major use for this is checking invariants to detect that an API is being misused so you can communicate to the caller that they have strictly and unambiguously abused your interface. - -Because PHP has no finally clause, it is acceptable to catch :Exception if you are cleaning up resources and then re-throwing, although most kinds of resources that need cleanup (like database transactions and temporary files) already have exception-aware APIs that will handle this for you. diff --git a/src/docs/draft/using_queryfx.txt b/src/docs/draft/using_queryfx.txt deleted file mode 100644 index 1f85ad2..0000000 --- a/src/docs/draft/using_queryfx.txt +++ /dev/null @@ -1,180 +0,0 @@ -This document describes how to use queryfx(), an extended form of queryf(). - -= What queryfx() Does = - -queryfx() stands for something like "query, formatted + extensions", (or "exceptions", depending on who you ask) and is a method for easily and correctly executing queries against a MySQL database. queryfx() is similar to queryf(), but provides more conversions and better error handling. - -The API is similar to the sprintf() family of functions. - - resource queryfx(managed_connection $conn, string $query_pattern, ...); - -Example usage might look like this. - - $ret = queryfx($conn_w, 'INSERT INTO stuff (name) VALUES (%s)', $name); - -queryfx() will properly escape parameters so they are safe in SQL and protect you from SQL injection holes. - -= queryfx_one() and queryfx_all() = - -Many queries either expect to select exactly one row, or want to select all result rows as a list of dictionaries. In these cases, you may use queryfx_one() or queryfx_all(), respectively. - -queryfx_one() will return either null if zero rows match, or a dictionary if exactly one row matches. If more than one row matches, a QueryCountException will be thrown. - - $pie = queryfx_one($conn_r, 'SELECT * FROM pie WHERE id = %d', $id); - if ($pie) { - echo "The pie's flavor is {$pie['flavor']}."; - } else { - echo 'No such pie exists. This is sad times.'; - } - -queryfx_all() will always return a list of dictionaries, although it may be empty. - - $list = queryfx_all( - $conn_r, - 'SELECT * FROM pie WHERE baked > %d', - time() - (60 * 60)); - - if (count($list)) { - echo 'Pies baked in the last hour: '.count($list); - } else { - echo 'No pies were baked in the last hour. This is sad times indeed.'; - } - -These convenience wrappers don't cover every case, but can simplify your code in many cases. - -= Supported Conversions = - -queryfx() supports three simple conversions, %d (integer), %s (string), and %f (float). These work exactly like sprintf(), except that they will be properly escaped for a MySQL context. - - $res = queryfx( - $conn_w, - 'INSERT INTO pie (flavor, size) values (%s, %d)', - $pie_flavor, - $pie_size); - -Note that %s is binary-safe, so it is safe to convert UTF-8 strings or raw byte sequences using %s. - -In addition to these simple conversions, a wide array of additional conversions is supported. - -Nullable conversions work like the simple conversions but handle NULL properly. The nullable conversions are %nd, %ns, and %ns. These conversions behave exactly like the corresponding normal conversions, except when the value they are passed is a strict null. In this case, they will print NULL. - - // INSERT INTO t (u, v) VALUES (3, NULL) - queryfx($conn_w, 'INSERT INTO t (u, v) VALUES (%nd, %nd)', 3, null); - -Nullable test conversions work like the simple conversions but handle equivalence with NULL properly by printing either an = or an IS NULL clause. The nullable test conversions are %=d, %=s, and %=f. - - // SELECT * FROM t WHERE u = 3 AND v IS NULL - queryfx($conn_r, 'SELECT * FROM t WHERE u %=d AND v %=d, 3, null); - -List conversions accept a list of values and produce a comma-separated list, appropriate for insertion into an IN clause. The list conversions are %Ld, %Ls, and %Lf. Note: these conversions treat their arguments as nullable, so null will be converted to NULL in the query, not 0 or empty string. - - // SELECT * FROM t WHERE u IN ('a', 'b', 'c') - queryfx($conn_r, 'SELECT * FROM t WHERE u IN (%Ls)', array('a', 'b', 'c')); - -Identifier conversions escape SQL identifiers like table or column names. The identifier conversions are %T (table), %C (column) and %LC (list of columns). - - // SELECT `select` FROM `from` WHERE `where` = 4 - queryfx( - $conn_r, - 'SELECT %C FROM %T WHERE %C = %d', - 'select', 'from', 'where', 4); - -Dictionary conversions escape a dictionary of key-value pairs into column-value pairs. The dictionary conversions are %U (update clause), %LA (list of predicates joined by AND), and %LO (list of predicates joined by OR). %LA and %LO also support array values for generating "IN" clauses. - - // UPDATE t SET a = 1, b = 2, c = 3 WHERE u = 5 AND d = 6 AND e IN (1, 2, 3) - queryfx( - $conn_w, - 'UPDATE t SET %U WHERE %LA', - array('a' => 1, 'b' => 2, 'c' => 3), - array('u' => 5, 'd' => 6, 'e' => array(1, 2, 3))); - -Like conversions escape a string for a LIKE (or NOT LIKE) clause. The like conversions are %~ (substring match), %> (prefix match), and %< (suffix match). - - // SELECT * FROM t WHERE u LIKE '%example%' OR v LIKE 'prefix%' - queryfx( - $conn_w, - 'SELECT * FROM t WHERE u LIKE %~ OR v LIKE %>', - 'example', - 'prefix'); - -Miscellaneous conversions escape other random junk. The miscellaneous conversions are %K (comment) and %Q (raw subquery). You must be extremely careful with %Q -- unlike other conversions, it does no escaping. If you do not use it properly, you will open a SQL injection hole. - - // UPDATE /* hey guys what is up */ t SET u = "v" - queryfx( - $conn_w, - 'UPDATE %K t SET %Q', - 'hey guys what is up', 'u = "v"'); - -Be careful with %Q because it's extremely dangerous. It should be rarely (if ever) used. Often, vqueryfx() is a better approach. - -= Handling Exceptions = - -queryfx() throws exceptions on failure, which means that you need to catch and handle them. All exceptions descend from QueryException. Generally, you should catch QueryExceptions at or near the top level -- that is, catching exceptions high in the callstack is often better than catching them low in the callstack. - - try { - $pies = pies_get_for_user($uid); - } catch (QueryException $ex) { - $errmsg = 'Pies are not available at this time, try again later.'; - } - -You should not catch exceptions in your database/generator function. You can't do anything useful with them here. - - COUNTEREXAMPLE - try { - $ret = queryfx($conn_r, 'SELECT * FROM pie WHERE owner = %d', $uid); - } catch (QueryException $ex) { - return null; - } - -This means that writing generators is much easier: - - function pies_for_users_dbget($user_ids) { - $rows = queryfx_all( - pies_get_conn('r'), - 'SELECT * FROM pies WHERE owner IN (%Ld)', - $user_ids); - return array_group($rows, 'owner') + array_fill_keys($user_ids, array()); - } - -This is a complete, correct database/generator function under queryfx(). Notably, you do not need to test either the connection or the return for `null', because exceptions will be thrown in either case. - -Note that the cache_get_scb() layer will properly catch and handle exceptions thrown by queryfx(). - -There are several kinds of :QueryException: - - QueryException - Abstract base class for all query exceptions. - - QueryParameterException (extends QueryException) - A parameter to the query was wrong: for instance, an empty list was passed - to a %Ld conversion, or the connection was `null'. - - QueryErrorException (extends QueryException) - The database returned with an error code not specifically recognized as - recoverable. - - QueryCountException (extends QueryException) - You issued a queryfx_one() call that returned more than one row. - -There are also several exceptions that are considered recoverable: - - RecoverableQueryException (extends QueryException) - Abstract base class for all "recoverable" exceptions; these are nonpermanent - failures. - - QueryDeadlockException (extends RecoverableQueryException) - The database returned, reporting a deadlock. The correct response to a - deadlock condition is often to retry the query. - - QueryConnectionException (extends RecoverableQueryException) - The MySQL connection dropped. This shouldn't happen with ManagedConnections, - but you may run into it if you aren't using ManagedConnections. - - QueryDuplicateKeyException (extends RecoverableQueryException) - You issued an insert or update statement which would have caused a duplicate - key (on the primary key or some unique key) collision. Attempting the insert - and catching this exception is often the correct way to ensure uniqueness. - -In most cases, it is sufficient to catch QueryException and consider it an unrecoverable error at the top level. However, you may find the fine-grained exceptions useful when building abstractions, debugging, or under unusual use cases. - -One caveat is that memcache_dispatch() is exception-aware but can not currently expose exceptions at the top level. Instead, it will convert QueryExceptions into an implicity null return value from your database/generator function. This may be fixed in the future but requires improving some abstractions.