Posted to couchdb-users because this might be of general interest.

On 21/12/2008, at 11:28 AM, Tim Parkin wrote:

Hi Anthony,

in short..  I'm interested in your emails about CouchDB relations and
_externals/update notifiers.. in longer ..

I'm following CouchDB at the moment and an app I'm building works with
'related' data. My methodology is basically a form of view cacheing..

i.e. I have a photogrpahic holiday app..

I have a db where I have a few different documents, some marked as
'tours', some marked as 'leaders'.

When I get a tour out, I'd like some information abou the tour leader.

I've created a view which gives me that information (e.g. Gets first
name and last name)

I've then tied in the update process so thatwhen the leaders first name
or last name changes, I cache this view on the tour.

This allows me to get a bunch of data I need for tours in one go without
getting the tour leader out (OK this is a fairly trivial example but
some tours have three assistants and other 'remote' attributes).

I'd really like to have this trigger process happen on the server but
haven't worked out how to do it.. I've read a lot of your posts and you
seem to have approached problem(s) like this and I'd be interested in
your recommmendation.

At the moment I'm looking at triggering an _external from an update
notification (rather like your post
http://markmail.org/message/wikw4vggx4uew5kz?q=couchdb+_external&page=1&refer=uwkon7i337iq6yxf)

I don't recommend having the notification trigger anything but setting a dirty-flag. I've tried to get a response from the gatekeepers about the intended synchronization contract between update notifications and _external request, to no avail. IMO without that being synchronous, it's not that useful.

I do it all in the _external, which is good because it then has the same lazy-update characteristics as a view. A test handler follows. This is in ruby just because I'm doing some ruby at the moment. I use SQLite as the index database because it's in-process and really easy.

The result I return from the _external call is not structured like a view result in this code (it's just test code)

The 'src' field in the 'links' table happens to be the doc_id, so I can remove entries when I notice a deletion. Without that, I would have to include the doc_id separately in the table so that I could handle the deletion of documents. I cheat for this system and use a domain-value key (the email address of the user). I do this because I use a single-writer peer to get a cheap uniqueness constraint. For my app, read-only replication is sufficient USP.

I'm not accepting any parameters in my _external request - I return the entire db (it's just test code).

If you look at Paul Davis's recent post about _external you can see that the query parameters to the _external request are supplied in the per-request json, so dealing with that is trivial.

This code was written to use my own _external erlang code - I think the db_name and update_seq fields are nested in some other structure in the newly-official _external handler, and you'll need a HEAD version of couchdb to make this work in any case, because you need the update_seq.

Anyway, here is some code

---------------------------------------------------------------------

require 'rubygems'
require 'json'
require 'couchrest'
require 'cgi'
require 'sqlite3'

# Need a command line parameter in the .ini rather than hardcoding this
db = SQLite3::Database.new("/usr/local/var/lib/couchdb/bims-users- index.db")

sql = <<SQL
create table if not exists seqnums (db TEXT PRIMARY KEY, seq_num INTEGER); create table if not exists links (db TEXT, src TEXT, dest TEXT, PRIMARY KEY (db, src, dest));
SQL

db.execute_batch(sql)

seq_nos = {}
db.execute("SELECT db, seq_num FROM seqnums") do | row |
  seq_nos[row[0]] = row[1].to_i
end

while line = gets

  data = JSON.parse(line)
  db_name = data["db_name"]
  seq = data["update_seq"].to_i
  current_seq = seq_nos[db_name] || 0

  if current_seq < seq

    STDERR.puts 'Start index update ' + db_name

    current_seq.step(seq, 500) do | offset |

result = CouchRest::get('http://localhost:5984/' + db_name + '/ _all_docs_by_seq?include_docs=true&startkey=' + offset.to_s + "&endkey=" + (offset + 500).to_s)

      result["rows"].each |row|
        doc_id = row["id"]
        doc = row["doc"]
        STDERR.puts 'Process ' + doc_id
        if obj["value"]["deleted"]
db.execute("delete from links where db = ? and src = ?", db_name, doc_id);
        elsif doc["Type"] == "user"
          doc["Roles"] && doc["Roles"].each do |r|
db.execute("replace into links values (?, ?, ?)", db_name, doc_id, r);
          end
        elsif doc["Type"] == "role"
          doc["Permissions"] && doc["Permissions"].each |p|
db.execute("replace into links values (?, ?, ?)", db_name, doc_id, p);
          end
        end
      end
    end

    seq_nos[db_name] = seq
    db.execute("replace into seqnums values (?, ?)", db_name, seq);
STDERR.puts 'Update index of database ' + db_name + ' to seq# ' + seq.to_s

  end

  rows = db.execute(" SELECT DISTINCT t1.src, t2.dest
               FROM links as t1, links as t2
WHERE t1.dest = t2.src AND t1.db = ? AND t2.db = ?", db_name, db_name)

  result = { "code" => 200, "json" => rows }

  STDOUT.puts result.to_json
  STDOUT.flush

end

---------------------------------------------------------------------

Antony Blakey
-------------
CTO, Linkuistics Pty Ltd
Ph: 0438 840 787

It is no measure of health to be well adjusted to a profoundly sick society.
  -- Jiddu Krishnamurti


Reply via email to