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 HoldsUser
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+00id          | 3
bucket      | 3
target_user | 237
pos         | 
create_time | 2017-04-01 20:35:46.26597+00Each 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+00Permission 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   | fQuerying 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>
</srfshPlugins
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,699
 
  