The iterator pattern has another caveat when applied to sqlite:

  foreach (row in statement) {
      if (isMatch(row)) {
          return true
      }
  }
  return false

If the iterator isn't exhausted, how do you know when to dispose the
sqlite3_stmt? There are other ways to manage the statement's lifetime so
this isn't a deal breaker, just something to keep in mind.


I know that our code base uses step/UPDATE/step/UPDATE/... in a couple of
places, without problems. But I guess that is just luck; as you note the
documentation clearly says the behaviour is undefined. To repeatedly get
the _same_ row over and over seems incredibly unfortunate though!

-Rowan


On 31 January 2017 at 11:29, Jens Alfke <j...@mooseyard.com> wrote:

> I’ve just run headlong in to the issues described in "No Isolation Between
> Operations On The Same Database Connection”. Specifically, I’ve discovered
> (after some debugging) that if I iterate over the the rows in a table using
> sqlite3_step, and update each row after it’s returned, Bad Stuff happens.
> Specifically, my query is just getting the first row over and over and over
> again, and the iteration runs forever. :(
>
> I had been under the impression that, since I’m using the WAL, queries
> operate on a snapshot of the database as of the time they begin, and are
> unaffected by subsequent changes. I got this from reading about "snapshot
> isolation” in a previous section of that document. (Also, another key/value
> database engine I’ve used recently _does_ behave this way, so it’s what I
> was expecting.) I now see that the “read transaction” described in that
> section has to be occurring in a different connection than the write
> transaction. (Right?)
>
> I’m unsure what to do now. I am working on a library whose API exposes
> iterator objects that run queries; the iterator’s “next()” method
> internally calls sqlite3_step. Thus the interleaving of the query and
> updating the database is not under my control; it’s up to the developer
> using our library, and I do _not_ want to expose inconvenient undefined
> behavior like this, or tell developers that “you can’t modify the database
> while you’re iterating it”.
>
> I can’t be the first person to run into this. Is there a best practice for
> enabling concurrent iteration and mutation? I can think of two solutions:
>
> A. Batch up all of the query results in memory at the start of the
> iteration, and have the iterator just read them out of the in-memory list.
>         I’d like to avoid this because of the obvious memory overhead and
> latency imposed on large queries. Version 1 of our library worked this way,
> which is why I probably hadn’t noticed the problem until now.
>
> B. Create a separate SQLite connection for the query; then it’ll be
> isolated from any changes being made in the main connection.
>         This seems elegant, but it will of course use more memory for the
> extra connection (with its own cache.) Moreover, it seems like I’ll need to
> open an indefinite number of extra connections: if the caller starts a
> query, makes some changes, and then starts another query (before reading
> the final row of the first query), I need to open another connection for
> the second query because it has to see the changes, which aren’t yet
> visible in the first query's connection … right?
>
> —Jens
>
> [1]: https://www.sqlite.org/isolation.html
> _______________________________________________
> sqlite-users mailing list
> sqlite-users@mailinglists.sqlite.org
> http://mailinglists.sqlite.org/cgi-bin/mailman/listinfo/sqlite-users
>
_______________________________________________
sqlite-users mailing list
sqlite-users@mailinglists.sqlite.org
http://mailinglists.sqlite.org/cgi-bin/mailman/listinfo/sqlite-users

Reply via email to