Unveiling Hidden Things in Evergreen

Galen Charlton
2017 Evergreen International Conference

Time...

First substantial commit on 4 February 2005

... and tide

commit 940e152e588a9b1c1b4f18cbbecf46691cb2f58c
Author: phasefx <phasefx@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Date:   Fri Feb 4 22:08:15 2005 +0000

    Initial revision
    
    
    git-svn-id: svn://svn.open-ils.org/ILS/trunk@2 dcc99617-32d9-48b4-a31d-7c20da2025e4
Year Commits
2005 2,562
2006 4,115
2007 1,142
2008-2013 12,553
2014 708
2015 928
2016 897
2017 421

Let's
introduce
a varmint
to this presentation

Everything is better with a friend

Particularly if you're a cat who cannot be trusted with a mouse

If you give a cat a library card...

How does Evergreen manage your friends?

It puts them in buckets, of course

Cats on a Farm by davidmaitland on Flickr CC-BY

CREATE TABLE container.user_bucket (
    id  SERIAL  PRIMARY KEY,
    owner   INT NOT NULL
            REFERENCES actor.usr (id)
                ON DELETE CASCADE
                ON UPDATE CASCADE
                DEFERRABLE
                INITIALLY DEFERRED,
    name    TEXT    NOT NULL,
    btype   TEXT    NOT NULL DEFAULT 'misc' REFERENCES container.user_bucket_type (code) DEFERRABLE INITIALLY DEFERRED,
    description TEXT,
    pub BOOL    NOT NULL DEFAULT FALSE,
    create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
    CONSTRAINT ub_name_once_per_owner UNIQUE (owner,name,btype)
);

CREATE TABLE container.user_bucket_item (
    id      SERIAL  PRIMARY KEY,
    bucket      INT NOT NULL
                REFERENCES container.user_bucket (id)
                    ON DELETE CASCADE
                    ON UPDATE CASCADE
                    DEFERRABLE
                    INITIALLY DEFERRED,
    target_user INT NOT NULL
                REFERENCES actor.usr (id)
                    ON DELETE CASCADE
                    ON UPDATE CASCADE
                    DEFERRABLE
                    INITIALLY DEFERRED,
    pos         INT,
    create_time TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW()
);

User
Buckets

           code           |            label            
--------------------------+-----------------------------
 misc                     | Miscellaneous
 folks                    | Friends
 folks:pub_book_bags.view | List Published Book Lists
 folks:pub_book_bags.add  | Add to Published Book Lists
 folks:circ.view          | View Circulations
 folks:circ.renew         | Renew Circulations
 folks:circ.checkout      | Checkout Items
 folks:hold.view          | View Holds
 folks:hold.cancel        | Cancel Holds

User
Bucket
Types

How does it work?

A bucket of type "folks" stores a user's friends

id          | 3
owner       | 236
name        | folks
btype       | folks
description | 
pub         | f
create_time | 2017-04-01 20:35:14.013986+00
id          | 3
bucket      | 3
target_user | 237
pos         | 
create_time | 2017-04-01 20:35:46.26597+00

Each entry in a bucket is a friend

But friendship counts only if it's mutual!

Handing over the keys

It's not enough to be friendly... specific permissions must be delegated. This is done usingthe other "folks:" bucket types.

id          | 2
owner       | 236
name        | folks:hold.view
btype       | folks:hold.view
description | 
pub         | f
create_time | 2017-04-01 20:28:32.672987+00

Permission delegation does NOT need to be mutual

// here be Freddie, operating srfsh

srfsh# request open-ils.actor open-ils.actor.friends.perms.apply \
       "cd365b8ca54495c900dd0a851ccb8cff" 236, 237, "hold.view"

Received Data: "1"

// and here I am later, checking on his hold request

srfsh# request open-ils.circ open-ils.circ.holds.retrieve \
       "9eb6c9709849ce2a85bb678c64c77449", 236                                                                   

Received Data: [
  { 
    "__c":"ahr",
    "__p":[
      null,
      null,
      null,
      null,
      "f",
      null,
      null,
      null,
      null,
      "T",
      null,
      263,

Delegation at work

Why is this a treasure?

  • The underlying mechanism, an experiment from 2008/2009... still works.
  • Expand the permission delegation mostly entails adding some calls to  OpenILS::Application::Actor::Friends->friend_perm_allowed()
  • There are lots of things we could do with user buckets as well

Why is this buried?

  • srfsh is not a recommended user interface
    • Though a staff-side UI for user buckets is coming soon!
  • Nor do I recommend giving cats direct open-ils.cstore access
    • Though open-ils.pcrud permissions for user buckets are coming soon!
  • Only "folks:hold_view" and "folks:circ_view" do anything at present

Now let's jump to 2010: qstore

It's full of queries

  • qstore, or Query Store, provides an API for running stored, pre-defined queries
  • Queries are stored in the query schema as structures that can be turned into SQL SELECT (and UNION, INTERSECT, and EXCEPT) statements

  • Features
    • bind parameters
    • streaming or atomic returns
    • CASE expressions
    • Can use stored functions

Let's build a query

query.stored_query

query.from_relation

-[ RECORD 1 ]-+-------
id            | 26
type          | SELECT
use_all       | f
use_distinct  | f
from_clause   | 11
where_clause  | 
having_clause | 
limit_count   | 
offset_count  | 
-[ RECORD 1 ]---+------------------------------------
id              | 11
type            | RELATION
table_name      | reporter.materialized_simple_record
class_name      | 
subquery        | 
function_call   | 
table_alias     | rmsr
parent_relation | 
seq_no          | 1
join_type       | 
on_clause       | 
-[ RECORD 1 ]---+---------
id              | 11
type            | RELATION
table_name      | 
class_name      | rmsr
subquery        | 
function_call   | 
table_alias     | 
parent_relation | 
seq_no          | 1
join_type       | 
on_clause       | 

or

Let's build a query

query.expr_xcol

query.select_item

-[ RECORD 1 ]+-------
id           | 61
parenthesize | f
parent_expr  | 
seq_no       | 1
table_alias  | rmsr
column_name  | title
negate       | f
-[ RECORD 2 ]+-------
id           | 62
parenthesize | f
parent_expr  | 
seq_no       | 1
table_alias  | rmsr
column_name  | author
negate       | f
-[ RECORD 1 ]+---
id           | 48
stored_query | 26
seq_no       | 1
expression   | 61
column_alias | 
grouped_by   | f
-[ RECORD 2 ]+---
id           | 50
stored_query | 26
seq_no       | 2
expression   | 62
column_alias | 
grouped_by   | f

Querying the query

srfsh# open open-ils.qstore                                               
Service open-ils.qstore opened
srfsh# request open-ils.qstore open-ils.qstore.prepare 26                 

Received Data: {
  "token":"5_1491150953_5837",
  "bind_variables":{
    
  }
}

------------------------------------
Request Completed Successfully
Request Time in seconds: 0.006293
------------------------------------
srfsh# request open-ils.qstore open-ils.qstore.columns "5_1491150953_5837" 

Received Data: [
  "title",
  "author"
]

Querying the query

srfsh# request open-ils.qstore open-ils.qstore.sql "5_1491150953_5837"    

// turned the "\n" into literal newlines for this slide
Received Data: "SELECT
   \"rmsr\".title,
   \"rmsr\".author 
FROM
   reporter.materialized_simple_record AS \"rmsr\";
"

Running the query

srfsh# request open-ils.qstore open-ils.qstore.execute "5_1491150953_5837"

Received Data: [
  "la canzone italiana del novecento",
  "baldazzi, gianfranco"
]

Received Data: [
  "le concerto",
  "ferchault, guy"
]

Received Data: [
  "piano concertos nos 2, 3 & 4",
  "kabalevsky, dmitry borisovich"
]

Received Data: [
  "huntington student recital, dec 6 1999",
  null
]

qstore methods

  • open-ils.qstore.prepare
  • open-ils.qstore.sql
  • open-ils.qstore.columns
  • open-ils.qstore.param_list
  • open-ils.qstore.bind_param
  • open-ils.qstore.execute[.atomic]
  • open-ils.qstore.messages
  • open-ils.qstore.finish

Why is this a treasure?

  • qstore can do some things json_query cannot
    • UNIONs, INTERSECTs, and EXCEPTs
    • CASE expressions
    • Not tied to the IDL (although it's there when you want it)
  • The batch patron edit feature will start using it.

Why is this buried?

  • Only available documentation was sitting in a tarball in the open-ils-dev archives
    • Until now!  See LP#1678638.
  • No editor... or really, help of any kind, for constructing the stored queries
    • Pg::SQL::Parser or its equivalent could be basis for an SQL-to-stored-query converter.

srfsh.py

srfsh.py in action

opensrf@dev-galen:~$ /home/gmc/OpenSRF/src/python/srfsh.py
Loading module oils.srfsh...OK.
srfsh# idl show class acp
Loading and parsing IDL...OK
------------------------------------------------------------
Item [acp] asset.copy
------------------------------------------------------------
[0]  age_protect [link @crahp]
[1]  alert_message [text]
[2]  barcode [text]
[3]  call_number [link @acn]
[4]  circ_as_type [text]
[5]  circ_lib [org_unit]
[6]  circ_modifier [link @ccm]
[7]  circulate [bool]
[8]  copy_number [text]
[9]  create_date [timestamp]
[10] active_date [timestamp]
[11] creator [link @au]

Another example

srfsh# router service-stats

Received Data: {
    "open-ils.circ":161,
    "open-ils.qstore":10,
    "open-ils.cat":0,
    "open-ils.actor":11,
    "open-ils.trigger":0,
    "open-ils.permacrud":0,
    "open-ils.auth":2,
    "open-ils.auth_proxy":0,
    "open-ils.url_verify":0,
    "open-ils.supercat":26,
    "open-ils.collections":0,
    "open-ils.storage":2,
    "open-ils.pcrud":0,
    "open-ils.vandelay":0,
    "opensrf.dbmath":0,

Configuration

<?xml version="1.0"?>
<!-- This file follows the standard bootstrap config file layout found in opensrf_core.xml -->
<srfsh>
  <router_name>router</router_name>
  <domain>private.localhost</domain>
  <username>opensrf</username>
  <passwd>IAmTheVeryModelOfAModernMajorPassword</passwd>
  <port>5222</port>
  <logfile>/openils/var/log/srfsh.log</logfile>
  <loglevel>4</loglevel>
  <client>true</client>
  <plugins>
    <plugin module="oils.srfsh"/>
  </plugins>
</srfsh

Plugins

def handle_org_setting(srfsh, args):
    ''' Retrieves the requested org setting.

    Arguments:
        org unit id,
        org setting name
    '''

    org_unit = args[0]
    setting = args[1]

    srfsh.handle_request([
        'open-ils.actor', 
        'open-ils.actor.ou_setting.ancestor_default', 
        org_unit, 
        ',"%s"' % setting
    ])

# ...

srfsh.add_command(command = 'org_setting', handler = handle_org_setting)

Buried...

  • Well, it's a bit of Python living in a C/Perl/JavaScript project (hi Bill!)

Treasure

  • Easily extendable
    • Has notion of plugins
    • No recompile steps needed

What buried treasure do you know of?

Thanks

Galen Charlton
Equinox Open Library Initiative
@gmcharlt

Unveiling Hidden Things in Evergreen

By Galen Charlton

Unveiling Hidden Things in Evergreen

  • 2,406