- From: Scott Hess <shess@google.com>
- Date: Tue, 16 Oct 2007 07:05:32 -0700
On 10/15/07, Ian Hickson <ian at hixie.ch> wrote: > On Mon, 15 Oct 2007, Scott Hess wrote: > > Whoa! I just realized that there's another group, constraint failures. > > These are statements which will sometimes succeed, sometimes fail. As > > currently spec'ed, it looks like a constraint failure will cause > > rollback. This is probably not appropriate, constraint failures are > > generally expected to be benign and detectable. Arguing against myself, > > I've seen plenty of code which just treats any errors from something > > which allows for a constraint failure as a constraint failure, so maybe > > this also can be lumped under a single big error code. > > Could you elaborate on this? What would code running into this situation > look like? How should we expose it? A common case is wanting to update a row, if present, or insert it, if not. You can handle this in three ways. You can use a transaction to keep things consistent: BEGIN; SELECT COUNT(*) FROM t WHERE id = ?; -- if == 0 INSERT INTO t VALUES (?, ...); -- if == 1 UPDATE t SET c = ?, ... WHERE id = ?; END; [Note that the above gets you the "read lock upgraded to write lock" case.] This style is generally avoided, because in a server environment, you have four round trips from when the transaction is opened to when it's closed, plus whatever contention for CPU is present at both ends, so it is not great for concurrency. Instead, you can just try the insert and rely on a unique or primary key to cause a constraint violation: INSERT INTO t VALUES (?, ...); -- if constraint violation on id UPDATE t SET c = ?, ... WHERE id = ?; That variant is best if you expect to usually succeed, and sometimes fall through to the update (for instance when inserting a new user record). Otherwise, you could do: UPDATE t SET c = ?, ... WHERE id = ?; -- if no rows affected INSERT INTO t VALUES (?, ...); That might be more appropriate for updating a user record where the user is known to exist but the record is not known to exist (say a record of the last time the user performed a particular operation). The first and third cases should work fine within the spec. --- Another example would be a table like: CREATE TABLE UniqueName ( id INTEGER PRIMARY KEY, name TEXT UNIQUE ); You might run: INSERT INTO UniqueName (id, name) VALUES (null, ?); If it succeeds, the id is in insertId. If there's a constraint failure, the programmer knows that it's because the value provided for name was already take. You can of course rewrite this like: BEGIN; SELECT count(*) FROM UniqueName WHERE name = ?; -- If != 0, fail. INSERT INTO UniqueName (id, name) VALUES (null, ?); END; Using the INSERT directly is the standard idiom for this type of thing, though. --- Unfortunately, offhand I'm not coming up with any cases which aren't fundamentally server-side. Part of the justification for using SQL in the spec was so that developers could use similar/identical code server-side and client-side, but I'll admit that the kinds of cases above are probably no more or less annoying to work around than the differences between any chosen server-side database and SQLite would be. -scott
Received on Tuesday, 16 October 2007 07:05:32 UTC