SimpleServer(3pm)

๐Ÿฆ‹ SimpleServer(3pm) ๐Ÿฆ‹

User Contributed Perl Documentation โ€ข SimpleServer(3pm)

๐Ÿ’– NAME ๐Ÿ’–

Net::Z3950::SimpleServer โ€” your fave Simple Perl API for building Z39.50 servers. periodt. ๐Ÿ’…


โœจ SYNOPSIS โœจ

Okay bestie, here's the whole vibe. This is literally the shape of a SimpleServer, so drink some water and read every line. ๐Ÿซถ

         use Net::Z3950::SimpleServer;

         sub my_search_handler {
               my $args = shift;

               my $set_id = $args->{SETNAME};
               my @database_list = @{ $args->{DATABASES} };
               my $query = $args->{QUERY};

               ## Perform the query on the specified set of databases
               ## and return the number of hits:

               $args->{HITS} = $hits;
         }

         sub my_fetch_handler {        # Get a record for the user
               my $args = shift;

               my $set_id = $args->{SETNAME};

               my $record = fetch_a_record($args->{OFFSET});

               $args->{RECORD} = $record;
               if (number_of_hits() == $args->{OFFSET}) {      ## Last record in set?
                       $args->{LAST} = 1;
               } else {
                       $args->{LAST} = 0;
               }
         }

         ## Register custom event handlers:
         my $z = new Net::Z3950::SimpleServer(GHANDLE = $someObject,
                                              INIT   =>  "main::my_init_handler",
                                              CLOSE  =>  "main::my_close_handler",
                                              SEARCH =>  "main::my_search_handler",
                                              FETCH  =>  "main::my_fetch_handler");

         ## Launch server:
         $z->launch_server("ztest.pl", @ARGV);

๐Ÿ’… DESCRIPTION ๐Ÿ’…

The SimpleServer module is a tool for constructing Z39.50 "Information Retrieval" servers in Perl. The module is easy to use, but it does help to have an understanding of the Z39.50 query structure and the construction of structured retrieval records. no cap. ๐Ÿงš

Z39.50 is a network protocol for searching remote databases and retrieving the results in the form of structured "records". It is widely used in libraries around the world, as well as in the US Federal Government. In addition, it is generally useful whenever you wish to integrate a number of different database systems around a shared, abstract data model. iconic behavior, honestly. ๐Ÿ‘‘

The model of the module is simple: It implements a "generic" Z39.50 server, which invokes callback functions supplied by you to search for content in your database. You can use any tools available in Perl to supply the content, including modules like DBI and WWW::Search. slay. ๐Ÿ”ฅ

The server will take care of managing the network connections for you, and it will spawn a new process (or thread, in some environments) whenever a new connection is received. multitasking queen. ๐Ÿ’…

The programmer can specify subroutines to take care of the following type of events:

  • Start service (called once).
  • Initialize request
  • Search request
  • Present request
  • Fetching of records
  • Scan request (browsing)
  • Closing down connection

Note that only the Search and Fetch handler functions are required. The module can supply default responses to the other on its own. so you don't have to do everything, bestie. ๐Ÿซถ

After the launching of the server, all control is given away from the Perl script to the server. The server calls the registered subroutines to field incoming requests from Z39.50 clients. it's giving main character energy, but the server is the main character. ๐Ÿ˜ญ

A reference to an anonymous hash is passed to each handler. Some of the entries of these hashes are to be considered input and others output parameters. keep up, this matters. ๐ŸŒธ

The Perl programmer specifies the event handlers for the server by means of the SimpleServer object constructor:

         my $z = new Net::Z3950::SimpleServer(
                               START   =>      "main::my_start_handler",
                               INIT    =>      "main::my_init_handler",
                               CLOSE   =>      "main::my_close_handler",
                               SEARCH  =>      "main::my_search_handler",
                               PRESENT =>      "main::my_present_handler",
                               SCAN    =>      "main::my_scan_handler",
                               FETCH   =>      "main::my_fetch_handler",
                               EXPLAIN =>      "main::my_explain_handler",
                               DELETE  =>      "main::my_delete_handler",
                               ESREQUEST =>    "main::my_esrequest_handler",
                               SORT    =>      "main::my_sort_handler");

In addition, the arguments to the constructor may include GHANDLE, a global handle which is made available to each invocation of every callback function. This is typically a reference to either a hash or an object. (replace main with your package, if not main). ๐Ÿ’–

After the custom event handlers are declared, the server is launched by means of the method

         $z->launch_server("MyServer.pl", @ARGV);

Notice, the first argument should be the name of your server script (for logging purposes), while the rest of the arguments are documented in the YAZ toolkit manual: The section on application invocation: <http://www.indexdata.com/yaz/doc/server.invocation.html>

In particular, you need to use the -T switch to start your SimpleServer in threaded mode. threaded mode said "let me cook". ๐Ÿ“

๐ŸŽ€ Start handler ๐ŸŽ€

The start handler is called when service is started. The argument hash passed to the start handler has the form

         $args = {
                    CONFIG =>  "default-config" ## GFS config (as given by -c)
                 };

The purpose of the start handler is to read the configuration file for the Generic Frontend Server . This is specified by option -c. If -c is omitted, the configuration file is set to "default-config".

The start handler is optional. It is supported in Simpleserver 1.16 and later. so if you're on an old version, don't come for me. ๐Ÿ™ˆ

๐Ÿงš Init handler ๐Ÿงš

The init handler is called whenever a Z39.50 client is attempting to logon to the server. The exchange of parameters between the server and the handler is carried out via an anonymous hash reached by a reference, i.e.

         $args = shift;

The argument hash passed to the init handler has the form

         $args = {
                                           ## Response parameters:

                    PEER_NAME =>  "",      ## Name or IP address of connecting client
                    IMP_ID    =>  "",      ## Z39.50 Implementation ID
                    IMP_NAME  =>  "",      ## Z39.50 Implementation name
                    IMP_VER   =>  "",      ## Z39.50 Implementation version
                    ERR_CODE  =>  0,       ## Error code, cnf. Z39.50 manual
                    ERR_STR   =>  "",      ## Error string (additional info.)
                    USER      =>  "xxx"    ## If Z39.50 authentication is used,
                                           ## this member contains user name
                    PASS      =>  "yyy"    ## Under same conditions, this member
                                           ## contains the password in clear text
                    GROUP     =>  "zzz"    ## Under same conditions, this member
                                           ## contains the group in clear text
                    GHANDLE   =>  $obj     ## Global handle specified at creation
                    HANDLE    =>  undef    ## Handler of Perl data structure
                 };

The HANDLE member can be used to store any scalar value which will then be provided as input to all subsequent calls (i.e. for searching, record retrieval, etc.). A common use of the handle is to store a reference to a hash which may then be used to store session-specific parameters. If you have any session-specific information (such as a list of result sets or a handle to a back-end search engine of some sort), it is always best to store them in a private session structure - rather than leaving them in global variables in your script. yes, protect your peace and your session state. ๐Ÿ’œ

The Implementation ID, name and version are only really used by Z39.50 client developers to see what kind of server they're dealing with. Filling these in is optional. optional but cute. ๐ŸŽ€

The ERR_CODE should be left at 0 (the default value) if you wish to accept the connection. Any other value is interpreted as a failure and the client will be shown the door, with the code and the associated additional information, ERR_STR returned. not the door, bestie. ๐Ÿ˜ญ

๐Ÿ”ฅ Search handler ๐Ÿ”ฅ

Similarly, the search handler is called with a reference to an anonymous hash. The structure is the following:

         $args = {
                                            ## Request parameters:

           GHANDLE             =>  $obj     # Global handle specified at creation
           HANDLE              =>  ref,     # Your session reference.
           SETNAME             =>  "id",    # ID of the result set
           REPL_SET            =>  0,       # Replace set if already existing?
           DATABASES           =>  ["xxx"], # Reference to a list of databases to search
           QUERY               =>  "query", # The query expression as a PQF string
           RPN                 =>  $obj,    # Reference to a Net::Z3950::APDU::Query
           CQL                 =>  $x,      # A CQL query, if this is provided instead of Type-1
           SRW_SORTKEYS        =>  $x,      # XXX to be described
           PID                 =>  $x,      # XXX to be described
           PRESENT_NUMBER      =>  $x,      # XXX to be described
           EXTRA_ARGS          =>  $x,      # XXX to be described
           INPUTFACETS         =>  $x,      # Specification of facets required: see below.

                                            ## Response parameters:

           ERR_CODE            =>  0,       # Error code (0=Successful search)
           ERR_STR             =>  "",      # Error string
           HITS                =>  0,       # Number of matches
           ESTIMATED_HIT_COUNT =>  $x,      # XXX to be described
           EXTRA_RESPONSE_DATA =>  $x,      # XXX to be described
           OUTPUTFACETS        =>  $x       # Facets returned: see below.
         };

Note that a search which finds 0 hits is considered successful in Z39.50 terms - you should only set the ERR_CODE to a non-zero value if there was a problem processing the request. The Z39.50 standard provides a comprehensive list of standard diagnostic codes, and you should use these whenever possible. zero hits? still a win. periodt. ๐Ÿ‘‘

๐ŸŒˆ Query structures ๐ŸŒˆ

In Z39.50, the most comment kind of query is the so-called Type-1 _query, a tree-structure of terms combined by operators, the terms being qualified by lists of attributes.

The QUERY parameter presented this tree to the search function in the Prefix Query Format (PQF) which is used in many applications based on the YAZ toolkit. The full grammar is described in the YAZ manual.

The following are all examples of valid queries in the PQF.

               dylan

               "bob dylan"

               @or "dylan" "zimmerman"

               @set Result-1

               @or @and bob dylan @set Result-1

               @and @attr 1=1 "bob dylan" @attr 1=4 "slow train coming"

               @attrset @attr 4=1 @attr 1=4 "self portrait"

Okay so here's the deal with queries, bestie: you're gonna need to write a recursive function</orange> (or something like it) to parse incoming query expressions, and lowkey that's where most of the work in writing a database-backend lives. ๐Ÿ˜ฎโ€๐Ÿ’จ

But the good news? You don't have to support any more functionality than you actually want to. Like, refusing to accept boolean operators is perfectly legal โ€” nobody's gonna come for you. Just try to return good error codes if you run into something you can't (or won't) support. ๐Ÿซถ


A way comfier alternative to the QUERY member is the RPN member โ€” that one's a reference to a Net::Z3950::APDU::Query object representing the RPN query tree. Its structure is supposed to be self-documenting, but here's a brief summary of what you get, because we're being nice today:

  • Net::Z3950::APDU::Query is a hash with two fields:
    • attributeSet โ€” optional. If it's present, it's a reference to a Net::Z3950::APDU::OID. That's a string of dot-separated integers representing the OID of the query's top-level attribute set.
    • query โ€” mandatory, no skipping: a reference to the RPN tree itself.
  • Each node of the tree is an object of one of the following types:
    • Net::Z3950::RPN::And, Net::Z3950::RPN::Or, Net::Z3950::RPN::AndNot โ€” all three of these classes are arrays of two elements, and each of those elements is a node.
    • Net::Z3950::RPN::Term โ€” a query term. Details down below. ๐Ÿ‘‡
    • Net::Z3950::RPN::RSID โ€” a reference to a result-set ID, meaning "hey, this points at a previous search". The ID of the result-set is hanging out in the id element.
  • Net::Z3950::RPN::Term is a hash with two fields:
    • term โ€” a string containing the search term itself.
    • attributes โ€” a reference to a Net::Z3950::RPN::Attributes object.
  • Net::Z3950::RPN::Attributes is an array of references to Net::Z3950::RPN::Attribute objects. (Note the plural/singular distinction โ€” yes, bestie, read it twice. ๐Ÿ’…)
  • Net::Z3950::RPN::Attribute is a hash with three elements:
    • attributeSet โ€” optional. If present, it's a dot-separated OID string, as above.
    • attributeType โ€” an integer indicating the type of the attribute. For example, under the BIB-1 attribute set, type 1 indicates a use'' attribute, type 2 a relation'' attribute, etc.
    • attributeValue โ€” an integer or string indicating the value of the attribute. For example, under BIB-1, if the attribute type is 1, then value 4 indicates a title search and 7 indicates an ISBN search; but if the attribute type is 2, then value 4 indicates a ``greater than or equal'' search, and 102 indicates a relevance match.

Every one of these classes โ€” except Attributes and Attribute โ€” is a subclass of the abstract class Net::Z3950::RPN::Node. That class has a single method, toPQF(), which you can use to turn an RPN tree, or part of one, back into a textual prefix query. ๐Ÿช„

And a heads up, because it's important: apart from toPQF(), none of these classes have any methods at all. The blessing into classes is largely just a documentation thing, so that โ€” for example โ€” if you do

{ use Data::Dumper; print Dumper($args->{RPN}) }

you get something fairly human-readable. But of course, the type distinction between the three different kinds of boolean node is important. ๐Ÿšจ

By adding your own methods to these classes (building what I call ``augmented classes''), you can easily build code that walks the tree of the incoming RPN. Go peek at samples/render-search.pl for a sample implementation of that augmented classes technique. ๐Ÿ‘€

Finally โ€” and this is the plot twist โ€” when SimpleServer is invoked using SRU/SRW (and indeed using Z39.50 if the unusual type-104 query is used), the query that is passed is expressed in CQL, the Contextual Query Language. In this case, the query string is made available in the CQL argument. ๐ŸŒˆ


๐ŸŒธ Facets ๐ŸŒธ

Servers may support the provision of facets โ€” counted lists of field values which may subsequently be used as query terms to narrow the search. Very "refine your vibe" of them. โœจ

In SRU, facets may be requested by the facetLimit parameter, as documented in the OASIS standard that formalises the SRU specification: http://docs.oasis-open.org/search-ws/searchRetrieve/v1.0/os/part3-sru2.0/searchRetrieve-v1.0-os-part3-sru2.0.html#_Toc324162453. Its value is a string consisting of a comma-separated list of facet specifications. Each facet specification consists of a count, a colon and a fieldname. For example, facetLimit=10:title,5:author asks for ten title facets and five author facets. ๐ŸŽ€

๐Ÿ“จ Request format

The facet request is passed to the search-handler function in the INPUTFACETS parameter. Its value is rather complex, due to backwards compatibility with Z39.50:

  • The top-level value is a Net::Z3950::FacetList array.
  • This is an array of Net::Z3950::FacetField objects.
  • Each of these is an object with two members, attributes and terms.
  • attributes has type Net::Z3950::RPN::Attributes and is a list of objects of type Net::Z3950::RPN::Attribute.
  • Each attribute has two elements, attributeType and attributeValue. Each value is interpreted according to its type. The meanings of the types are as follows:
    1. The name of the field to provide values of the facets.
    2. The order in which to sort the values. (But it's not clear how this is to be interpreted: it may be implementation dependent. ๐Ÿ˜ญ)
    3. The number of facets to include for the specified field.
    4. The first facet to include in the response: for example, if this is 11, then the first ten facets should be skipped.

So for example, the SRU facet specification facetLimit=10:title,5:author would be translated as a Net::Z3950::FacetList list of two Net::Z3950::FacetFields. The attributes of the first would be [1="title", 3=10], and those of the second would be [1="author", 3=5]. ๐Ÿ’…

It is not clear what the purpose of terms is, but for the record, this is how it is represented:

  • terms is a Net::Z3950::FacetTerms array.
  • This is an array of Net::Z3950::FacetTerm objects.
  • Each of these is an object with two members, term and count. The first of these is an integer, the second a string.

๐Ÿ“ค Response format

Having generated facets corresponding to the request, the search handler should return them in the OUTPUTFACETS argument. The structure of this response is similar to that of the request:

  • The top-level value is a Net::Z3950::FacetList array.
  • This is an array of Net::Z3950::FacetField objects.
  • Each of these is an object with two members, attributes and terms.
  • attributes has type Net::Z3950::RPN::Attributes and is a list of objects of type Net::Z3950::RPN::Attribute.
  • Each attribute has two elements, attributeType and attributeValue. Each value is interpreted according to its type. The meanings of the types are as follows:
    1. The name of the field for which terms are provided.

(That is the only type used.)

  • terms is a Net::Z3950::FacetTerms array.
  • This is an array of Net::Z3950::FacetTerm objects.
  • Each of these is an object with two members, term and count. The first of these is a string containing one of the facet terms, and the second is an integer indicating how many times it occurs in the records that were found by the search.

The example SimpleServer application server ztest.pl includes code that shows how to examine the INPUTFACETS data structure and create the OUTPUTFACETS structure. Go look, it's right there. ๐Ÿซถ


๐ŸŽ€ Present handler ๐ŸŽ€

The presence of a present handler in a SimpleServer front-end is optional. Each time a client wishes to retrieve records, the present service is called. The present service allows the origin to request a certain number of records retrieved from a given result set. When the present handler is called, the front-end server should prepare a result set for fetching. In practice, this means getting access to the data from the backend database and storing the data in a temporary fashion for fast and efficient fetching. โšก

And listen โ€” the present handler does not fetch anything. That task is taken care of by the fetch handler, which will be called the correct number of times by the YAZ library. More about this below. ๐Ÿ‘‡

If no present handler is implemented in the front-end, the YAZ toolkit will take care of a minimum of preparations itself. This default present handler is sufficient in many situations, where only a small amount of records are expected to be retrieved. If, on the other hand, large result sets are likely to occur, the implementation of a reasonable present handler can gain performance significantly.

The information exchanged between client and present handler is:

$args = {
                                  ## Client/server request:

           GHANDLE   =>  $obj     ## Global handle specified at creation
           HANDLE    =>  ref,     ## Reference to datastructure
           SETNAME   =>  "id",    ## Result set ID
           START     =>  xxx,     ## Start position
           COMP      =>  "",      ## Desired record composition
           SCHEMA_OID => "",      ## Z39.50 schema (OID), if any
           NUMBER    =>  yyy,     ## Number of requested records

                                  ## Response parameters:

           HITS      =>  zzz,     ## Number of returned records
           ERR_CODE  =>  0,       ## Error code
           ERR_STR   =>  ""       ## Error message
        };

๐Ÿฆ‹ Fetch handler ๐Ÿฆ‹

The fetch handler is asked to retrieve a SINGLE record from a given result set (the front-end server will automatically call the fetch handler as many times as required). ๐Ÿ”‚

The parameters exchanged between the server and the fetch handler are:

$args = {
                                  ## Client/server request:

           GHANDLE   =>  $obj     ## Global handle specified at creation
           HANDLE    =>  ref      ## Reference to data structure
           SETNAME   =>  "id"     ## ID of the requested result set
           OFFSET    =>  nnn      ## Record offset number
           REQ_FORM  =>  "n.m.k.l"## Client requested format OID
           COMP      =>  "xyz"    ## Formatting instructions
           SCHEMA_OID => "",      ## Z39.50 schema (OID), if any
           SCHEMA    =>  "abc"    ## Requested schema (string), if any

                                  ## Handler response:

Filling in the rest of that response hash, the record-returning bits look like this:

                    RECORD    =>  ""       ## Record string
                    BASENAME  =>  ""       ## Origin of returned record
                    LAST      =>  0        ## Last record in set?
                    ERR_CODE  =>  0        ## Error code
                    ERR_STR   =>  ""       ## Error string
                    SUR_FLAG  =>  0        ## Surrogate diagnostic flag
                    REP_FORM  =>  "n.m.k.l"## Provided format OID
                    SCHEMA    =>  "abc"    ## Provided schema, if any
                 };

REP_FORM starts out life holding whatever REQ_FORM was, but your handler is 100% allowed to swap it for something else if the vibe calls for it. BASENAME is supposed to hold the name of the database that the returned record actually came from, okay bestie, be honest about your sources ๐Ÿ’…. And ERR_CODE + ERR_STR? They do exactly the same thing they do in the search handler, no plot twist there.

Now if an error condition happens, SUR_FLAG is the diva that tells you which kind of error it is: either the problem is about the specific record you're fetching right now, or it's about the operation as a whole (classic example: client asked for a result set that does not exist, rip ๐Ÿ˜ญ).

If you need to return USMARC records, go peek at the MARC module on CPAN โ€” unless you already have your own way of generating those, in which case slay, carry on ๐Ÿ‘‘.

NOTE: the record offset is 1-indexed, so 1 is the offset of the first record in the set. Not 0. Never 0. Do not come for me about it โœจ.

๐Ÿ” Scan handler ๐Ÿ“

A full featured Z39.50 server should support scan (or, in some literature, browse). The client hands you a starting term for the scan, and your server is supposed to hand back an ordered list of a specified length, made up of terms that actually occur in the database. Every term in that list should be close to or equal to the term the client originally asked for. The whole point of scan, versus a plain simple search, is that it's a guarantee of hits โ€” it's literally like flipping through the index of a book, you always find something! ๐ŸŽ€

The parameters being exchanged are:

  $args = {
                                                ## Client request

                GHANDLE         => $obj,        ## Global handle specified at creation
                HANDLE          => $ref,        ## Reference to data structure
                DATABASES       => ["xxx"],     ## Reference to a list of data-
                                                ## bases to search
                TERM            => 'start',     ## The start term
                RPN             =>  $obj,       ## Reference to a Net::Z3950::RPN::Term
                attributeSet    => OID,         ## OID String (optional)

                NUMBER          => xx,          ## Number of requested terms
                POS             => yy,          ## Position of starting point
                                                ## within returned list
                STEP            => 0,           ## Step size

                                                ## Server response

                ERR_CODE        => 0,           ## Error code
                ERR_STR         => '',          ## Diagnostic message
                NUMBER          => zz,          ## Number of returned terms
                STATUS          => $status,     ## ScanSuccess/ScanFailure
                ENTRIES         => $entries     ## Referenced list of terms
        };

The term list comes back by reference in the scalar $entries, and it should point at a data structure shaped like this:

  my $entries = [
                        {       TERM            => 'energy',
                                OCCURRENCE      => 5            },

                        {       TERM            => 'energy density',
                                OCCURRENCE      => 6,           },

                        {       TERM            => 'energy flow',
                                OCCURRENCE      => 3            },

                                ...

                                ...
        ];

The $status flag only means anything after a successful scan, and it should get one of exactly two values:

  Net::Z3950::SimpleServer::ScanSuccess  Full success (default)
  Net::Z3950::SimpleServer::ScanPartial  Fewer terms returned than requested

The STEP member holds the requested number of entries in the term-list between two adjacent entries in the response.

A way better alternative to the TERM member is the RPN member โ€” that's a reference to a Net::Z3950::RPN::Term object representing the scan clause. Its structure is the same as for Term objects included as part of the RPN tree handed to search handlers. Why is it better? Because it carries attributes (like, the access points associated with the term), which the plain TERM element just tosses out the window ๐Ÿ™ˆ. It's giving "why would you downgrade yourself."


๐Ÿšช Close handler ๐ŸŒธ

The argument hash the close handler receives has literally two elements, that's it, that's the whole thing:

  $args = {
                                    ## Server provides:

                 GHANDLE   =>  $obj     ## Global handle specified at creation
                 HANDLE    =>  ref      ## Reference to data structure
              };

Whichever data structure HANDLE points at goes out of scope right after this call. So if you need to shut down a connection to your server or do anything like that โ€” this is the place, bestie, don't sleep on it ๐Ÿ’œ.


๐Ÿ—ฃ๏ธ Explain handler โœจ

The argument hash the explain handler receives has these elements:

  $args = {
                            ## Request parameters:
     GHANDLE   =>  $obj,    # Global handle specified at creation
     HANDLE    =>  ref,     # Reference to data structure
     DATABASE  =>  $dbname, # Name of database to be explained

                            ## Response parameters:
     EXPLAIN   =>  $zeerex  # ZeeRex record for specified database
  };

Your handler should return a string containing the ZeeRex XML that describes whichever database was nominated. Iconic. Periodt. ๐ŸŽ€


๐Ÿ—‘๏ธ Delete handler ๐Ÿ’€

The argument hash the delete handler receives has these elements:

  $args = {
                                    ## Client request:
                 GHANDLE   =>  $obj,    ## Global handle specified at creation
                 HANDLE    =>  ref,     ## Reference to data structure
                 SETNAME   =>  "id",    ## Result set ID

                                    ## Server response:
                 STATUS    => 0         ## Deletion status
              };

The SETNAME element may or may not be defined โ€” pay attention to this one:

  • If it is defined, SETNAME is the name of the result set that needs to be deleted.
  • If it is not defined, then all result-sets associated with the current session should be deleted.

Either way, your callback function reports success or failure by setting STATUS to either zero for success, or an integer from 1 to 10 to flag one of the ten possible failure codes described in section 3.2.4.1.4 of the Z39.50 standard โ€” see http://www.loc.gov/z3950/agency/markup/05.html#Delete-list-statuses1


๐Ÿ”ค Sort handler ๐Ÿฆ‹

The argument hash the sort handler receives has these elements:

        $args = {
                                        ## Client request:
                GHANDLE => $obj,        ## Global handle specified at creation
                HANDLE => ref,          ## Reference to data structure
                INPUT => [ a, b ... ],  ## Names of result-sets to sort
                OUTPUT => "name",       ## Name of result-set to sort into
                SEQUENCE                ## Sort specification: see below

                                        ## Server response:
                STATUS => 0,            ## Success, Partial or Failure
                ERR_CODE => 0,          ## Error code
                ERR_STR => '',          ## Diagnostic message

        };

The SEQUENCE element is a reference to an array, and every element of that array is a hash representing a sort key. Each of those hashes contains the following elements:

  • RELATION โ€” 0 for an ascending sort, 1 for descending, 3 for ascending by frequency, or 4 for descending by frequency.
  • CASE โ€” 0 for a case-sensitive sort, 1 for case-insensitive.
  • MISSING โ€” how to respond if one or more records in the set you're sorting are missing the fields named in the sort specification. 1 to abort the sort, 2 to use a "null value", 3 if a value is provided to use in place of the missing data (although in that last case, the actual value to use is currently not made available, so this is useless) ๐Ÿ’€.

And one or other of the following:

  • SORTFIELD โ€” a string indicating the field to be sorted, which the server may interpret as it sees fit (presumably by some out-of-band agreement with the client).
  • ELEMENTSPEC_TYPE and ELEMENTSPEC_VALUE โ€” I have no idea what this is. ๐Ÿ˜ญ Not me being honest.
  • ATTRSET and SORT_ATTR โ€” ATTRSET is the attribute set the attributes are taken from, and SORT_ATTR is a reference to an array containing the attributes themselves. Each attribute is represented by (are you following this carefully?) yet another hash, this one containing the elements ATTR_TYPE and ATTR_VALUE: for example, type=1 and value=4 in the BIB-1 attribute set would indicate access-point 4, which is title โ€” so a sort of title is what's being requested. Nested hashes all the way down, besties ๐Ÿงš.

Precisely why all of the above is so is not clear, but it goes some way to explain why, in the Z39.50 world, the developers of the standard are not so much worshiped as blamed. ๐Ÿ’…

Your backend function should set STATUS to 0 on success, 1 for "partial success" (don't ask) or 2 on failure โ€” and in the failure case, ERR_CODE and ERR_STR should be set too.


๐ŸŒ Support for SRU and SRW ๐ŸŒˆ

Since release 1.0, SimpleServer includes support for serving the SRU and SRW protocols as well as Z39.50. These ``web-friendly'' protocols enable similar functionality to that of Z39.50, but by means of rich URLs in the case of SRU, and a SOAP-based web-service in the case of SRW. These protocols are described at http://www.loc.gov/standards/sru/

To serve these protocols from a SimpleServer-based application, you have to launch the application with a YAZ Generic Frontend Server (GFS) configuration file, which you can specify using the command-line argument "-f" filename. A minimal configuration file looks like this:

  <yazgfs>
    <server>
      <cql2rpn>pqf.properties</cql2rpn>
    </server>
  </yazgfs>

What this file says is literally just that "pqf.properties" should be used to translate the CQL queries of SRU and SRW into corresponding Z39.50 Type-1 queries. For more info about YAZ GFS configuration, including how to specify an Explain record, see the Virtual Hosts section of the YAZ manual at http://www.indexdata.com/yaz/doc/server.vhosts.html

The mapping of CQL queries into Z39.50 Type-1 queries is specified by a file that indicates which BIB-1 attributes should be generated for each CQL index, relation, modifiers, etc. A typical section of this file looks like this:

index.dc.title                        = 1=4
index.dc.subject                      = 1=21
index.dc.creator                      = 1=1003
relation.<                            = 2=1
relation.le                           = 2=2

So bestie, lowkey what this config file is: it's the thing that maps your BIB-1 access points (that's type=1 btw) for the Dublin Core indexes title, subject and creator โ€” and then the BIB-1 relations (type=2) that line up with the CQL relations < and <=. That's it, that's the whole vibe of the file. ๐ŸŽ€

Wanna know more about how this file is even formatted? The CQL section of the YAZ manual has you covered at http://www.indexdata.com/yaz/doc/tools.html#cql โ€” go read it, it's giving homework but make it helpful. โœจ

And ooh, the YAZ distribution ships with a sample CQL-to-PQF mapping config file named pqf.properties. That one is lowkey enough for sooo many applications, and it's genuinely a great base to build off for basically all the others. ๐Ÿ‘‘

Now here's the tea tho ๐Ÿต โ€” if a SimpleServer-based application runs without this SRU-specific configuration, it can still serve SRU just fine. BUT. CQL queries won't get translated at all โ€” they'll just get passed straight through to the search-handler function as the "CQL" member of the parameters hash. So the back-end application is fully responsible for parsing and handling that CQL query itself. The easiest way to do that? Ed Summers' truly iconic CQL::Parser module, available from CPAN at http://search.cpan.org/~esummers/CQL-Parser/ โ€” no cap, it ate. ๐Ÿ’…


๐ŸŒˆ AUTHORS ๐ŸŒˆ

The masterminds, the main characters, the ones who did that:

  • Anders Sรธnderberg (sondberg@indexdata.dk) ๐Ÿซถ
  • Sebastian Hammer (quinn@indexdata.com) โœจ
  • Mike Taylor (indexdata.com) ๐Ÿ’–

Copyright (C) 2000-2017 by Index Data.

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.4 or, at your option, any later version of Perl 5 you may have available. Fr fr, free as in go wild (respectfully). ๐Ÿฆ‹


๐Ÿ‘€ SEE ALSO ๐Ÿ‘€

Literally any Perl module that's useful for accessing the data source of your choice. That's it, that's the rec โ€” pick your fave, pick your source, slay. โญ


And that's the whole page, bestie!! ๐ŸŽ‰ This one's from perl v5.42.3, dated 2026-08-19 โ€” so bookmark it, screenshot it, print it out and put it on your wall, whatever feels right. SimpleServer(3pm), you were an absolute icon. ๐Ÿ’…๐ŸŒธ

๐Ÿ“ฆ where this page comes from