place web files in 1.6 folder.
<simpara>For example, the following code implements an OpenSRF service. The service\r
includes one method named <literal>opensrf.simple-text.reverse()</literal> that accepts one\r
string as input and returns the reversed version of that string:</simpara>\r
- <programlisting language="perl" linenumbering="unnumbered">#!/usr/bin/perl\r
+<programlisting language="perl" linenumbering="unnumbered">\r
+#!/usr/bin/perl\r
\r
- package OpenSRF::Application::Demo::SimpleText;\r
+package OpenSRF::Application::Demo::SimpleText;\r
\r
- use strict;\r
+use strict;\r
\r
- use OpenSRF::Application;\r
- use parent qw/OpenSRF::Application/;\r
+use OpenSRF::Application;\r
+use parent qw/OpenSRF::Application/;\r
\r
- sub text_reverse {\r
- my ($self , $conn, $text) = @_;\r
- my $reversed_text = scalar reverse($text);\r
- return $reversed_text;\r
- }\r
+sub text_reverse {\r
+ my ($self , $conn, $text) = @_;\r
+ my $reversed_text = scalar reverse($text);\r
+ return $reversed_text;\r
+}\r
\r
- __PACKAGE__->register_method(\r
- method => 'text_reverse',\r
- api_name => 'opensrf.simple-text.reverse'\r
- );</programlisting>\r
+__PACKAGE__->register_method(\r
+ method => 'text_reverse',\r
+ api_name => 'opensrf.simple-text.reverse'\r
+);\r
+</programlisting>\r
<simpara>Ten lines of code, and we have a complete OpenSRF service that exposes a single\r
method and could be deployed quickly on a cluster of servers to meet your\r
application’s ravenous demand for reversed strings! If you’re unfamiliar with\r
<simpara>Begin by defining the service itself in <literal>opensrf.xml</literal>. To register the\r
<literal>opensrf.simple-text</literal> service, add the following section to the <literal><apps></literal>\r
element (corresponding to the XPath <literal>/opensrf/default/apps/</literal>):</simpara>\r
- <programlisting language="xml" linenumbering="unnumbered"><apps>\r
- <opensrf.simple-text> <co id="CO1-1"/> \r
- <keepalive>3</keepalive><co id="CO1-2"/> \r
- <stateless>1</stateless><co id="CO1-3"/>\r
- <language>perl</language><co id="CO1-4"/> \r
- <implementation>OpenSRF::Application::Demo::SimpleText</implementation><co id="CO1-5"/> \r
- <max_requests>100</max_requests><co id="CO1-6"/> \r
- <unix_config>\r
- <max_requests>1000</max_requests> <co id="CO1-7"/> \r
- <unix_log>opensrf.simple-text_unix.log</unix_log> <co id="CO1-8"/> \r
- <unix_sock>opensrf.simple-text_unix.sock</unix_sock><co id="CO1-9"/> \r
- <unix_pid>opensrf.simple-text_unix.pid</unix_pid> <co id="CO1-10"/> \r
- <min_children>5</min_children> <co id="CO1-11"/> \r
- <max_children>15</max_children><co id="CO1-12"/> \r
- <min_spare_children>2</min_spare_children><co id="CO1-13"/> \r
- <max_spare_children>5</max_spare_children> <co id="CO1-14"/> \r
- </unix_config>\r
- </opensrf.simple-text>\r
+<programlisting language="xml" linenumbering="unnumbered">\r
+<apps>\r
+ <opensrf.simple-text> <co id="CO1-1"/> \r
+ <keepalive>3</keepalive><co id="CO1-2"/> \r
+ <stateless>1</stateless><co id="CO1-3"/>\r
+ <language>perl</language><co id="CO1-4"/> \r
+ <implementation>OpenSRF::Application::Demo::SimpleText</implementation><co id="CO1-5"/> \r
+ <max_requests>100</max_requests><co id="CO1-6"/> \r
+ <unix_config>\r
+ <max_requests>1000</max_requests> <co id="CO1-7"/> \r
+ <unix_log>opensrf.simple-text_unix.log</unix_log> <co id="CO1-8"/> \r
+ <unix_sock>opensrf.simple-text_unix.sock</unix_sock><co id="CO1-9"/> \r
+ <unix_pid>opensrf.simple-text_unix.pid</unix_pid> <co id="CO1-10"/> \r
+ <min_children>5</min_children> <co id="CO1-11"/> \r
+ <max_children>15</max_children><co id="CO1-12"/> \r
+ <min_spare_children>2</min_spare_children><co id="CO1-13"/> \r
+ <max_spare_children>5</max_spare_children> <co id="CO1-14"/> \r
+ </unix_config>\r
+ </opensrf.simple-text>\r
\r
- <!-- other OpenSRF services registered here... -->\r
- </apps></programlisting>\r
+ <!-- other OpenSRF services registered here... -->\r
+</apps>\r
+</programlisting>\r
<calloutlist>\r
<callout arearefs="CO1-1">\r
<simpara>\r
edit the <literal>opensrf_core.xml</literal> configuration file to add the service to the list\r
of publicly accessible services:</simpara>\r
<formalpara><title>Making a service publicly accessible in <literal>opensrf_core.xml</literal></title><para>\r
- <programlisting language="xml" linenumbering="unnumbered"><router><co id="CO2-1"/> \r
- <!-- This is the public router. On this router, we only register applications\r
- which should be accessible to everyone on the opensrf network -->\r
- <name>router</name>\r
- <domain>public.localhost</domain><co id="CO2-2"/>\r
- <services>\r
- <service>opensrf.math</service>\r
- <service>opensrf.simple-text</service> <co id="CO2-3"/> \r
- </services>\r
- </router></programlisting>\r
+<programlisting language="xml" linenumbering="unnumbered">\r
+<router><co id="CO2-1"/> \r
+ <!-- This is the public router. On this router, we only register applications\r
+ which should be accessible to everyone on the opensrf network -->\r
+ <name>router</name>\r
+ <domain>public.localhost</domain><co id="CO2-2"/>\r
+ <services>\r
+ <service>opensrf.math</service>\r
+ <service>opensrf.simple-text</service> <co id="CO2-3"/> \r
+ </services>\r
+</router>\r
+</programlisting>\r
</para></formalpara>\r
<calloutlist>\r
<callout arearefs="CO2-1">\r
<simpara>The following example calls the <literal>opensrf.simple-text.reverse</literal> method of the\r
<literal>opensrf.simple-text</literal> OpenSRF service, passing the string <literal>"foobar"</literal> as the\r
only method argument:</simpara>\r
- <programlisting language="sh" linenumbering="unnumbered">$ srfsh\r
- srfsh # request opensrf.simple-text opensrf.simple-text.reverse "foobar"\r
+<programlisting language="sh" linenumbering="unnumbered">\r
+$ srfsh\r
+srfsh # request opensrf.simple-text opensrf.simple-text.reverse "foobar"\r
\r
- Received Data: "raboof"\r
+Received Data: "raboof"\r
\r
- =------------------------------------\r
- Request Completed Successfully\r
- Request Time in seconds: 0.016718\r
- =------------------------------------</programlisting>\r
+=------------------------------------\r
+Request Completed Successfully\r
+Request Time in seconds: 0.016718\r
+=------------------------------------\r
+</programlisting>\r
</simplesect>\r
<simplesect id="opensrfIntrospection">\r
<title>Getting documentation for OpenSRF methods from the srfsh client</title>\r
accurate information. The quality varies across the set of OpenSRF and\r
Evergreen APIs, although some effort is being put towards improving the\r
state of the internal documentation.</simpara></note>\r
- <programlisting language="sh" linenumbering="unnumbered">srfsh# introspect opensrf.simple-text "opensrf.simple-text.reverse"\r
- --> opensrf.simple-text\r
+<programlisting language="sh" linenumbering="unnumbered">\r
+srfsh# introspect opensrf.simple-text "opensrf.simple-text.reverse"\r
+--> opensrf.simple-text\r
\r
- Received Data: {\r
- "__c":"opensrf.simple-text",\r
- "__p":{\r
- "api_level":1,\r
- "stream":0, <co id="CO3-1"/>\r
- "object_hint":"OpenSRF_Application_Demo_SimpleText",\r
- "remote":0,\r
- "package":"OpenSRF::Application::Demo::SimpleText", <co id="CO3-2"/>\r
- "api_name":"opensrf.simple-text.reverse",<co id="CO3-3"/>\r
- "server_class":"opensrf.simple-text",\r
- "signature":{ <co id="CO3-4"/>\r
- "params":[ <co id="CO3-5"/>\r
- {\r
- "desc":"The string to reverse",\r
- "name":"text",\r
- "type":"string"\r
- }\r
- ],\r
- "desc":"Returns the input string in reverse order\n", <co id="CO3-6"/>\r
- "return":{ <co id="CO3-7"/>\r
- "desc":"Returns the input string in reverse order",\r
- "type":"string"\r
- }\r
- },\r
- "method":"text_reverse", <co id="CO3-8"/>\r
- "argc":1 <co id="CO3-9"/>\r
- }\r
- }</programlisting>\r
+Received Data: {\r
+ "__c":"opensrf.simple-text",\r
+ "__p":{\r
+ "api_level":1,\r
+ "stream":0, <co id="CO3-1"/>\r
+ "object_hint":"OpenSRF_Application_Demo_SimpleText",\r
+ "remote":0,\r
+ "package":"OpenSRF::Application::Demo::SimpleText", <co id="CO3-2"/>\r
+ "api_name":"opensrf.simple-text.reverse",<co id="CO3-3"/>\r
+ "server_class":"opensrf.simple-text",\r
+ "signature":{ <co id="CO3-4"/>\r
+ "params":[ <co id="CO3-5"/>\r
+ {\r
+ "desc":"The string to reverse",\r
+ "name":"text",\r
+ "type":"string"\r
+ }\r
+ ],\r
+ "desc":"Returns the input string in reverse order\n", <co id="CO3-6"/>\r
+ "return":{ <co id="CO3-7"/>\r
+ "desc":"Returns the input string in reverse order",\r
+ "type":"string"\r
+ }\r
+ },\r
+ "method":"text_reverse", <co id="CO3-8"/>\r
+ "argc":1 <co id="CO3-9"/>\r
+ }\r
+}\r
+</programlisting>\r
<calloutlist>\r
<callout arearefs="CO3-1">\r
<simpara>\r
<title>Calling OpenSRF methods from Perl applications</title>\r
<simpara>To call an OpenSRF method from Perl, you must connect to the OpenSRF service,\r
issue the request to the method, and then retrieve the results.</simpara>\r
- <programlisting language="perl" linenumbering="unnumbered">#/usr/bin/perl\r
- use strict;\r
- use OpenSRF::AppSession;\r
- use OpenSRF::System;\r
+<programlisting language="perl" linenumbering="unnumbered">\r
+#/usr/bin/perl\r
+use strict;\r
+use OpenSRF::AppSession;\r
+use OpenSRF::System;\r
\r
- OpenSRF::System->bootstrap_client(config_file => '/openils/conf/opensrf_core.xml');<co id="CO4-1"/>\r
+OpenSRF::System->bootstrap_client(config_file => '/openils/conf/opensrf_core.xml');<co id="CO4-1"/>\r
\r
- my $session = OpenSRF::AppSession->create("opensrf.simple-text");<co id="CO4-2"/>\r
+my $session = OpenSRF::AppSession->create("opensrf.simple-text");<co id="CO4-2"/>\r
\r
- print "substring: Accepts a string and a number as input, returns a string\n";\r
- my $result = $session->request("opensrf.simple-text.substring", "foobar", 3);<co id="CO4-3"/>\r
- my $request = $result->gather(); <co id="CO4-4"/>\r
- print "Substring: $request\n\n";\r
+print "substring: Accepts a string and a number as input, returns a string\n";\r
+my $result = $session->request("opensrf.simple-text.substring", "foobar", 3);<co id="CO4-3"/>\r
+my $request = $result->gather(); <co id="CO4-4"/>\r
+print "Substring: $request\n\n";\r
\r
- print "split: Accepts two strings as input, returns an array of strings\n";\r
- $request = $session->request("opensrf.simple-text.split", "This is a test", " ");<co id="CO4-5"/>\r
- my $output = "Split: [";\r
- my $element;\r
- while ($element = $request->recv()) { <co id="CO4-6"/>\r
- $output .= $element->content . ", "; <co id="CO4-7"/>\r
- }\r
- $output =~ s/, $/]/;\r
- print $output . "\n\n";\r
+print "split: Accepts two strings as input, returns an array of strings\n";\r
+$request = $session->request("opensrf.simple-text.split", "This is a test", " ");<co id="CO4-5"/>\r
+my $output = "Split: [";\r
+my $element;\r
+while ($element = $request->recv()) { <co id="CO4-6"/>\r
+ $output .= $element->content . ", "; <co id="CO4-7"/>\r
+}\r
+$output =~ s/, $/]/;\r
+print $output . "\n\n";\r
\r
- print "statistics: Accepts an array of strings as input, returns a hash\n";\r
- my @many_strings = [\r
- "First I think I'll have breakfast",\r
- "Then I think that lunch would be nice",\r
- "And then seventy desserts to finish off the day"\r
- ];\r
+print "statistics: Accepts an array of strings as input, returns a hash\n";\r
+my @many_strings = [\r
+ "First I think I'll have breakfast",\r
+ "Then I think that lunch would be nice",\r
+ "And then seventy desserts to finish off the day"\r
+];\r
\r
- $result = $session->request("opensrf.simple-text.statistics", @many_strings); <co id="CO4-8"/>\r
- $request = $result->gather(); <co id="CO4-9"/>\r
- print "Length: " . $result->{'length'} . "\n";\r
- print "Word count: " . $result->{'word_count'} . "\n";\r
+$result = $session->request("opensrf.simple-text.statistics", @many_strings); <co id="CO4-8"/>\r
+$request = $result->gather(); <co id="CO4-9"/>\r
+print "Length: " . $result->{'length'} . "\n";\r
+print "Word count: " . $result->{'word_count'} . "\n";\r
\r
- $session->disconnect(); <co id="CO4-10"/></programlisting>\r
+$session->disconnect(); <co id="CO4-10"/>\r
+</programlisting>\r
<calloutlist>\r
<callout arearefs="CO4-1">\r
<simpara>\r
returns a list, we accept two arguments of type string: the string to be split,\r
and the delimiter that should be used to split the string.</simpara>\r
<formalpara><title>Basic text splitting method</title><para>\r
- <programlisting language="perl" linenumbering="unnumbered">sub text_split {\r
- my $self = shift;\r
- my $conn = shift;\r
- my $text = shift;\r
- my $delimiter = shift || ' ';\r
+<programlisting language="perl" linenumbering="unnumbered">\r
+sub text_split {\r
+ my $self = shift;\r
+ my $conn = shift;\r
+ my $text = shift;\r
+ my $delimiter = shift || ' ';\r
\r
- my @split_text = split $delimiter, $text;\r
- return \@split_text;\r
- }\r
+ my @split_text = split $delimiter, $text;\r
+ return \@split_text;\r
+}\r
\r
- __PACKAGE__->register_method(\r
- method => 'text_split',\r
- api_name => 'opensrf.simple-text.split'\r
- );</programlisting>\r
+__PACKAGE__->register_method(\r
+ method => 'text_split',\r
+ api_name => 'opensrf.simple-text.split'\r
+);\r
+</programlisting>\r
</para></formalpara>\r
<simpara>We simply return a reference to the list, and OpenSRF does the rest of the work\r
for us to convert the data into the language-independent format that is then\r
configuration file and dynamically register OpenSRF methods for creating,\r
reading, updating, and deleting all of the defined classes.</simpara>\r
<formalpara><title>Example fieldmapper class definition for "Open User Summary"</title><para>\r
- <programlisting language="xml" linenumbering="unnumbered"><class id="mous" controller="open-ils.cstore open-ils.pcrud"\r
- oils_obj:fieldmapper="money::open_user_summary"\r
- oils_persist:tablename="money.open_usr_summary"\r
- reporter:label="Open User Summary"> <co id="CO5-1"/>\r
- <fields oils_persist:primary="usr" oils_persist:sequence=""> <co id="CO5-2"/> \r
- <field name="balance_owed" reporter:datatype="money" /> <co id="CO5-3"/> \r
- <field name="total_owed" reporter:datatype="money" />\r
- <field name="total_paid" reporter:datatype="money" />\r
- <field name="usr" reporter:datatype="link"/>\r
- </fields>\r
- <links>\r
- <link field="usr" reltype="has_a" key="id" map="" class="au"/><co id="CO5-4"/> \r
- </links>\r
- <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1"><co id="CO5-5"/> \r
- <actions>\r
- <retrieve permission="VIEW_USER"><co id="CO5-6"/> \r
- <context link="usr" field="home_ou"/><co id="CO5-7"/>\r
- </retrieve>\r
- </actions>\r
- </permacrud>\r
- </class></programlisting>\r
+<programlisting language="xml" linenumbering="unnumbered">\r
+<class id="mous" controller="open-ils.cstore open-ils.pcrud"\r
+ oils_obj:fieldmapper="money::open_user_summary"\r
+ oils_persist:tablename="money.open_usr_summary"\r
+ reporter:label="Open User Summary"> <co id="CO5-1"/>\r
+ <fields oils_persist:primary="usr" oils_persist:sequence=""> <co id="CO5-2"/> \r
+ <field name="balance_owed" reporter:datatype="money" /> <co id="CO5-3"/> \r
+ <field name="total_owed" reporter:datatype="money" />\r
+ <field name="total_paid" reporter:datatype="money" />\r
+ <field name="usr" reporter:datatype="link"/>\r
+ </fields>\r
+ <links>\r
+ <link field="usr" reltype="has_a" key="id" map="" class="au"/><co id="CO5-4"/> \r
+ </links>\r
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1"><co id="CO5-5"/> \r
+ <actions>\r
+ <retrieve permission="VIEW_USER"><co id="CO5-6"/> \r
+ <context link="usr" field="home_ou"/><co id="CO5-7"/>\r
+ </retrieve>\r
+ </actions>\r
+ </permacrud>\r
+</class>\r
+</programlisting>\r
</para></formalpara>\r
<calloutlist>\r
<callout arearefs="CO5-1">\r
<simpara>In the following example, the text splitting method has been reimplemented to\r
support streaming; very few changes are required:</simpara>\r
<formalpara><title>Text splitting method - streaming mode</title><para>\r
- <programlisting language="perl" linenumbering="unnumbered">sub text_split {\r
- my $self = shift;\r
- my $conn = shift;\r
- my $text = shift;\r
- my $delimiter = shift || ' ';\r
+<programlisting language="perl" linenumbering="unnumbered">\r
+sub text_split {\r
+ my $self = shift;\r
+ my $conn = shift;\r
+ my $text = shift;\r
+ my $delimiter = shift || ' ';\r
\r
- my @split_text = split $delimiter, $text;\r
- foreach my $string (@split_text) { <co id="CO6-1"/>\r
- $conn->respond($string);\r
- }\r
- return undef;\r
- }\r
+ my @split_text = split $delimiter, $text;\r
+ foreach my $string (@split_text) { <co id="CO6-1"/>\r
+ $conn->respond($string);\r
+ }\r
+ return undef;\r
+}\r
\r
- __PACKAGE__->register_method(\r
- method => 'text_split',\r
- api_name => 'opensrf.simple-text.split',\r
- stream => 1<co id="CO6-2"/>\r
- );</programlisting>\r
+__PACKAGE__->register_method(\r
+ method => 'text_split',\r
+ api_name => 'opensrf.simple-text.split',\r
+ stream => 1<co id="CO6-2"/>\r
+);\r
+</programlisting>\r
</para></formalpara>\r
<calloutlist>\r
<callout arearefs="CO6-1">\r
<simpara>You can include many calls to the OpenSRF logger; only those that are higher\r
than your configured logging level will actually hit the log. The following\r
example exercises all of the available logging levels in OpenSRF:</simpara>\r
- <programlisting language="perl" linenumbering="unnumbered">use OpenSRF::Utils::Logger;\r
- my $logger = OpenSRF::Utils::Logger;\r
- # some code in some function\r
- {\r
- $logger->error("Hmm, something bad DEFINITELY happened!");\r
- $logger->warn("Hmm, something bad might have happened.");\r
- $logger->info("Something happened.");\r
- $logger->debug("Something happened; here are some more details.");\r
- $logger->internal("Something happened; here are all the gory details.")\r
- }</programlisting>\r
+<programlisting language="perl" linenumbering="unnumbered">\r
+use OpenSRF::Utils::Logger;\r
+my $logger = OpenSRF::Utils::Logger;\r
+# some code in some function\r
+{\r
+ $logger->error("Hmm, something bad DEFINITELY happened!");\r
+ $logger->warn("Hmm, something bad might have happened.");\r
+ $logger->info("Something happened.");\r
+ $logger->debug("Something happened; here are some more details.");\r
+ $logger->internal("Something happened; here are all the gory details.")\r
+}\r
+</programlisting>\r
<simpara>If you call the mythical OpenSRF method containing the preceding OpenSRF logger\r
statements on a system running at the default logging level of INFO, you will\r
only see the INFO, WARN, and ERR messages, as follows:</simpara>\r
<formalpara><title>Results of logging calls at the default level of INFO</title><para>\r
- <screen>[2010-03-17 22:27:30] opensrf.simple-text [ERR :5681:SimpleText.pm:277:] Hmm, something bad DEFINITELY happened!\r
- [2010-03-17 22:27:30] opensrf.simple-text [WARN:5681:SimpleText.pm:278:] Hmm, something bad might have happened.\r
- [2010-03-17 22:27:30] opensrf.simple-text [INFO:5681:SimpleText.pm:279:] Something happened.</screen>\r
+<screen>\r
+[2010-03-17 22:27:30] opensrf.simple-text [ERR :5681:SimpleText.pm:277:] Hmm, something bad DEFINITELY happened!\r
+[2010-03-17 22:27:30] opensrf.simple-text [WARN:5681:SimpleText.pm:278:] Hmm, something bad might have happened.\r
+[2010-03-17 22:27:30] opensrf.simple-text [INFO:5681:SimpleText.pm:279:] Something happened.\r
+</screen>\r
</para></formalpara>\r
<simpara>If you then increase the the logging level to INTERNAL (5), the logs will\r
contain much more information, as follows:</simpara>\r
<formalpara><title>Results of logging calls at the default level of INTERNAL</title><para>\r
- <screen>[2010-03-17 22:48:11] opensrf.simple-text [ERR :5934:SimpleText.pm:277:] Hmm, something bad DEFINITELY happened!\r
- [2010-03-17 22:48:11] opensrf.simple-text [WARN:5934:SimpleText.pm:278:] Hmm, something bad might have happened.\r
- [2010-03-17 22:48:11] opensrf.simple-text [INFO:5934:SimpleText.pm:279:] Something happened.\r
- [2010-03-17 22:48:11] opensrf.simple-text [DEBG:5934:SimpleText.pm:280:] Something happened; here are some more details.\r
- [2010-03-17 22:48:11] opensrf.simple-text [INTL:5934:SimpleText.pm:281:] Something happened; here are all the gory details.\r
- [2010-03-17 22:48:11] opensrf.simple-text [ERR :5934:SimpleText.pm:283:] Resolver did not find a cache hit\r
- [2010-03-17 22:48:21] opensrf.simple-text [INTL:5934:Cache.pm:125:] Stored opensrf.simple-text.test_cache.masaa => "here" in memcached server\r
- [2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:Application.pm:579:] Coderef for [OpenSRF::Application::Demo::SimpleText::test_cache]...\r
- [2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:Application.pm:586:] A top level Request object is responding de nada\r
- [2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:Application.pm:190:] Method duration for [opensrf.simple-text.test_cache]: 10.005\r
- [2010-03-17 22:48:21] opensrf.simple-text [INTL:5934:AppSession.pm:780:] Calling queue_wait(0)\r
- [2010-03-17 22:48:21] opensrf.simple-text [INTL:5934:AppSession.pm:769:] Resending...0\r
- [2010-03-17 22:48:21] opensrf.simple-text [INTL:5934:AppSession.pm:450:] In send\r
- [2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:AppSession.pm:506:] AppSession sending RESULT to opensrf@private.localhost/... \r
- [2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:AppSession.pm:506:] AppSession sending STATUS to opensrf@private.localhost/... \r
- ...</screen>\r
+<screen>\r
+[2010-03-17 22:48:11] opensrf.simple-text [ERR :5934:SimpleText.pm:277:] Hmm, something bad DEFINITELY happened!\r
+[2010-03-17 22:48:11] opensrf.simple-text [WARN:5934:SimpleText.pm:278:] Hmm, something bad might have happened.\r
+[2010-03-17 22:48:11] opensrf.simple-text [INFO:5934:SimpleText.pm:279:] Something happened.\r
+[2010-03-17 22:48:11] opensrf.simple-text [DEBG:5934:SimpleText.pm:280:] Something happened; here are some more details.\r
+[2010-03-17 22:48:11] opensrf.simple-text [INTL:5934:SimpleText.pm:281:] Something happened; here are all the gory details.\r
+[2010-03-17 22:48:11] opensrf.simple-text [ERR :5934:SimpleText.pm:283:] Resolver did not find a cache hit\r
+[2010-03-17 22:48:21] opensrf.simple-text [INTL:5934:Cache.pm:125:] Stored opensrf.simple-text.test_cache.masaa => "here" in memcached server\r
+[2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:Application.pm:579:] Coderef for [OpenSRF::Application::Demo::SimpleText::test_cache]...\r
+[2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:Application.pm:586:] A top level Request object is responding de nada\r
+[2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:Application.pm:190:] Method duration for [opensrf.simple-text.test_cache]: 10.005\r
+[2010-03-17 22:48:21] opensrf.simple-text [INTL:5934:AppSession.pm:780:] Calling queue_wait(0)\r
+[2010-03-17 22:48:21] opensrf.simple-text [INTL:5934:AppSession.pm:769:] Resending...0\r
+[2010-03-17 22:48:21] opensrf.simple-text [INTL:5934:AppSession.pm:450:] In send\r
+[2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:AppSession.pm:506:] AppSession sending RESULT to opensrf@private.localhost/... \r
+[2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:AppSession.pm:506:] AppSession sending STATUS to opensrf@private.localhost/... \r
+...\r
+</screen>\r
</para></formalpara>\r
<simpara>To see everything that is happening in OpenSRF, try leaving your logging level\r
set to INTERNAL for a few minutes - just ensure that you have a lot of free disk\r
by sleeping for 10 seconds the first time it receives a given cache key and\r
cannot retrieve a corresponding value from the cache:</simpara>\r
<formalpara><title>Simple caching OpenSRF service</title><para>\r
- <programlisting language="perl" linenumbering="unnumbered">use OpenSRF::Utils::Cache;<co id="CO7-1"/>\r
- sub test_cache {\r
- my $self = shift;\r
- my $conn = shift;\r
- my $test_key = shift;\r
- my $cache = OpenSRF::Utils::Cache->new('global'); <co id="CO7-2"/>\r
- my $cache_key = "opensrf.simple-text.test_cache.$test_key"; <co id="CO7-3"/>\r
- my $result = $cache->get_cache($cache_key) || undef; <co id="CO7-4"/>\r
- if ($result) {\r
- $logger->info("Resolver found a cache hit");\r
- return $result;\r
- }\r
- sleep 10; <co id="CO7-5"/>\r
- my $cache_timeout = 300; <co id="CO7-6"/>\r
- $cache->put_cache($cache_key, "here", $cache_timeout); <co id="CO7-7"/>\r
- return "There was no cache hit.";\r
- }</programlisting>\r
+<programlisting language="perl" linenumbering="unnumbered">\r
+use OpenSRF::Utils::Cache;<co id="CO7-1"/>\r
+sub test_cache {\r
+ my $self = shift;\r
+ my $conn = shift;\r
+ my $test_key = shift;\r
+ my $cache = OpenSRF::Utils::Cache->new('global'); <co id="CO7-2"/>\r
+ my $cache_key = "opensrf.simple-text.test_cache.$test_key"; <co id="CO7-3"/>\r
+ my $result = $cache->get_cache($cache_key) || undef; <co id="CO7-4"/>\r
+ if ($result) {\r
+ $logger->info("Resolver found a cache hit");\r
+ return $result;\r
+ }\r
+ sleep 10; <co id="CO7-5"/>\r
+ my $cache_timeout = 300; <co id="CO7-6"/>\r
+ $cache->put_cache($cache_key, "here", $cache_timeout); <co id="CO7-7"/>\r
+ return "There was no cache hit.";\r
+}\r
+</programlisting>\r
</para></formalpara>\r
<calloutlist>\r
<callout arearefs="CO7-1">\r
OpenSRF requests and returns OpenSRF results as HTTP results to the initiating\r
HTTP client.</simpara>\r
<formalpara><title>Issuing an HTTP POST request to an OpenSRF method via the OpenSRF HTTP Translator</title><para>\r
- <programlisting language="bash" linenumbering="unnumbered"># curl request broken up over multiple lines for legibility\r
- curl -H "X-OpenSRF-service: opensrf.simple-text"<co id="CO8-1"/>\r
- --data 'osrf-msg=[ \<co id="CO8-2"/>\r
- {"__c":"osrfMessage","__p":{"threadTrace":0,"locale":"en-CA", <co id="CO8-3"/>\r
- "type":"REQUEST","payload": {"__c":"osrfMethod","__p": \r
- {"method":"opensrf.simple-text.reverse","params":["foobar"]} \r
- }} \r
- }]' \r
- http://localhost/osrf-http-translator <co id="CO8-4"/></programlisting>\r
+<programlisting language="bash" linenumbering="unnumbered">\r
+# curl request broken up over multiple lines for legibility\r
+curl -H "X-OpenSRF-service: opensrf.simple-text"<co id="CO8-1"/>\r
+ --data 'osrf-msg=[ \<co id="CO8-2"/>\r
+ {"__c":"osrfMessage","__p":{"threadTrace":0,"locale":"en-CA", <co id="CO8-3"/>\r
+ "type":"REQUEST","payload": {"__c":"osrfMethod","__p": \r
+ {"method":"opensrf.simple-text.reverse","params":["foobar"]} \r
+ }} \r
+ }]' \r
+http://localhost/osrf-http-translator <co id="CO8-4"/>\r
+</programlisting>\r
</para></formalpara>\r
<calloutlist>\r
<callout arearefs="CO8-1">\r
</callout>\r
</calloutlist>\r
<formalpara><title>Results from an HTTP POST request to an OpenSRF method via the OpenSRF HTTP Translator</title><para>\r
- <programlisting language="bash" linenumbering="unnumbered"># HTTP response broken up over multiple lines for legibility\r
- [{"__c":"osrfMessage","__p": <co id="CO9-1"/>\r
- {"threadTrace":0, "payload": <co id="CO9-2"/>\r
- {"__c":"osrfResult","__p": <co id="CO9-3"/>\r
- {"status":"OK","content":"raboof","statusCode":200} <co id="CO9-4"/>\r
- },"type":"RESULT","locale":"en-CA" <co id="CO9-5"/>\r
- }\r
- },\r
- {"__c":"osrfMessage","__p": <co id="CO9-6"/>\r
- {"threadTrace":0,"payload": <co id="CO9-7"/>\r
- {"__c":"osrfConnectStatus","__p": <co id="CO9-8"/>\r
- {"status":"Request Complete","statusCode":205}<co id="CO9-9"/>\r
- },"type":"STATUS","locale":"en-CA" <co id="CO9-10"/>\r
- }\r
- }]</programlisting>\r
+<programlisting language="bash" linenumbering="unnumbered">\r
+# HTTP response broken up over multiple lines for legibility\r
+[{"__c":"osrfMessage","__p": <co id="CO9-1"/>\r
+ {"threadTrace":0, "payload": <co id="CO9-2"/>\r
+ {"__c":"osrfResult","__p": <co id="CO9-3"/>\r
+ {"status":"OK","content":"raboof","statusCode":200} <co id="CO9-4"/>\r
+ },"type":"RESULT","locale":"en-CA" <co id="CO9-5"/>\r
+ }\r
+},\r
+{"__c":"osrfMessage","__p": <co id="CO9-6"/>\r
+ {"threadTrace":0,"payload": <co id="CO9-7"/>\r
+ {"__c":"osrfConnectStatus","__p": <co id="CO9-8"/>\r
+ {"status":"Request Complete","statusCode":205}<co id="CO9-9"/>\r
+ },"type":"STATUS","locale":"en-CA" <co id="CO9-10"/>\r
+ }\r
+}]\r
+</programlisting>\r
</para></formalpara>\r
<calloutlist>\r
<callout arearefs="CO9-1">\r
the advantages of locale support and tracing the request from the requester\r
through the listener and responder (drone).</simpara>\r
<formalpara><title>A request for opensrf.simple-text.reverse("foobar"):</title><para>\r
- <programlisting language="xml" linenumbering="unnumbered"><message from='router@private.localhost/opensrf.simple-text'\r
- to='opensrf@private.localhost/opensrf.simple-text_listener_at_localhost_6275'\r
- router_from='opensrf@private.localhost/_karmic_126678.3719_6288'\r
- router_to='' router_class='' router_command='' osrf_xid=''\r
- >\r
- <thread>1266781414.366573.12667814146288</thread>\r
- <body>\r
- [\r
- {"__c":"osrfMessage","__p":\r
- {"threadTrace":"1","locale":"en-US","type":"REQUEST","payload":\r
- {"__c":"osrfMethod","__p":\r
- {"method":"opensrf.simple-text.reverse","params":["foobar"]}\r
- }\r
- }\r
- }\r
- ]\r
- </body>\r
- </message></programlisting>\r
+<programlisting language="xml" linenumbering="unnumbered">\r
+<message from='router@private.localhost/opensrf.simple-text'\r
+ to='opensrf@private.localhost/opensrf.simple-text_listener_at_localhost_6275'\r
+ router_from='opensrf@private.localhost/_karmic_126678.3719_6288'\r
+ router_to='' router_class='' router_command='' osrf_xid=''\r
+>\r
+ <thread>1266781414.366573.12667814146288</thread>\r
+ <body>\r
+[\r
+ {"__c":"osrfMessage","__p":\r
+ {"threadTrace":"1","locale":"en-US","type":"REQUEST","payload":\r
+ {"__c":"osrfMethod","__p":\r
+ {"method":"opensrf.simple-text.reverse","params":["foobar"]}\r
+ }\r
+ }\r
+ }\r
+]\r
+ </body>\r
+</message>\r
+</programlisting>\r
</para></formalpara>\r
<formalpara><title>A response from opensrf.simple-text.reverse("foobar")</title><para>\r
- <programlisting language="xml" linenumbering="unnumbered"><message from='opensrf@private.localhost/opensrf.simple-text_drone_at_localhost_6285'\r
- to='opensrf@private.localhost/_karmic_126678.3719_6288'\r
- router_command='' router_class='' osrf_xid=''\r
- >\r
- <thread>1266781414.366573.12667814146288</thread>\r
- <body>\r
- [\r
- {"__c":"osrfMessage","__p":\r
- {"threadTrace":"1","payload":\r
- {"__c":"osrfResult","__p":\r
- {"status":"OK","content":"raboof","statusCode":200}\r
- } ,"type":"RESULT","locale":"en-US"}\r
- },\r
- {"__c":"osrfMessage","__p":\r
- {"threadTrace":"1","payload":\r
- {"__c":"osrfConnectStatus","__p":\r
- {"status":"Request Complete","statusCode":205}\r
- },"type":"STATUS","locale":"en-US"}\r
- }\r
- ]\r
- </body>\r
- </message></programlisting>\r
+<programlisting language="xml" linenumbering="unnumbered">\r
+<message from='opensrf@private.localhost/opensrf.simple-text_drone_at_localhost_6285'\r
+ to='opensrf@private.localhost/_karmic_126678.3719_6288'\r
+ router_command='' router_class='' osrf_xid=''\r
+>\r
+ <thread>1266781414.366573.12667814146288</thread>\r
+ <body>\r
+[\r
+ {"__c":"osrfMessage","__p":\r
+ {"threadTrace":"1","payload":\r
+ {"__c":"osrfResult","__p":\r
+ {"status":"OK","content":"raboof","statusCode":200}\r
+ } ,"type":"RESULT","locale":"en-US"}\r
+ },\r
+ {"__c":"osrfMessage","__p":\r
+ {"threadTrace":"1","payload":\r
+ {"__c":"osrfConnectStatus","__p":\r
+ {"status":"Request Complete","statusCode":205}\r
+ },"type":"STATUS","locale":"en-US"}\r
+ }\r
+]\r
+ </body>\r
+</message>\r
+</programlisting>\r
</para></formalpara>\r
<simpara>The content of the <literal><body></literal> element of the OpenSRF request and result should\r
look familiar; they match the structure of the <link linkend="OpenSRFOverHTTP">OpenSRF over HTTP examples</link> that we previously dissected.</simpara>\r
</listitem>\r
<listitem>\r
<simpara>\r
- The optional <literal>stream</literal> member, if set to any value, specifies that the method supports returning multiple values from a single call to subsequent requests. OpenSRF automatically creates a corresponding method with ".atomic" appended to its name that returns the complete set of results in a single request. Streaming methods are useful if you are returning hundreds of records and want to act on the results as they return.\r
+ The optional <literal>stream</literal> member, if set to any value, specifies that the method supports returning multiple values from a single call to \r
+ subsequent requests. OpenSRF automatically creates a corresponding method with ".atomic" appended to its name that returns the complete set of results in a \r
+ single request. Streaming methods are useful if you are returning hundreds of records and want to act on the results as they return.\r
</simpara>\r
</listitem>\r
<listitem>\r
</listitem>\r
<listitem>\r
<simpara>\r
- The <literal>params</literal> member of the <literal>signature</literal> hash is an array of hashes in which each array element describes the corresponding method argument in order.\r
+ The <literal>params</literal> member of the <literal>signature</literal> hash is an array of hashes in which each array element describes the corresponding method \r
+ argument in order.\r
</simpara>\r
<itemizedlist>\r
<listitem>\r
</listitem>\r
<listitem>\r
<simpara>\r
- The <literal>type</literal> member of the <literal>return</literal> hash specifies the data type of the return value: for example, string, integer, boolean, number, array, or hash.\r
+ The <literal>type</literal> member of the <literal>return</literal> hash specifies the data type of the return value: for example, string, integer, boolean, number, \r
+ array, or hash.\r
</simpara>\r
</listitem>\r
</itemizedlist>\r
<itemizedlist>\r
<listitem>\r
<simpara>\r
- The <literal>open-ils.actor</literal> service supports common tasks for working with user\r
+ The <systemitem class="service">open-ils.actor</systemitem> service supports common tasks for working with user\r
accounts and libraries.\r
</simpara>\r
</listitem>\r
<listitem>\r
<simpara>\r
- The <literal>open-ils.auth</literal> service supports authentication of Evergreen users.\r
+ The <systemitem class="service">open-ils.auth</systemitem> service supports authentication of Evergreen users.\r
</simpara>\r
</listitem>\r
<listitem>\r
<simpara>\r
- The <literal>open-ils.booking</literal> service supports the management of reservations\r
+ The <systemitem class="service">open-ils.booking</systemitem> service supports the management of reservations\r
for bookable items.\r
</simpara>\r
</listitem>\r
<listitem>\r
<simpara>\r
- The <literal>open-ils.cat</literal> service supports common cataloging tasks, such as\r
+ The <systemitem class="service">open-ils.cat</systemitem> service supports common cataloging tasks, such as\r
creating, modifying, and merging bibliographic and authority records.\r
</simpara>\r
</listitem>\r
<listitem>\r
<simpara>\r
- The <literal>open-ils.circ</literal> service supports circulation tasks such as checking\r
+ The <systemitem class="service">open-ils.circ</systemitem> service supports circulation tasks such as checking\r
out items and calculating due dates.\r
</simpara>\r
</listitem>\r
<listitem>\r
<simpara>\r
- The <literal>open-ils.collections</literal> service supports tasks that assist collections\r
+ The <systemitem class="service">open-ils.collections</systemitem> service supports tasks that assist collections\r
agencies in contacting users with outstanding fines above a certain\r
threshold.\r
</simpara>\r
</listitem>\r
<listitem>\r
<simpara>\r
- The <literal>open-ils.cstore</literal> private service supports unrestricted access to\r
+ The <systemitem class="service">open-ils.cstore</systemitem> private service supports unrestricted access to\r
Evergreen fieldmapper objects.\r
</simpara>\r
</listitem>\r
<listitem>\r
<simpara>\r
- The <literal>open-ils.ingest</literal> private service supports tasks for importing\r
+ The <systemitem class="service">open-ils.ingest</systemitem> private service supports tasks for importing\r
data such as bibliographic and authority records.\r
</simpara>\r
</listitem>\r
<listitem>\r
<simpara>\r
- The <literal>open-ils.pcrud</literal> service supports permission-based access to Evergreen\r
+ The <systemitem class="service">open-ils.pcrud</systemitem> service supports permission-based access to Evergreen\r
fieldmapper objects.\r
</simpara>\r
</listitem>\r
<listitem>\r
<simpara>\r
- The <literal>open-ils.penalty</literal> penalty service supports the calculation of\r
+ The <systemitem class="service">open-ils.penalty</systemitem> penalty service supports the calculation of\r
penalties for users, such as being blocked from further borrowing, for\r
conditions such as having too many items checked out or too many unpaid\r
fines.\r
</listitem>\r
<listitem>\r
<simpara>\r
- The <literal>open-ils.reporter</literal> service supports the creation and scheduling of\r
+ The <systemitem class="service">open-ils.reporter</systemitem> service supports the creation and scheduling of\r
reports.\r
</simpara>\r
</listitem>\r
<listitem>\r
<simpara>\r
- The <literal>open-ils.reporter-store</literal> private service supports access to Evergreen\r
+ The <systemitem class="service">open-ils.reporter-store</systemitem> private service supports access to Evergreen\r
fieldmapper objects for the reporting service.\r
</simpara>\r
</listitem>\r
<listitem>\r
<simpara>\r
- The <literal>open-ils.search</literal> service supports searching across bibliographic\r
+ The <systemitem class="service">open-ils.search</systemitem> service supports searching across bibliographic\r
records, authority records, serial records, Z39.50 sources, and ZIP codes.\r
</simpara>\r
</listitem>\r
<listitem>\r
<simpara>\r
- The <literal>open-ils.storage</literal> private service supports a deprecated method of\r
+ The <systemitem class="service">open-ils.storage</systemitem> private service supports a deprecated method of\r
providing access to Evergreen fieldmapper objects. Implemented in Perl,\r
this service has largely been replaced by the much faster C-based\r
<literal>open-ils.cstore</literal> service.\r
</listitem>\r
<listitem>\r
<simpara>\r
- The <literal>open-ils.supercat</literal> service supports transforms of MARC records into\r
+ The <systemitem class="service">open-ils.supercat</systemitem> service supports transforms of MARC records into\r
other formats, such as MODS, as well as providing Atom and RSS feeds and\r
SRU access.\r
</simpara>\r
</listitem>\r
<listitem>\r
<simpara>\r
- The <literal>open-ils.trigger</literal> private service supports event-based triggers for\r
+ The <systemitem class="service">open-ils.trigger</systemitem> private service supports event-based triggers for\r
actions such as overdue and holds available notification emails.\r
</simpara>\r
</listitem>\r
<listitem>\r
<simpara>\r
- The <literal>open-ils.vandelay</literal> service supports the import and export of batches of\r
+ The <systemitem class="service">open-ils.vandelay</systemitem> service supports the import and export of batches of\r
bibliographic and authority records.\r
</simpara>\r
</listitem>\r
</itemizedlist>\r
- <simpara>Of some interest is that the <literal>open-ils.reporter-store</literal> and <literal>open-ils.cstore</literal>\r
+ <simpara>Of some interest is that the <systemitem class="service">open-ils.reporter-store</systemitem> and <systemitem class="service">open-ils.cstore</systemitem>\r
services have identical implementations. Surfacing them as separate services\r
enables a deployer of Evergreen to ensure that the reporting service does not\r
- interfere with the performance-critical <literal>open-ils.cstore</literal> service. One can also\r
+ interfere with the performance-critical <systemitem class="service">open-ils.cstore</systemitem> service. One can also\r
direct the reporting service to a read-only database replica to, again, avoid\r
- interference with <literal>open-ils.cstore</literal> which must write to the master database.</simpara>\r
+ interference with <systemitem class="service">open-ils.cstore</systemitem> which must write to the master database.</simpara>\r
<simpara>There are only a few significant services that are not built on OpenSRF in\r
Evergreen 1.6.0, such as the SIP and Z39.50 servers. These services implement\r
different protocols and build on existing daemon architectures (Simple2ZOOM\r
-<?xml version="1.0" encoding="utf-8"?>
-<chapter xml:id="Customizing_OPAC" xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="EN"
- xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:xlink="http://www.w3.org/1999/xlink">
- <info>
- <title>Customizing the OPAC</title>
- </info>
- <para>While Evergreen is ready to go out of the box, libraries will want to customize Evergreen with their own color scheme, logos and layout. This chapter will explain how to
- customize Evergreen to meet the needs of your users. For these task some knowledge of html and css is required. Many of these instructions assume a default installation of
- Evergreen using the default file locations.</para>
- <note>
- <para>Be sure to save a backup copy of all files you edit in a location other than /openils/var/web/opac/ as files here could be overwritten when you upgrade your copy
- of Evergreen.</para>
- </note>
- <section xml:id="ColorScheme">
- <title>Change the Color Scheme</title>
- <para>To change the color scheme of the default Evergreen skin, edit <filename>/openils/var/web/opac/theme/default/css/colors.css</filename>. From this one file you can
- change the 4 base color scheme as well as colors of specific elements.
- </para>
- <para>You can also create alternate themes for your users.</para>
- <procedure>
- <step>
- <para>Copy the css folder and its contents from the example alternate theme <filename>/openils/var/web/opac/theme/reddish/</filename>
- to a new folder <filename>/openils/var/web/opac/theme/<emphasis>[your new theme]</emphasis>/</filename>.</para>
- </step>
- <step>
- <para>Edit /openils/var/web/opac/theme/<emphasis>[your new theme]</emphasis>/css/colors.css to use the colors you want.</para>
- </step>
- <step>
- <para>Link to your new style sheet by adding the following to <filename>/openils/var/web/opac/skin/default/xml/common/css_common.xml</filename>.</para>
- <screen><link type='text/css'</screen>
- <screen>rel="alternate stylesheet"</screen>
- <screen>title='&opac.style.yourtheme;'</screen>
- <screen>href="<!--#echo var='OILS_THEME_BASE'-->/yourtheme/css/colors.css"</screen>
- <screen>name='Default' csstype='color'/></screen>
- </step>
- <step>
- <para> Give your new theme a name users can select by adding the following to <filename>/openils/var/web/opac/locale/<emphasis>
- [your locale]</emphasis>/opac.dtd</filename>.</para>
- <screen><!ENTITY opac.style.yourtheme "YourTheme"></screen>
- </step>
- </procedure>
- </section>
- <section xml:id="customizing_opac_text">
- <title>customizing Opac Text and Labels</title>
- <para>To change text and links used throughout the OPAC, edit the following files:</para>
- <itemizedlist>
- <listitem><filename>/openils/var/web/opac/locale/<emphasis>[your locale]</emphasis>/lang.dtd</filename></listitem>
- <listitem><filename>/openils/var/web/opac/locale/<emphasis>[your locale]</emphasis>/opac.dtd</filename></listitem>
- </itemizedlist>
- <tip>
- <para>A better way to customize OPAC text is to create custom <emphasis>dtd</emphasis> files for your lang and opac customizations and then add a include
- statement above the default dtd files.</para>
- <screen><!DOCTYPE html PUBLIC</screen>
- <screen>"-//W3C//DTD XHTML 1.0 Transitional//EN"</screen>
- <screen>"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" [</screen>
- <screen><!--#include virtual="/opac/locale/${locale}/custom_opac.dtd"--></screen>
- <screen><!--#include virtual="/opac/locale/${locale}/opac.dtd"--></screen>
- <screen>]></screen>
- <para>position is important here. The first/top included <emphasis>dtd</emphasis> files will take precedence over the subsequent dtd includes.</para>
- </tip>
- <para>While it is possible to add text to the xml files itself, it is a good practice to use the DTD file to control the text and refer to the DTD elements in the xml/html code.</para>
- <para>For example, the footer.xml file has this code to generate a copyright statement:</para>
- <screen><div id='copyright_text'></screen>
- <screen><span>&footer.copyright;</span></screen>
- <para>The included <filename>opac.dtd</filename> file in the en-US locale direcotry has this setting for &footer.copyright text:</para>
- <screen><!ENTITY footer.copyright "Copyright © 2006-2010 Georgia Public Library Service, and others"></screen>
- </section>
- <section xml:id="Logo_Images">
- <title>Logo Images</title>
- <para>To change the logos used by default to your own logos, replace the following files with images of your own, appropriately sized. </para>
- <itemizedlist>
- <listitem>Large main logo:<filename>/openils/var/web/opac/images/main_logo.jpg</filename></listitem>
- <listitem>Small logo:<filename>/openils/var/web/opac/images/small_logo.jpg</filename></listitem>
- </itemizedlist>
- </section>
- <section xml:id="AddedContent">
- <title>Added Content</title>
- <para>By default Evergreen includes customizable <quote>Added Content</quote> features to enhance the OPAC experience for your user. These features include Amazon book covers
- and Google books searching. These features can be turned off or custimized.</para>
- <simplesect xml:id="bookcovers">
- <title>Book Covers</title>
- <para>The default install of Evergreen includes Amazon book covers. The settings for this are controlled by the <added_content> section of
- <filename>/opneils/conf/opensrf.xml</filename>. Here are the key elements of this configuration:</para>
- <screen><module>OpenILS::WWW::AddedContent::Amazon</module></screen>
- <para>This calls the Amazon perl module. If you wish to link to a different book cover service other than Amazon, you must create a new perl module and refer to it here.
- You will also need to change other settings accordingly. There are some available book cover perl modules available in
- <ulink url="http://svn.open-ils.org/trac/ILS/browser/trunk/Open-ILS/src/perlmods/OpenILS/WWW/AddedContent">trunk</ulink></para>
- <screen><base_url>http://images.amazon.com/images/P/</base_url></screen>
- <para>Base URL for Amazon added content fetching. This URL may need to be shortened when new (read: non-image) content fetching
- capabilities are added.</para>
- <screen><timeout>1</timeout></screen>
- <para>Max number of seconds to wait for an added content request to return data. Data not returned within the timeout is considered a failure.</para>
- <screen><retry_timeout>600</retry_timeout></screen>
- <para>After added content lookups have been disabled due to too many lookup failures, this is the amount of time to wait before we try again.</para>
- <screen><max_errors>15</max_errors></screen>
- <para>Maximum number of consecutive lookup errors a given process can live before added content lookups are disabled for everyone.</para>
- <screen><userid>MY_USER_ID</userid></screen>
- <para>If a userid is required to access the added content.</para>
- </simplesect>
- <simplesect xml:id="googlebookslink">
- <title>Google Books Link</title>
- <para>The results page will display a <quote>Browse in Google Books Search</quote> link for items in the results page which have corresponding entries in Google Books.
- This will link to Google Books content including table of contents and complete versions of the work if it exists in Google Books. Items not in Google Books will not
- display a link. This feature can be turned off by changing the googleBooksLink variable setting to <quote>false</quote>in the file
- <filename>/openils/var/web/opac/skin/default/js/result_common.js</filename>. By default, this feature is activated. </para>
- </simplesect>
- </section>
- <section xml:id="resultspage">
- <title>Customizing the Results Page</title>
- <para>The results page is extremely customizable and allows some built in features to be activated with some simple edits or more advanced customizations can be done by more
- experienced web developers.</para>
- <para>There are several critical files to edit if you wish to customize the results page:</para>
- <itemizedlist>
- <listitem><filename>/openils/var/web/opac/skin/default/js/result_common.js</filename> - This file controls the javascript for the top level elements on the results
- page and should only be edited by experienced web developers except for the <link linkend='googlebookslink'>google books link setting</link> mentioned perviously.</listitem>
- <listitem><filename>/openils/var/web/opac/skin/default/js/rresult.js</filename> - Has some good controls of results page settings at the top of this file but
- requires web development skills for editing this file.</listitem>
- <listitem><filename>/openils/var/web/opac/skin/default/xml/result/rresult_table.xml</filename> - This controls the layout of the items table on results page.</listitem>
- </itemizedlist>
- </section>
- <section xml:id="deatailspage">
- <title>Customizing the Details Page</title>
- <para>There are many options when customizing the details page in Evergreen. The default settings are effective for most libraries, but it is important to understand the full potential
- of Evergreen when displaying the details of items.</para>
- <para>Some quick features can be turned on and off by changing variable values in the file <filename>/openils/var/web/opac/skin/default/js/rdedail.js</filename>.
- You will notice the section at the top of this file called <quote>Per-skin configuration settings</quote>. Changing setting in this section can control several features includuing
- limiting results to local only or showing copy location or displaying serial holdings. Form this section you can also enable refworks and set the Refworks host URL.</para>
- <para>Some copy level details settings can be turned on and off from <filename>/openils/var/web/opac/skin/default/js/copy_details.js</filename> including displaying certain fields
- such as due date in the OPAC.</para>
- <para>An important file is the <filename>/openils/var/web/opac/skin/default/xml/rdetail/rdetail_summary.xml</filename> file. This file allows you to control which field to display in
- the details summary of the record. The new <link linkend='googlebookslink'>BibTemnplate</link> feature makes this file even more powerful by allowing you to display any marc fields
- with a variey of formatting options.</para>
- <para>The <filename>/openils/var/web/opac/skin/default/xml/rdetail/rdetail_copyinfo.xml</filename> file allows you to format the display of the copy information.</para>
- </section>
- <section xml:id="BibTemplate">
- <title>BibTemplate</title>
- <para>BibTemplate is an Evergreen-custom Dojo module which can be used to retrieve and format XML data served by the Evergreen unAPI service. unAPI is a protocol for requesting known objects in specific formats, and Evergreen uses this to supply data – bibliographic records, metarecords, monograph holdings information, Located URIs, and more to come –
- in many different formats from MARCXML to MODS to custom XML applications.</para>
- <para>Managing the display of information from raw XML can be difficult, and the purpose of BibTemplate is to make this simpler, as well as move the display closer to the
- client and away from the source data. This is good from a separation-of-responsibilities perspective, and also makes it easier to contain and control local customization.</para>
- <para>BibTemplate supports the foloowing Evergreen metadata formats:</para>
- <itemizedlist>
- <listitem>MARCXML - datatype='marcxml-full' (default)</listitem>
- <listitem>MODS 3.3: datatype='mods33'</listitem>
- <listitem>Dublin Core: datatype='rdf_dc'</listitem>
- <listitem>FGDC: datatype='fgdc'</listitem>
- </itemizedlist>
- <simplesect>
- <title>HTML API</title>
- <para>BibTemplate follows the Dojo convention of adding attributes to existing (X)HTML in order to progressively change its behavior. The 1.6.0 HTML API consists of a
- set of attributes that are added to existing OPAC markup, and fall into two classes:</para>
- <itemizedlist>
- <listitem> The slot marker – Elements that denote the location of bibliographic data to insert.</listitem>
- <listitem>The slot formatter – Elements that specify how the named data should be formatted for display.</listitem>
- </itemizedlist>
- </simplesect>
- <simplesect>
- <title>Slot Marker</title>
- <para>A slot marker is any displayable HTML element that has a type attribute with a value starting with opac/slot-data. This element will become the container
- for the formatted data. A slot marker is required in order to retrieve, format and display data using BibTemplate. A slot marker must also have an
- attribute called query containing a CSS3 selector. This selector is applied to the XML returned by the unAPI service in order to gather the specific XML
- Nodes that should be considered for formatting.</para>
- <para>The slot marker can also specify the format of the data to be returned from the unAPI service. This can be specified by adding +{format} to the type
- attribute, as in opac/slot-data+mods33-full. The default data format is marcxml-uri, which is an augmented MARCXML record containing Located URI information
- and unAPI links.</para>
- <para>Example of a slot marker:</para>
- <screen><p type='opac/slot-data' query='datafield[tag=245]'></p></screen>
- <para>Most useful attribute match operators include:</para>
- <itemizedlist>
- <listitem> datafield[tag=245] - exact match</listitem>
- <listitem>datafield[tag^=65] - match start of value</listitem>
- </itemizedlist>
- <tip><para>Selectors always narrow, so select broadly and iterate through the NodeList</para></tip>
- </simplesect>
- <simplesect>
- <title>Slot Formatter</title>
- <para>A slot formatter is any invisible HTML element which has a type attribute with the value of opac/slot-format. (NOTE: before 1.6.0.4, only <script>
- elements were supported, though this restriction is now removed to support Internet Explorer.) Only one slot formatter element is allowed in each slot. The text contents
- of this element are wrapped in a JavaScript function and run for each node returned by the query CSS3 selector specified on the slot marker. This function is passed
- one argument, called item, which an XML Node captured by the selector. This function should return HTML text. The output for all runs of the slot formatter is
- concatenated into a single string and used to replace the contents of the slot marker.</para>
- <para>The slot formatter is optional, and if not supplied BibTemplate will create a simple function which extracts and returns the text content of the XML Nodes
- specified in the CSS3 selector.</para>
- <para>Example of a slot formatter:</para>
- <programlisting>
- <td class='rdetail_item' id='rdetail_online' type='opac/slot-data' query='volumes volume uris uri' join=", ">
- <script type='opac/slot-format'><![CDATA[
- var link = '<a href="' + item.getAttribute('href') + '">' + item.getAttribute('label') + '</a>';
- if (item.getAttribute('use_restriction'))
- link += ' (Use restriction: ' + item.getAttribute('use_restriction') + ')';
- return link;
- ]]></script>
- </td>
- </programlisting>
- </simplesect>
- <simplesect>
- <title>JavaScript API</title>
- <para>In order for BibTemplate to find the slot markers and invoke the slot formatters JavaScript renderer must be instantiated and called. This must be done
- for each record that is to contribute to a pages display. The API for this is simple and straight-forward:</para>
- <para>The slot formatter is optional, and if not supplied BibTemplate will create a simple function which extracts and returns the text content of the XML Nodes
- specified in the CSS3 selector.</para>
- <para>Example of a slot formatter:</para>
- <programlisting>
- dojo.require('openils.BibTemplate'); // Tell Dojo to load BibTemplate, if it is not already loaded
-
- // Create a renderer supplying the record id and the short name of the org unit, if known, and call the render() method
- new openils.BibTemplate({ record : new CGI().param('r'), org_unit : here.shortname() }).render();
-
- </programlisting>
- <para>The argument hash supplied to the new openils.BibTemplate() constructor can have the following properties:</para>
- <itemizedlist>
- <listitem>record – The bibliographic record ID.</listitem>
- <listitem>org_unit – The relevant Organizational Unit, used to restrict holdings scope as on a search result or record detail page.</listitem>
- <listitem>root – The root element within the web page that BibTemplate should search for slot markers</listitem>
- </itemizedlist>
- </simplesect>
- <simplesect>
- <title>BibTemplate Examples</title>
- <para>This is all that we had to add to display the contents of an arbitrary MARC field:</para>
- <programlisting>
- <tr>
- <td>Bibliography note</td>
- <td type='opac/slot-data' query='datafield[tag=504]'></td>
- </tr>
- </programlisting>
- <note><para>If multiple fields match, they are displayed on consecutive lines within the same left-hand cell.</para></note>
- <para>To display a specific MARC subfield, add that subfield to the query attribute.</para>
- <para>For example, subfield $a is the only user-oriented subfield in field 586 (Awards Note)</para>
- <programlisting>
- <tr>
- <td>Awards note</td>
- <td type='opac/slot-data' query='datafield[tag=586] subfield[code=a]'></td>
- </tr>
- </programlisting>
- <para>Hide empty rows by default, and display them only if they have content:</para>
- <programlisting>
- <tr class='hide_me' id='tag504'>
- <td>Bibliographic note</td>
- <td type='opac/slot-data' query='datafield[tag=504]'>
- <script type='opac/slot-format'><![CDATA[
- dojo.query('#tag504').removeClass('hide_me');
- return '<span>' + dojox.data.dom.textContent(item) +
- '</span><br/>';
- ]]></script>
- </td></tr>
- </programlisting>
- <itemizedlist>
- <listitem><![CDATA[ ... ]]> tells Evergreen Web server to treat the contents as literal <quote>character data</quote> -
- avoids hilarity of entity substitution</listitem>
- <listitem><script type='opac/slot-format'>...</script>, contained within an 'opac/slot-data' element, receives a variable named item
- containing the results of the query (a NodeList)</listitem>
- </itemizedlist>
- <para>Suppressing a subfield:</para>
- <programlisting>
- <tr class='hide_me' id='tag700'>
- <td>Additional authors</td>
- <td type='opac/slot-data' query='datafield[tag=700]'>
- <script type='opac/slot-format'><![CDATA[
- dojo.query('#tag700').removeClass('hide_me');
- var text = '';
- var list = dojo.query('subfield:not([code=4])', item);
- for (var i =0; i < list.length; i++) {
- text += dojox.data.dom.textContent(list[i]) + ' ';
- }
- return '<span>' + text + '</span><br/>';
- ]]></script>
- </td></tr>
- </programlisting>
- </simplesect>
- </section>
-</chapter>
-
+<?xml version="1.0" encoding="utf-8"?>\r
+<chapter xml:id="Customizing_OPAC" xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="EN"\r
+ xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:xlink="http://www.w3.org/1999/xlink">\r
+ <info>\r
+ <title>Customizing the OPAC</title>\r
+ </info>\r
+ <para>While Evergreen is ready to go out of the box, libraries will want to customize Evergreen with their own color scheme, logos and layout. This chapter will explain how to \r
+ customize Evergreen to meet the needs of your users. For these task some knowledge of <systemitem>html</systemitem> and <systemitem>css</systemitem> is required. Many of these \r
+ instructions assume aninstallation of Evergreen using the default file locations.</para>\r
+ <note>\r
+ <para>Be sure to save a backup copy of all files you edit in a location other than <filename class="directory">/openils/var/web/opac/</filename> as files here could be \r
+ overwritten when you upgrade your copy of Evergreen.</para>\r
+ </note> \r
+ <section xml:id="ColorScheme">\r
+ <title>Change the Color Scheme</title>\r
+ <para>To change the color scheme of the default Evergreen skin, edit <filename>/openils/var/web/opac/theme/default/css/colors.css</filename>. From this one file you can \r
+ change the 4 base color scheme as well as colors of specific elements. \r
+ </para> \r
+ <para>You can also create alternate themes for your users.</para> \r
+ <procedure>\r
+ <step>\r
+ <para>Copy the css folder and its contents from the example alternate theme <filename>/openils/var/web/opac/theme/reddish/</filename> \r
+ to a new folder <filename>/openils/var/web/opac/theme/<emphasis>[your new theme]</emphasis>/</filename>.</para>\r
+ </step> \r
+ <step>\r
+ <para>Edit <filename>/openils/var/web/opac/theme/<emphasis role="bold">[your new theme]</emphasis>/css/colors.css</filename> to use the colors you want.</para>\r
+ </step>\r
+ <step>\r
+ <para>Link to your new style sheet by adding the following to <filename>/openils/var/web/opac/skin/default/xml/common/css_common.xml</filename>.</para>\r
+<programlisting language="xml">\r
+<link type='text/css'\r
+rel="alternate stylesheet" \r
+title='&opac.style.yourtheme;' \r
+href="<!--#echo var='OILS_THEME_BASE'-->/yourtheme/css/colors.css" \r
+name='Default' csstype='color'/>\r
+</programlisting> \r
+ </step> \r
+ <step>\r
+ <para> Give your new theme a name users can select by adding the following to <filename>/openils/var/web/opac/locale/<emphasis role="bold">\r
+ [your locale]</emphasis>/opac.dtd</filename>.</para>\r
+ <screen><!ENTITY opac.style.yourtheme "YourTheme"></screen> \r
+ </step> \r
+ </procedure> \r
+ </section>\r
+ <section xml:id="customizing_opac_text">\r
+ <title>customizing Opac Text and Labels</title>\r
+ <para>To change text and links used throughout the OPAC, edit the following files:</para>\r
+ <itemizedlist>\r
+ <listitem><filename>/openils/var/web/opac/locale/<emphasis role="bold">[your locale]</emphasis>/lang.dtd</filename></listitem>\r
+ <listitem><filename>/openils/var/web/opac/locale/<emphasis role="bold">[your locale]</emphasis>/opac.dtd</filename></listitem>\r
+ </itemizedlist>\r
+ <tip>\r
+ <para>A better way to customize OPAC text is to create custom dtd files for your lang and opac customizations and then add a include \r
+ statement above the default dtd files.</para>\r
+ <programlisting>\r
+ <!DOCTYPE html PUBLIC \r
+ "-//W3C//DTD XHTML 1.0 Transitional//EN"\r
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" [\r
+ <!--#include virtual="/opac/locale/${locale}/custom_opac.dtd"-->\r
+ <!--#include virtual="/opac/locale/${locale}/opac.dtd"-->\r
+ ]>\r
+ </programlisting>\r
+ <para>position is important here. The first/top included dtd files will take precedence over the subsequent dtd includes.</para> \r
+ </tip>\r
+ <para>While it is possible to add text to the xml files itself, it is a good practice to use the DTD file to control the text and refer to the DTD elements in the xml/html code.</para> \r
+ <para>For example, the <filename>footer.xml</filename> file has this code to generate a copyright statement:</para>\r
+<programlisting>\r
+<div id='copyright_text'>\r
+<span>&footer.copyright;</span>\r
+</programlisting>\r
+ <para>The included <filename>opac.dtd</filename> file in the en-US locale direcotry has this setting for &footer.copyright text:</para> \r
+ <programlisting><!ENTITY footer.copyright "Copyright © 2006-2010 Georgia Public Library Service, and others"></programlisting>\r
+ </section>\r
+ <section xml:id="Logo_Images">\r
+ <title>Logo Images</title>\r
+ <para>To change the logos used by default to your own logos, replace the following files with images of your own, appropriately sized. </para>\r
+ <itemizedlist>\r
+ <listitem>Large main logo:<filename>/openils/var/web/opac/images/main_logo.jpg</filename></listitem>\r
+ <listitem>Small logo:<filename>/openils/var/web/opac/images/small_logo.jpg</filename></listitem>\r
+ </itemizedlist> \r
+ </section>\r
+ <section xml:id="AddedContent">\r
+ <title>Added Content</title>\r
+ <para>By default Evergreen includes customizable <quote>Added Content</quote> features to enhance the OPAC experience for your user. These features include Amazon book covers \r
+ and Google books searching. These features can be turned off or custimized.</para>\r
+ <simplesect xml:id="bookcovers">\r
+ <title>Book Covers</title>\r
+ <para>The default install of Evergreen includes Amazon book covers. The settings for this are controlled by the <added_content> section of \r
+ <filename>/opneils/conf/opensrf.xml</filename>. Here are the key elements of this configuration:</para> \r
+ <programlisting><module>OpenILS::WWW::AddedContent::Amazon</module></programlisting>\r
+ <para>This calls the Amazon perl module. If you wish to link to a different book cover service other than Amazon, you must create a new perl module and refer to it here. \r
+ You will also need to change other settings accordingly. There are some available book cover perl modules available in \r
+ <ulink url="http://svn.open-ils.org/trac/ILS/browser/trunk/Open-ILS/src/perlmods/OpenILS/WWW/AddedContent">trunk</ulink></para>\r
+ <programlisting><base_url>http://images.amazon.com/images/P/</base_url></programlisting>\r
+ <para>Base URL for Amazon added content fetching. This URL may need to be shortened when new (read: non-image) content fetching \r
+ capabilities are added.</para> \r
+ <programlisting><timeout>1</timeout></programlisting>\r
+ <para>Max number of seconds to wait for an added content request to return data. Data not returned within the timeout is considered a failure.</para> \r
+ <programlisting><retry_timeout>600</retry_timeout></programlisting>\r
+ <para>After added content lookups have been disabled due to too many lookup failures, this is the amount of time to wait before we try again.</para>\r
+ <programlisting><max_errors>15</max_errors></programlisting>\r
+ <para>Maximum number of consecutive lookup errors a given process can live before added content lookups are disabled for everyone.</para> \r
+ <programlisting><userid>MY_USER_ID</userid></programlisting>\r
+ <para>If a userid is required to access the added content.</para>\r
+ </simplesect>\r
+ <simplesect xml:id="googlebookslink">\r
+ <title>Google Books Link</title>\r
+ <para>The results page will display a <emphasis role="bold">Browse in Google Books Search</emphasis> link for items in the results page which have corresponding entries\r
+ in <systemitem class="resource">Google Books</systemitem>. \r
+ This will link to Google Books content including table of contents and complete versions of the work if it exists in Google Books. Items not in Google Books will not \r
+ display a link. This feature can be turned off by changing the googleBooksLink variable setting to <emphasis>false</emphasis> in the file \r
+ <filename>/openils/var/web/opac/skin/default/js/result_common.js</filename>. By default, this feature is activated. </para> \r
+ </simplesect> \r
+ </section>\r
+ <section xml:id="resultspage">\r
+ <title>Customizing the Results Page</title>\r
+ <para>The results page is extremely customizable and allows some built in features to be activated with some simple edits or more advanced customizations can be done by more \r
+ experienced web developers.</para>\r
+ <para>There are several critical files to edit if you wish to customize the results page:</para>\r
+ <itemizedlist>\r
+ <listitem><filename>/openils/var/web/opac/skin/default/js/result_common.js</filename> - This file controls the javascript for the top level elements on the results \r
+ page and should only be edited by experienced web developers except for the <link linkend='googlebookslink'>google books link setting</link> mentioned perviously.</listitem>\r
+ <listitem><filename>/openils/var/web/opac/skin/default/js/rresult.js</filename> - Has some good controls of results page settings at the top of this file but \r
+ requires web development skills for editing this file.</listitem>\r
+ <listitem><filename>/openils/var/web/opac/skin/default/xml/result/rresult_table.xml</filename> - This controls the layout of the items table on results page.</listitem>\r
+ </itemizedlist> \r
+ </section>\r
+ <section xml:id="deatailspage">\r
+ <title>Customizing the Details Page</title>\r
+ <para>There are many options when customizing the details page in Evergreen. The default settings are effective for most libraries, but it is important to understand the full potential \r
+ of Evergreen when displaying the details of items.</para> \r
+ <para>Some quick features can be turned on and off by changing variable values in the file <filename>/openils/var/web/opac/skin/default/js/rdedail.js</filename>. \r
+ You will notice the section at the top of this file called <quote>Per-skin configuration settings</quote>. Changing setting in this section can control several features includuing \r
+ limiting results to local only or showing copy location or displaying serial holdings. Form this section you can also enable refworks and set the Refworks host URL.</para>\r
+ <para>Some copy level details settings can be turned on and off from <filename>/openils/var/web/opac/skin/default/js/copy_details.js</filename> including displaying certain fields \r
+ such as due date in the OPAC.</para>\r
+ <para>An important file is the <filename>/openils/var/web/opac/skin/default/xml/rdetail/rdetail_summary.xml</filename> file. This file allows you to control which field to display in \r
+ the details summary of the record. The new <link linkend='googlebookslink'>BibTemnplate</link> feature makes this file even more powerful by allowing you to display any marc fields \r
+ with a variey of formatting options.</para>\r
+ <para>The <filename>/openils/var/web/opac/skin/default/xml/rdetail/rdetail_copyinfo.xml</filename> file allows you to format the display of the copy information.</para> \r
+ </section> \r
+ <section xml:id="BibTemplate">\r
+ <title>BibTemplate</title><indexterm><primary>BibTemplate</primary></indexterm>\r
+ <para>BibTemplate is an Evergreen-custom Dojo module which can be used to retrieve and format XML data served by the Evergreen <systemitem class="protocol">unAPI</systemitem> service. <systemitem class="protocol">unAPI</systemitem> is a protocol for requesting known objects in specific formats, and Evergreen uses this to supply data – bibliographic records, \r
+ metarecords, monograph holdings information, Located URIs, and more to come – in many different formats from MARCXML to MODS to custom XML applications.</para>\r
+ <indexterm><primary>unAPI</primary></indexterm> <indexterm><primary>MARCXML</primary></indexterm><indexterm><primary>MODS</primary></indexterm>\r
+ <para>Managing the display of information from raw XML can be difficult, and the purpose of BibTemplate is to make this simpler, as well as move the display closer to the \r
+ client and away from the source data. This is good from a separation-of-responsibilities perspective, and also makes it easier to contain and control local customization.</para>\r
+ <para>BibTemplate supports the foloowing Evergreen metadata formats:</para>\r
+ <itemizedlist>\r
+ <listitem>MARCXML - datatype='marcxml-full' (default)</listitem> \r
+ <listitem>MODS 3.3: datatype='mods33'</listitem>\r
+ <listitem>Dublin Core: datatype='rdf_dc'</listitem><indexterm><primary>Dublin Core</primary></indexterm>\r
+ <listitem>FGDC: datatype='fgdc'</listitem><indexterm><primary>FGDC</primary></indexterm>\r
+ </itemizedlist> \r
+ <simplesect>\r
+ <title>HTML API</title>\r
+ <para>BibTemplate follows the Dojo convention of adding attributes to existing (X)HTML in order to progressively change its behavior. The 1.6.0 HTML API consists of a \r
+ set of attributes that are added to existing OPAC markup, and fall into two classes:</para><indexterm><primary>Dojo toolkit</primary></indexterm>\r
+ <itemizedlist>\r
+ <listitem> The slot marker – Elements that denote the location of bibliographic data to insert.</listitem>\r
+ <listitem>The slot formatter – Elements that specify how the named data should be formatted for display.</listitem>\r
+ </itemizedlist> \r
+ </simplesect>\r
+ <simplesect>\r
+ <title>Slot Marker</title><indexterm><primary>slot marker</primary></indexterm>\r
+ <para>A slot marker is any displayable HTML element that has a type attribute with a value starting with opac/slot-data. This element will become the container \r
+ for the formatted data. A slot marker is required in order to retrieve, format and display data using BibTemplate. A slot marker must also have an \r
+ attribute called query containing a CSS3 selector. This selector is applied to the XML returned by the unAPI service in order to gather the specific XML \r
+ Nodes that should be considered for formatting.</para><indexterm><primary>CSS3</primary></indexterm>\r
+ <para>The slot marker can also specify the format of the data to be returned from the unAPI service. This can be specified by adding +{format} to the type \r
+ attribute, as in opac/slot-data+mods33-full. The default data format is marcxml-uri, which is an augmented MARCXML record containing Located URI information \r
+ and unAPI links.</para>\r
+ <para>Example of a slot marker:</para>\r
+ <screen><p type='opac/slot-data' query='datafield[tag=245]'></p></screen>\r
+ <para>Most useful attribute match operators include:</para>\r
+ <itemizedlist>\r
+ <listitem> datafield[tag=245] - exact match</listitem>\r
+ <listitem>datafield[tag^=65] - match start of value</listitem>\r
+ </itemizedlist> \r
+ <tip><para>Selectors always narrow, so select broadly and iterate through the NodeList</para></tip>\r
+ </simplesect>\r
+ <simplesect>\r
+ <title>Slot Formatter</title><indexterm><primary>slot formatter</primary></indexterm>\r
+ <para>A slot formatter is any invisible HTML element which has a type attribute with the value of opac/slot-format. (NOTE: before 1.6.0.4, only <script> \r
+ elements were supported, though this restriction is now removed to support Internet Explorer.) Only one slot formatter element is allowed in each slot. The text contents \r
+ of this element are wrapped in a JavaScript<indexterm><primary>JavaScript</primary></indexterm> function and run for each node returned by the query CSS3 selector \r
+ specified on the slot marker. This function is passed \r
+ one argument, called item, which an XML Node captured by the selector. This function should return HTML text. The output for all runs of the slot formatter is \r
+ concatenated into a single string and used to replace the contents of the slot marker.</para>\r
+ <para>The slot formatter is optional, and if not supplied BibTemplate will create a simple function which extracts and returns the text content of the XML Nodes \r
+ specified in the CSS3 selector.</para>\r
+ <para>Example of a slot formatter:</para>\r
+<programlisting>\r
+ <td class='rdetail_item' id='rdetail_online' type='opac/slot-data' query='volumes volume uris uri' join=", ">\r
+ <script type='opac/slot-format'><![CDATA[\r
+ var link = '<a href="' + item.getAttribute('href') + '">' + item.getAttribute('label') + '</a>';\r
+ if (item.getAttribute('use_restriction'))\r
+ link += ' (Use restriction: ' + item.getAttribute('use_restriction') + ')';\r
+ return link;\r
+ ]]></script>\r
+ </td>\r
+</programlisting>\r
+ </simplesect>\r
+ <simplesect>\r
+ <title>JavaScript API</title><indexterm><primary>JavaScript</primary></indexterm>\r
+ <para>In order for BibTemplate to find the slot markers and invoke the slot formatters JavaScript renderer must be instantiated and called. This must be done \r
+ for each record that is to contribute to a pages display. The API for this is simple and straight-forward:</para>\r
+ <para>The slot formatter is optional, and if not supplied BibTemplate will create a simple function which extracts and returns the text content of the XML Nodes \r
+ specified in the CSS3 selector.</para><indexterm><primary>CSS3</primary></indexterm>\r
+ <para>Example of a slot formatter:</para>\r
+<programlisting>\r
+ dojo.require('openils.BibTemplate'); // Tell Dojo to load BibTemplate, if it is not already loaded\r
+ \r
+ // Create a renderer supplying the record id and the short name of the org unit, if known, and call the render() method \r
+ new openils.BibTemplate({ record : new CGI().param('r'), org_unit : here.shortname() }).render(); \r
+</programlisting>\r
+ <para>The argument hash supplied to the new <systemitem>openils.BibTemplate()</systemitem> constructor can have the following properties:</para>\r
+ <itemizedlist>\r
+ <listitem>record – The bibliographic record ID.</listitem>\r
+ <listitem>org_unit – The relevant Organizational Unit, used to restrict holdings scope as on a search result or record detail page.</listitem>\r
+ <listitem>root – The root element within the web page that BibTemplate should search for slot markers</listitem>\r
+ </itemizedlist> \r
+ </simplesect>\r
+ <simplesect>\r
+ <title>BibTemplate Examples</title>\r
+ <para>This is all that we had to add to display the contents of an arbitrary MARC field:</para>\r
+<programlisting>\r
+<tr>\r
+ <td>Bibliography note</td>\r
+ <td type='opac/slot-data' query='datafield[tag=504]'></td>\r
+</tr>\r
+</programlisting>\r
+ <note><para>If multiple fields match, they are displayed on consecutive lines within the same left-hand cell.</para></note>\r
+ <para>To display a specific MARC subfield, add that subfield to the query attribute.</para><indexterm><primary>MARC</primary></indexterm>\r
+ <para>For example, subfield $a is the only user-oriented subfield in field 586 (Awards Note)</para>\r
+<programlisting>\r
+<tr>\r
+ <td>Awards note</td>\r
+ <td type='opac/slot-data' query='datafield[tag=586] subfield[code=a]'></td>\r
+</tr>\r
+</programlisting>\r
+ <para>Hide empty rows by default, and display them only if they have content:</para>\r
+<programlisting>\r
+ <tr class='hide_me' id='tag504'>\r
+ <td>Bibliographic note</td>\r
+ <td type='opac/slot-data' query='datafield[tag=504]'>\r
+ <script type='opac/slot-format'><![CDATA[\r
+ dojo.query('#tag504').removeClass('hide_me');\r
+ return '<span>' + dojox.data.dom.textContent(item) +\r
+ '</span><br/>';\r
+ ]]></script>\r
+ </td></tr>\r
+</programlisting>\r
+ <itemizedlist>\r
+ <listitem><![CDATA[ ... ]]> tells Evergreen Web server to treat the contents as literal <quote>character data</quote> - \r
+ avoids hilarity of entity substitution</listitem>\r
+ <listitem><script type='opac/slot-format'>...</script>, contained within an <quote>opac/slot-data</quote> element, receives a variable named item \r
+ containing the results of the query (a NodeList)</listitem> \r
+ </itemizedlist> \r
+ <para>Suppressing a subfield:</para>\r
+<programlisting>\r
+<tr class='hide_me' id='tag700'>\r
+ <td>Additional authors</td>\r
+ <td type='opac/slot-data' query='datafield[tag=700]'>\r
+ <script type='opac/slot-format'><![CDATA[\r
+ dojo.query('#tag700').removeClass('hide_me');\r
+ var text = '';\r
+ var list = dojo.query('subfield:not([code=4])', item);\r
+ for (var i =0; i < list.length; i++) {\r
+ text += dojox.data.dom.textContent(list[i]) + ' ';\r
+ }\r
+ return '<span>' + text + '</span><br/>';\r
+ ]]></script>\r
+ </td></tr>\r
+</programlisting>\r
+ </simplesect>\r
+ </section>\r
+</chapter>\r
+\r
</abstract>\r
<section id="exploring_database_schema">\r
<title>Exploring the Database Schema</title>\r
- <simpara>The database schema is tied pretty tightly to PostgreSQL. Although PostgreSQL\r
- adheres closely to ANSI SQL standards, the use of schemas, SQL functions\r
- implemented in both plpgsql and plperl, and PostgreSQL’s native full-text\r
+ <simpara>The database schema is tied pretty tightly to PostgreSQL. Although PostgreSQL<indexterm><primary>databases</primary><secondary>PostgreSQL</secondary></indexterm>\r
+ adheres closely to ANSI SQL standards, the use of schemas, SQL functions<indexterm><primary>ANSI</primary></indexterm>\r
+ implemented in both <systemitem>plpgsql</systemitem> and <systemitem>plperl</systemitem>, and PostgreSQL’s native full-text\r
search would make it… challenging… to port to other database platforms.</simpara>\r
<simpara>A few common PostgreSQL interfaces for poking around the schema and\r
manipulating data are:</simpara>\r
<itemizedlist>\r
<listitem>\r
<simpara>\r
- psql (the command line client)\r
+ psql (the command line client)<indexterm><primary>databases</primary><secondary>PostgreSQL</secondary><tertiery>psql</tertiery></indexterm>\r
</simpara>\r
</listitem>\r
<listitem>\r
<simpara>\r
- pgadminIII (a GUI client).\r
+ pgadminIII (a GUI client).<indexterm><primary>databases</primary><secondary>PostgreSQL</secondary><tertiery>pgadminIII</tertiery></indexterm>\r
</simpara>\r
</listitem>\r
</itemizedlist>\r
- <simpara>Or you can read through the source files in Open-ILS/src/sql/Pg.</simpara>\r
+ <simpara>Or you can read through the source files in <filename class="directoy">Open-ILS/src/sql/Pg</filename>.</simpara>\r
<simpara>Let’s take a quick tour through the schemas, pointing out some highlights\r
and some key interdependencies:</simpara>\r
<itemizedlist>\r
</itemizedlist>\r
<simplesect id="_idl_basic_example_config_language_map">\r
<title>IDL basic example (config.language_map)</title>\r
- <programlisting language="xml" linenumbering="unnumbered"><class id="clm" controller="open-ils.cstore open-ils.pcrud"\r
- oils_obj:fieldmapper="config::language_map"\r
- oils_persist:tablename="config.language_map"\r
- reporter:label="Language Map" oils_persist:field_safe="true"> <co id="dmCO5-1"/> <co id="dmCO5-2"/> <co id="dmCO5-3"/> <co id="dmCO5-4"/>\r
- <fields oils_persist:primary="code" oils_persist:sequence=""> <co id="dmCO5-5"/>\r
- <field reporter:label="Language Code" name="code"\r
- reporter:selector="value" reporter:datatype="text"/> <co id="dmCO5-6"/>\r
- <field reporter:label="Language" name="value"\r
- reporter:datatype="text" oils_persist:i18n="true"/> <co id="dmCO5-7"/>\r
- </fields>\r
- <links/>\r
- <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1"> <co id="dmCO5-8"/>\r
- <actions>\r
- <create global_required="true" permission="CREATE_MARC_CODE"> <co id="dmCO5-9"/>\r
- <retrieve global_required="true"\r
- permission="CREATE_MARC_CODE UPDATE_MARC_CODE DELETE_MARC_CODE">\r
- <update global_required="true" permission="UPDATE_MARC_CODE">\r
- <delete global_required="true" permission="DELETE_MARC_CODE">\r
- </actions>\r
- </permacrud>\r
- </class></programlisting>\r
+<programlisting language="xml" linenumbering="unnumbered">\r
+<class id="clm" controller="open-ils.cstore open-ils.pcrud"\r
+ oils_obj:fieldmapper="config::language_map"\r
+ oils_persist:tablename="config.language_map"\r
+ reporter:label="Language Map" oils_persist:field_safe="true"> <co id="dmCO5-1"/> <co id="dmCO5-2"/> <co id="dmCO5-3"/> <co id="dmCO5-4"/>\r
+ <fields oils_persist:primary="code" oils_persist:sequence=""> <co id="dmCO5-5"/>\r
+ <field reporter:label="Language Code" name="code"\r
+ reporter:selector="value" reporter:datatype="text"/> <co id="dmCO5-6"/>\r
+ <field reporter:label="Language" name="value"\r
+ reporter:datatype="text" oils_persist:i18n="true"/> <co id="dmCO5-7"/>\r
+ </fields>\r
+ <links/>\r
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1"> <co id="dmCO5-8"/>\r
+ <actions>\r
+ <create global_required="true" permission="CREATE_MARC_CODE"> <co id="dmCO5-9"/>\r
+ <retrieve global_required="true"\r
+ permission="CREATE_MARC_CODE UPDATE_MARC_CODE DELETE_MARC_CODE">\r
+ <update global_required="true" permission="UPDATE_MARC_CODE">\r
+ <delete global_required="true" permission="DELETE_MARC_CODE">\r
+ </actions>\r
+ </permacrud>\r
+</class>\r
+</programlisting>\r
<calloutlist>\r
<callout arearefs="dmCO5-1">\r
<simpara>\r
access to the classes for which <literal>oils_persist:field_safe</literal> is <literal>true</literal>. For\r
example,\r
</simpara>\r
- <screen>srfsh# request open-ils.fielder open-ils.fielder.clm.atomic \\r
- {"query":{"code":{"=":"eng"}}}\r
+<screen>\r
+<userinput>\r
+srfsh# request open-ils.fielder open-ils.fielder.clm.atomic \\r
+{"query":{"code":{"=":"eng"}}}\r
\r
- Received Data: [\r
- {\r
- "value":"English",\r
- "code":"eng"\r
- }\r
- ]</screen>\r
+Received Data: [\r
+ {\r
+ "value":"English",\r
+ "code":"eng"\r
+ }\r
+]\r
+</userinput>\r
+</screen>\r
</callout>\r
<callout arearefs="dmCO5-5">\r
<simpara>\r
<callout arearefs="dmCO5-8">\r
<simpara>\r
The <literal>permacrud</literal> element defines the permissions (if any) required\r
- to <emphasis role="strong">c</emphasis>reate, <emphasis role="strong">r</emphasis>etrieve, <emphasis role="strong">u</emphasis>pdate, and <emphasis role="strong">d</emphasis>elete data for this\r
+ to <emphasis role="strong">c</emphasis>reate, <emphasis role="strong">r</emphasis>etrieve, <emphasis role="strong">u</emphasis>pdate, \r
+ and <emphasis role="strong">d</emphasis>elete data for this\r
class. <literal>open-ils.permacrud</literal> must be defined as a controller for the class\r
for the permissions to be applied.\r
</simpara>\r
can contain fields that link to fields in other classes. The <literal><links></literal>\r
element defines which fields link to fields in other classes, and\r
the nature of the relationship:</simpara>\r
- <programlisting language="xml" linenumbering="unnumbered"><class id="aws" controller="open-ils.cstore"\r
- oils_obj:fieldmapper="actor::workstation"\r
- oils_persist:tablename="actor.workstation"\r
- reporter:label="Workstation">\r
- <fields oils_persist:primary="id"\r
- oils_persist:sequence="actor.workstation_id_seq">\r
- <field reporter:label="Workstation ID" name="id"\r
- reporter:datatype="id"/>\r
- <field reporter:label="Workstation Name" name="name"\r
- reporter:datatype="text"/>\r
- <field reporter:label="Owning Library" name="owning_lib"\r
- reporter:datatype="org_unit"/>\r
- <field reporter:label="Circulations" name="circulations"\r
- oils_persist:virtual="true" reporter:datatype="link"/> <co id="dmCO6-1"/>\r
- </fields>\r
- <links> <co id="dmCO6-2"/>\r
- <link field="owning_lib" reltype="has_a" key="id"\r
- map="" class="aou"/> <co id="dmCO6-3"/>\r
- <link field="circulations" reltype="has_many" key="workstation"\r
- map="" class="circ"/>\r
- <link field="circulation_checkins" reltype="has_many"\r
- key="checkin_workstation" map="" class="circ"/>\r
- </links>\r
- </class></programlisting>\r
+<programlisting language="xml" linenumbering="unnumbered">\r
+<class id="aws" controller="open-ils.cstore"\r
+ oils_obj:fieldmapper="actor::workstation"\r
+ oils_persist:tablename="actor.workstation"\r
+ reporter:label="Workstation">\r
+ <fields oils_persist:primary="id"\r
+ oils_persist:sequence="actor.workstation_id_seq">\r
+ <field reporter:label="Workstation ID" name="id"\r
+ reporter:datatype="id"/>\r
+ <field reporter:label="Workstation Name" name="name"\r
+ reporter:datatype="text"/>\r
+ <field reporter:label="Owning Library" name="owning_lib"\r
+ reporter:datatype="org_unit"/>\r
+ <field reporter:label="Circulations" name="circulations"\r
+ oils_persist:virtual="true" reporter:datatype="link"/> <co id="dmCO6-1"/>\r
+ </fields>\r
+ <links> <co id="dmCO6-2"/>\r
+ <link field="owning_lib" reltype="has_a" key="id"\r
+ map="" class="aou"/> <co id="dmCO6-3"/>\r
+ <link field="circulations" reltype="has_many" key="workstation"\r
+ map="" class="circ"/>\r
+ <link field="circulation_checkins" reltype="has_many"\r
+ key="checkin_workstation" map="" class="circ"/>\r
+ </links>\r
+</class>\r
+</programlisting>\r
<calloutlist>\r
<callout arearefs="dmCO6-1">\r
<simpara>\r
conditions.</simpara>\r
<simpara>For example, to generate a list of barcodes that are held in a\r
copy location that allows holds and is visible in the OPAC:</simpara>\r
- <programlisting language="sh" linenumbering="unnumbered">srfsh# request open-ils.cstore open-ils.cstore.json_query <co id="dmCO7-1"/>\r
- {"select": {"acp":["barcode"], "acpl":["name"]}, <co id="dmCO7-2"/>\r
- "from": {"acp":"acpl"}, <co id="dmCO7-3"/>\r
- "where": [ <co id="dmCO7-4"/>\r
- {"+acpl": "holdable"}, <co id="dmCO7-5"/>\r
- {"+acpl": "opac_visible"} <co id="dmCO7-6"/>\r
- ]}\r
+<programlisting language="sh" linenumbering="unnumbered">\r
+srfsh# request open-ils.cstore open-ils.cstore.json_query <co id="dmCO7-1"/>\r
+ {"select": {"acp":["barcode"], "acpl":["name"]}, <co id="dmCO7-2"/>\r
+ "from": {"acp":"acpl"}, <co id="dmCO7-3"/>\r
+ "where": [ <co id="dmCO7-4"/>\r
+ {"+acpl": "holdable"}, <co id="dmCO7-5"/>\r
+ {"+acpl": "opac_visible"} <co id="dmCO7-6"/>\r
+ ]}\r
\r
- Received Data: {\r
- "barcode":"BARCODE1",\r
- "name":"Stacks"\r
- }\r
+Received Data: {\r
+ "barcode":"BARCODE1",\r
+ "name":"Stacks"\r
+}\r
\r
- Received Data: {\r
- "barcode":"BARCODE2",\r
- "name":"Stacks"\r
- }</programlisting>\r
+Received Data: {\r
+ "barcode":"BARCODE2",\r
+ "name":"Stacks"\r
+}\r
+</programlisting>\r
<calloutlist>\r
<callout arearefs="dmCO7-1">\r
<simpara>\r
<literal>open-ils.cstore.direct.\*.retrieve</literal> methods allow you to specify a\r
JSON structure defining the fields you wish to flesh in the returned object.</simpara>\r
<formalpara><title>Fleshing fields in objects returned by <literal>open-ils.cstore</literal></title><para>\r
- <programlisting language="sh" linenumbering="unnumbered">srfsh# request open-ils.cstore open-ils.cstore.direct.asset.copy.retrieve 1, \\r
- {\r
- "flesh": 1, <co id="dmCO8-1"/>\r
- "flesh_fields": { <co id="dmCO8-2"/>\r
- "acp": ["location"]\r
- }\r
- }</programlisting>\r
+<programlisting language="sh" linenumbering="unnumbered">\r
+srfsh# request open-ils.cstore open-ils.cstore.direct.asset.copy.retrieve 1, \\r
+ {\r
+ "flesh": 1, <co id="dmCO8-1"/>\r
+ "flesh_fields": { <co id="dmCO8-2"/>\r
+ "acp": ["location"]\r
+ }\r
+ }\r
+</programlisting>\r
</para></formalpara>\r
<calloutlist>\r
<callout arearefs="dmCO8-1">\r
let’s also flesh the call number attached to the copy, and then flesh\r
the bibliographic record attached to the call number.</simpara>\r
<formalpara><title>Fleshing fields in fields of objects returned by <literal>open-ils.cstore</literal></title><para>\r
- <programlisting language="java" linenumbering="unnumbered">request open-ils.cstore open-ils.cstore.direct.asset.copy.retrieve 1, \\r
- {\r
- "flesh": 2,\r
- "flesh_fields": {\r
- "acp": ["location", "call_number"],\r
- "acn": ["record"]\r
- }\r
- }</programlisting>\r
+<programlisting language="java" linenumbering="unnumbered">\r
+request open-ils.cstore open-ils.cstore.direct.asset.copy.retrieve 1, \\r
+ {\r
+ "flesh": 2,\r
+ "flesh_fields": {\r
+ "acp": ["location", "call_number"],\r
+ "acn": ["record"]\r
+ }\r
+ }\r
+</programlisting>\r
</para></formalpara>\r
</simplesect>\r
</section>\r
</listitem>\r
<listitem>\r
<simpara>\r
- Rerun <literal>/openils/bin/autogen.sh</literal> to regenerate the JavaScript versions\r
+ Rerun <filename>/openils/bin/autogen.sh</filename> to regenerate the JavaScript versions<indexterm><primary>autogen</primary></indexterm>\r
of the IDL required by the HTTP translator and gateway.\r
</simpara>\r
</listitem>\r
</orderedlist>\r
- <simpara>We also need to adjust our JavaScript client to use the nifty new\r
+ <simpara>We also need to adjust our JavaScript client to use the nifty new<indexterm><primary>JavaScript</primary></indexterm>\r
objects that <literal>open-ils.resolver.resolve_holdings</literal> now returns.\r
- The best approach is to use the support in Evergreen’s Dojo extensions\r
+ The best approach is to use the support in Evergreen’s Dojo extensions<indexterm><primary>Dojo toolkit</primary></indexterm>\r
to generate the JavaScript classes directly from the IDL XML file.</simpara>\r
<formalpara><title>Accessing classes defined in the IDL via Fieldmapper</title><para>\r
<programlisting language="html" linenumbering="unnumbered"></programlisting>\r
</callout>\r
<callout arearefs="">\r
<simpara>\r
- <literal>fieldmapper.AutoIDL</literal> reads <literal>/openils/var/reports/fm_IDL.xml</literal> to\r
+ <literal>fieldmapper.AutoIDL</literal> reads <filename>/openils/var/reports/fm_IDL.xml</filename> to\r
generate a list of class properties.\r
</simpara>\r
</callout>\r
worry if the INSERT statement is completely unfamiliar, we’ll talk more about\r
the syntax of the insert statement later.</simpara>\r
<formalpara><title><literal>actor.usr_note</literal> database table</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">evergreen=# INSERT INTO actor.usr_note (usr, creator, pub, title, value)\r
- VALUES (1, 1, TRUE, 'Who is this guy?', 'He''s the administrator!');\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+evergreen=# INSERT INTO actor.usr_note (usr, creator, pub, title, value)\r
+ VALUES (1, 1, TRUE, 'Who is this guy?', 'He''s the administrator!');\r
\r
- evergreen=# select id, usr, creator, pub, title, value from actor.usr_note;\r
- id | usr | creator | pub | title | value\r
- ----+-----+---------+-----+------------------+-------------------------\r
- 1 | 1 | 1 | t | Who is this guy? | He's the administrator!\r
- (1 rows)</programlisting>\r
+evergreen=# select id, usr, creator, pub, title, value from actor.usr_note;\r
+ id | usr | creator | pub | title | value\r
+----+-----+---------+-----+------------------+-------------------------\r
+ 1 | 1 | 1 | t | Who is this guy? | He's the administrator!\r
+(1 rows)\r
+</programlisting>\r
</para></formalpara>\r
<simpara>PostgreSQL supports table inheritance, which lets you define tables that\r
inherit the column definitions of a given parent table. A search of the data in\r
the <literal>public</literal> schema. As a result, you might not find the object that you’re\r
looking for if you don’t use the appropriate schema.</simpara>\r
<formalpara><title>Example: Creating a table without a specific schema</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">evergreen=# CREATE TABLE foobar (foo TEXT, bar TEXT);\r
- CREATE TABLE\r
- evergreen=# \d foobar\r
- Table "public.foobar"\r
- Column | Type | Modifiers\r
- --------+------+-----------\r
- foo | text |\r
- bar | text |</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+evergreen=# CREATE TABLE foobar (foo TEXT, bar TEXT);\r
+CREATE TABLE\r
+evergreen=# \d foobar\r
+ Table "public.foobar"\r
+ Column | Type | Modifiers\r
+--------+------+-----------\r
+ foo | text |\r
+ bar | text |\r
+</programlisting>\r
</para></formalpara>\r
<formalpara><title>Example: Trying to access a unqualified table outside of the public schema</title><para>\r
<programlisting language="sql" linenumbering="unnumbered">evergreen=# SELECT * FROM usr_note;\r
<simpara>The <literal>actor.org_address</literal> table is a simple table in the Evergreen schema that\r
we can use as a concrete example of many of the properties of databases that\r
we have discussed so far.</simpara>\r
- <programlisting language="sql" linenumbering="unnumbered">CREATE TABLE actor.org_address (\r
- id SERIAL PRIMARY KEY, <co id="sqlCO1-1"/>\r
- valid BOOL NOT NULL DEFAULT TRUE, <co id="sqlCO1-2"/>\r
- address_type TEXT NOT NULL DEFAULT 'MAILING', <co id="sqlCO1-3"/>\r
- org_unit INT NOT NULL REFERENCES actor.org_unit (id) <co id="sqlCO1-4"/>\r
- DEFERRABLE INITIALLY DEFERRED,\r
- street1 TEXT NOT NULL,\r
- street2 TEXT, <co id="sqlCO1-5"/>\r
- city TEXT NOT NULL,\r
- county TEXT,\r
- state TEXT NOT NULL,\r
- country TEXT NOT NULL,\r
- post_code TEXT NOT NULL\r
- );</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+CREATE TABLE actor.org_address (\r
+ id SERIAL PRIMARY KEY, <co id="sqlCO1-1"/>\r
+ valid BOOL NOT NULL DEFAULT TRUE, <co id="sqlCO1-2"/>\r
+ address_type TEXT NOT NULL DEFAULT 'MAILING', <co id="sqlCO1-3"/>\r
+ org_unit INT NOT NULL REFERENCES actor.org_unit (id) <co id="sqlCO1-4"/>\r
+ DEFERRABLE INITIALLY DEFERRED,\r
+ street1 TEXT NOT NULL,\r
+ street2 TEXT, <co id="sqlCO1-5"/>\r
+ city TEXT NOT NULL,\r
+ county TEXT,\r
+ state TEXT NOT NULL,\r
+ country TEXT NOT NULL,\r
+ post_code TEXT NOT NULL\r
+);\r
+</programlisting>\r
<calloutlist>\r
<callout arearefs="sqlCO1-1">\r
<simpara>\r
<simpara>To display the definition of a database object such as a table, issue the\r
command <literal>\d _object-name_</literal>. For example, to display the definition of the\r
actor.usr_note table:</simpara>\r
- <programlisting language="sh" linenumbering="unnumbered">$ psql evergreen <co id="sqlCO2-1"/>\r
- psql (8.4.1)\r
- Type "help" for help.\r
+<programlisting language="sh" linenumbering="unnumbered">\r
+$ psql evergreen <co id="sqlCO2-1"/>\r
+psql (8.4.1)\r
+Type "help" for help.\r
\r
- evergreen=# \d actor.usr_note <co id="sqlCO2-2"/>\r
- Table "actor.usr_note"\r
- Column | Type | Modifiers\r
- -------------+--------------------------+-------------------------------------------------------------\r
- id | bigint | not null default nextval('actor.usr_note_id_seq'::regclass)\r
- usr | bigint | not null\r
- creator | bigint | not null\r
- create_date | timestamp with time zone | default now()\r
- pub | boolean | not null default false\r
- title | text | not null\r
- value | text | not null\r
- Indexes:\r
- "usr_note_pkey" PRIMARY KEY, btree (id)\r
- "actor_usr_note_creator_idx" btree (creator)\r
- "actor_usr_note_usr_idx" btree (usr)\r
- Foreign-key constraints:\r
- "usr_note_creator_fkey" FOREIGN KEY (creator) REFERENCES actor.usr(id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED\r
- "usr_note_usr_fkey" FOREIGN KEY (usr) REFERENCES actor.usr(id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED\r
+evergreen=# \d actor.usr_note <co id="sqlCO2-2"/>\r
+ Table "actor.usr_note"\r
+ Column | Type | Modifiers\r
+-------------+--------------------------+-------------------------------------------------------------\r
+ id | bigint | not null default nextval('actor.usr_note_id_seq'::regclass)\r
+ usr | bigint | not null\r
+ creator | bigint | not null\r
+ create_date | timestamp with time zone | default now()\r
+ pub | boolean | not null default false\r
+ title | text | not null\r
+ value | text | not null\r
+Indexes:\r
+ "usr_note_pkey" PRIMARY KEY, btree (id)\r
+ "actor_usr_note_creator_idx" btree (creator)\r
+ "actor_usr_note_usr_idx" btree (usr)\r
+Foreign-key constraints:\r
+ "usr_note_creator_fkey" FOREIGN KEY (creator) REFERENCES actor.usr(id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED\r
+ "usr_note_usr_fkey" FOREIGN KEY (usr) REFERENCES actor.usr(id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED\r
\r
- evergreen=# \q <co id="sqlCO2-3"/>\r
- $</programlisting>\r
+evergreen=# \q <co id="sqlCO2-3"/>\r
+$\r
+</programlisting>\r
<calloutlist>\r
<callout arearefs="sqlCO2-1">\r
<simpara>\r
<simpara>For example, to sort the rows returned from your <literal>actor.usr_address</literal> query by\r
city, with county and then zip code as the tie breakers, issue the\r
following query:</simpara>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT city, county, state\r
- FROM actor.usr_address\r
- ORDER BY city, county, post_code\r
- ;</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT city, county, state\r
+ FROM actor.usr_address\r
+ ORDER BY city, county, post_code\r
+;\r
+</programlisting>\r
</simplesect>\r
<simplesect id="_filtering_results_with_the_where_clause">\r
<title>Filtering results with the WHERE clause</title>\r
<simpara>For example, to restrict the results returned from your <literal>actor.usr_address</literal>\r
query to only those rows containing a state value of <emphasis>Connecticut</emphasis>, issue the\r
following query:</simpara>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT city, county, state\r
- FROM actor.usr_address\r
- WHERE state = 'Connecticut'\r
- ORDER BY city, county, post_code\r
- ;</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT city, county, state\r
+ FROM actor.usr_address\r
+ WHERE state = 'Connecticut'\r
+ ORDER BY city, county, post_code\r
+;\r
+</programlisting>\r
<simpara>You can include more conditions in the <literal>WHERE</literal> clause with the <literal>OR</literal> and <literal>AND</literal>\r
operators. For example, to further restrict the results returned from your\r
<literal>actor.usr_address</literal> query to only those rows where the state column contains a\r
value of <emphasis>Connecticut</emphasis> and the city column contains a value of <emphasis>Hartford</emphasis>,\r
issue the following query:</simpara>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT city, county, state\r
- FROM actor.usr_address\r
- WHERE state = 'Connecticut'\r
- AND city = 'Hartford'\r
- ORDER BY city, county, post_code\r
- ;</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT city, county, state\r
+ FROM actor.usr_address\r
+ WHERE state = 'Connecticut'\r
+ AND city = 'Hartford'\r
+ ORDER BY city, county, post_code\r
+;\r
+</programlisting>\r
<note><simpara>To return rows where the state is <emphasis>Connecticut</emphasis> and the city is <emphasis>Hartford</emphasis> or\r
<emphasis>New Haven</emphasis>, you must use parentheses to explicitly group the city value\r
conditions together, or else the database will evaluate the <literal>OR city = 'New\r
Haven'</literal> clause entirely on its own and match all rows where the city column is\r
<emphasis>New Haven</emphasis>, even though the state might not be <emphasis>Connecticut</emphasis>.</simpara></note>\r
<formalpara><title>Trouble with OR</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT city, county, state\r
- FROM actor.usr_address\r
- WHERE state = 'Connecticut'\r
- AND city = 'Hartford' OR city = 'New Haven'\r
- ORDER BY city, county, post_code\r
- ;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT city, county, state\r
+ FROM actor.usr_address\r
+ WHERE state = 'Connecticut'\r
+ AND city = 'Hartford' OR city = 'New Haven'\r
+ ORDER BY city, county, post_code\r
+;\r
\r
- -- Can return unwanted rows because the OR is not grouped!</programlisting>\r
+-- Can return unwanted rows because the OR is not grouped!\r
+</programlisting>\r
</para></formalpara>\r
<formalpara><title>Grouped OR’ed conditions</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT city, county, state\r
- FROM actor.usr_address\r
- WHERE state = 'Connecticut'\r
- AND (city = 'Hartford' OR city = 'New Haven')\r
- ORDER BY city, county, post_code\r
- ;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT city, county, state\r
+ FROM actor.usr_address\r
+ WHERE state = 'Connecticut'\r
+ AND (city = 'Hartford' OR city = 'New Haven')\r
+ ORDER BY city, county, post_code\r
+;\r
\r
- -- The parentheses ensure that the OR is applied to the cities, and the\r
- -- state in either case must be 'Connecticut'</programlisting>\r
+-- The parentheses ensure that the OR is applied to the cities, and the\r
+-- state in either case must be 'Connecticut'\r
+</programlisting>\r
</para></formalpara>\r
<simplesect id="_comparison_operators">\r
<title>Comparison operators</title>\r
<simpara>To return rows from a table where a given column is not <literal>NULL</literal>, use the\r
<literal>IS NOT NULL</literal> comparison operator.</simpara>\r
<formalpara><title>Retrieving rows where a column is not <literal>NULL</literal></title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT id, first_given_name, family_name\r
- FROM actor.usr\r
- WHERE second_given_name IS NOT NULL\r
- ;</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT id, first_given_name, family_name\r
+ FROM actor.usr\r
+ WHERE second_given_name IS NOT NULL\r
+;\r
+</programlisting>\r
</para></formalpara>\r
<simpara>Similarly, to return rows from a table where a given column is <literal>NULL</literal>, use\r
the <literal>IS NULL</literal> comparison operator.</simpara>\r
<formalpara><title>Retrieving rows where a column is <literal>NULL</literal></title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT id, first_given_name, second_given_name, family_name\r
- FROM actor.usr\r
- WHERE second_given_name IS NULL\r
- ;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT id, first_given_name, second_given_name, family_name\r
+ FROM actor.usr\r
+ WHERE second_given_name IS NULL\r
+;\r
\r
- id | first_given_name | second_given_name | family_name\r
- ----+------------------+-------------------+----------------\r
- 1 | Administrator | | System Account\r
- (1 row)</programlisting>\r
+ id | first_given_name | second_given_name | family_name\r
+----+------------------+-------------------+----------------\r
+ 1 | Administrator | | System Account\r
+(1 row)\r
+</programlisting>\r
</para></formalpara>\r
<simpara>Notice that the <literal>NULL</literal> value in the output is displayed as empty space,\r
indistinguishable from an empty string; this is the default display method in\r
<literal>psql</literal>. You can change the behaviour of <literal>psql</literal> using the <literal>pset</literal> command:</simpara>\r
<formalpara><title>Changing the way <literal>NULL</literal> values are displayed in <literal>psql</literal></title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">evergreen=# \pset null '(null)'\r
- Null display is '(null)'.\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+evergreen=# \pset null '(null)'\r
+Null display is '(null)'.\r
\r
- SELECT id, first_given_name, second_given_name, family_name\r
- FROM actor.usr\r
- WHERE second_given_name IS NULL\r
- ;\r
+SELECT id, first_given_name, second_given_name, family_name\r
+ FROM actor.usr\r
+ WHERE second_given_name IS NULL\r
+;\r
\r
- id | first_given_name | second_given_name | family_name\r
- ----+------------------+-------------------+----------------\r
- 1 | Administrator | (null) | System Account\r
- (1 row)</programlisting>\r
+ id | first_given_name | second_given_name | family_name\r
+----+------------------+-------------------+----------------\r
+ 1 | Administrator | (null) | System Account\r
+(1 row)\r
+</programlisting>\r
</para></formalpara>\r
<simpara>Database queries within programming languages such as Perl and C have\r
special methods of checking for <literal>NULL</literal> values in returned results.</simpara>\r
<simpara>For example, to change the last name of a user in the <literal>actor.usr</literal> table to\r
<literal>L’estat</literal>, issue the following SQL:</simpara>\r
<formalpara><title>Escaping <literal>'</literal> in TEXT values</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">UPDATE actor.usr\r
- SET family_name = 'L''estat'\r
- WHERE profile IN (\r
- SELECT id\r
- FROM permission.grp_tree\r
- WHERE name = 'Vampire'\r
- )\r
- ;</programlisting>\r
- </para></formalpara>\r
- <simpara>When you retrieve the row from the database, the value is displayed with just\r
- a single <literal>'</literal> character:</simpara>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT id, family_name\r
- FROM actor.usr\r
- WHERE family_name = 'L''estat'\r
- ;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+UPDATE actor.usr\r
+ SET family_name = 'L''estat'\r
+ WHERE profile IN (\r
+ SELECT id\r
+ FROM permission.grp_tree\r
+ WHERE name = 'Vampire'\r
+ )\r
+ ;</programlisting>\r
+ </para></formalpara>\r
+ <simpara>When you retrieve the row from the database, the value is displayed with just\r
+ a single <literal>'</literal> character:</simpara>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT id, family_name\r
+ FROM actor.usr\r
+ WHERE family_name = 'L''estat'\r
+;\r
\r
- id | family_name\r
- ----+-------------\r
- 1 | L'estat\r
- (1 row)</programlisting>\r
+ id | family_name\r
+----+-------------\r
+ 1 | L'estat\r
+(1 row)\r
+</programlisting>\r
</simplesect>\r
<simplesect id="_grouping_and_eliminating_results_with_the_group_by_and_having_clauses">\r
<title>Grouping and eliminating results with the GROUP BY and HAVING clauses</title>\r
results for a range of values in a single query, rather than requiring you to\r
issue one query per target value.</simpara>\r
<formalpara><title>Returning unique results of a single column with <literal>GROUP BY</literal></title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT grp\r
- FROM permission.grp_perm_map\r
- GROUP BY grp\r
- ORDER BY grp;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT grp\r
+ FROM permission.grp_perm_map\r
+ GROUP BY grp\r
+ ORDER BY grp;\r
\r
- grp\r
- -----+\r
- 1\r
- 2\r
- 3\r
- 4\r
- 5\r
- 6\r
- 7\r
- 10\r
- (8 rows)</programlisting>\r
+ grp\r
+-----+\r
+ 1\r
+ 2\r
+ 3\r
+ 4\r
+ 5\r
+ 6\r
+ 7\r
+ 10\r
+(8 rows)\r
+</programlisting>\r
</para></formalpara>\r
<simpara>While <literal>GROUP BY</literal> can be useful for a single column, it is more often used\r
to return the distinct results across multiple columns. For example, the\r
following query shows us which groups have permissions at each depth in\r
the library hierarchy:</simpara>\r
<formalpara><title>Returning unique results of multiple columns with <literal>GROUP BY</literal></title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT grp, depth\r
- FROM permission.grp_perm_map\r
- GROUP BY grp, depth\r
- ORDER BY depth, grp;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT grp, depth\r
+ FROM permission.grp_perm_map\r
+ GROUP BY grp, depth\r
+ ORDER BY depth, grp;\r
\r
- grp | depth\r
- -----+-------\r
- 1 | 0\r
- 2 | 0\r
- 3 | 0\r
- 4 | 0\r
- 5 | 0\r
- 10 | 0\r
- 3 | 1\r
- 4 | 1\r
- 5 | 1\r
- 6 | 1\r
- 7 | 1\r
- 10 | 1\r
- 3 | 2\r
- 4 | 2\r
- 10 | 2\r
- (15 rows)</programlisting>\r
+ grp | depth\r
+-----+-------\r
+ 1 | 0\r
+ 2 | 0\r
+ 3 | 0\r
+ 4 | 0\r
+ 5 | 0\r
+ 10 | 0\r
+ 3 | 1\r
+ 4 | 1\r
+ 5 | 1\r
+ 6 | 1\r
+ 7 | 1\r
+ 10 | 1\r
+ 3 | 2\r
+ 4 | 2\r
+ 10 | 2\r
+(15 rows)\r
+</programlisting>\r
</para></formalpara>\r
<simpara>Extending this further, you can use the <literal>COUNT()</literal> aggregate function to\r
also return the number of times each unique combination of <literal>grp</literal> and <literal>depth</literal>\r
appears in the table. <emphasis>Yes, this is a sneak peek at the use of aggregate\r
functions! Keeners.</emphasis></simpara>\r
<formalpara><title>Counting unique column combinations with <literal>GROUP BY</literal></title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT grp, depth, COUNT(grp)\r
- FROM permission.grp_perm_map\r
- GROUP BY grp, depth\r
- ORDER BY depth, grp;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT grp, depth, COUNT(grp)\r
+ FROM permission.grp_perm_map\r
+ GROUP BY grp, depth\r
+ ORDER BY depth, grp;\r
\r
- grp | depth | count\r
- -----+-------+-------\r
- 1 | 0 | 6\r
- 2 | 0 | 2\r
- 3 | 0 | 45\r
- 4 | 0 | 3\r
- 5 | 0 | 5\r
- 10 | 0 | 1\r
- 3 | 1 | 3\r
- 4 | 1 | 4\r
- 5 | 1 | 1\r
- 6 | 1 | 9\r
- 7 | 1 | 5\r
- 10 | 1 | 10\r
- 3 | 2 | 24\r
- 4 | 2 | 8\r
- 10 | 2 | 7\r
- (15 rows)</programlisting>\r
+ grp | depth | count\r
+-----+-------+-------\r
+ 1 | 0 | 6\r
+ 2 | 0 | 2\r
+ 3 | 0 | 45\r
+ 4 | 0 | 3\r
+ 5 | 0 | 5\r
+ 10 | 0 | 1\r
+ 3 | 1 | 3\r
+ 4 | 1 | 4\r
+ 5 | 1 | 1\r
+ 6 | 1 | 9\r
+ 7 | 1 | 5\r
+ 10 | 1 | 10\r
+ 3 | 2 | 24\r
+ 4 | 2 | 8\r
+ 10 | 2 | 7\r
+(15 rows)\r
+</programlisting>\r
</para></formalpara>\r
<simpara>You can use the <literal>WHERE</literal> clause to restrict the returned results before grouping\r
is applied to the results. The following query restricts the results to those\r
rows that have a depth of 0.</simpara>\r
<formalpara><title>Using the <literal>WHERE</literal> clause with <literal>GROUP BY</literal></title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT grp, COUNT(grp)\r
- FROM permission.grp_perm_map\r
- WHERE depth = 0\r
- GROUP BY grp\r
- ORDER BY 2 DESC\r
- ;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT grp, COUNT(grp)\r
+ FROM permission.grp_perm_map\r
+ WHERE depth = 0\r
+ GROUP BY grp\r
+ ORDER BY 2 DESC\r
+;\r
\r
- grp | count\r
- -----+-------\r
- 3 | 45\r
- 1 | 6\r
- 5 | 5\r
- 4 | 3\r
- 2 | 2\r
- 10 | 1\r
- (6 rows)</programlisting>\r
+ grp | count\r
+-----+-------\r
+ 3 | 45\r
+ 1 | 6\r
+ 5 | 5\r
+ 4 | 3\r
+ 2 | 2\r
+ 10 | 1\r
+(6 rows)\r
+</programlisting>\r
</para></formalpara>\r
<simpara>To restrict results after grouping has been applied to the rows, use the\r
<literal>HAVING</literal> clause; this is typically used to restrict results based on\r
the following query restricts the returned rows to those that have more than\r
5 occurrences of the same value for <literal>grp</literal> in the table.</simpara>\r
<formalpara><title><literal>GROUP BY</literal> restricted by a <literal>HAVING</literal> clause</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT grp, COUNT(grp)\r
- FROM permission.grp_perm_map\r
- GROUP BY grp\r
- HAVING COUNT(grp) > 5\r
- ;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT grp, COUNT(grp)\r
+ FROM permission.grp_perm_map\r
+ GROUP BY grp\r
+ HAVING COUNT(grp) > 5\r
+;\r
\r
- grp | count\r
- -----+-------\r
- 6 | 9\r
- 4 | 15\r
- 5 | 6\r
- 1 | 6\r
- 3 | 72\r
- 10 | 18\r
- (6 rows)</programlisting>\r
+ grp | count\r
+-----+-------\r
+ 6 | 9\r
+ 4 | 15\r
+ 5 | 6\r
+ 1 | 6\r
+ 3 | 72\r
+ 10 | 18\r
+(6 rows)\r
+</programlisting>\r
</para></formalpara>\r
</simplesect>\r
<simplesect id="_eliminating_duplicate_results_with_the_distinct_keyword">\r
them, then applying the <literal>DISTINCT</literal> keyword might be a sign that you are\r
papering over a real problem.</simpara>\r
<formalpara><title>Returning unique results of multiple columns with <literal>DISTINCT</literal></title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT DISTINCT grp, depth\r
- FROM permission.grp_perm_map\r
- ORDER BY depth, grp\r
- ;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT DISTINCT grp, depth\r
+ FROM permission.grp_perm_map\r
+ ORDER BY depth, grp\r
+;\r
\r
- grp | depth\r
- -----+-------\r
- 1 | 0\r
- 2 | 0\r
- 3 | 0\r
- 4 | 0\r
- 5 | 0\r
- 10 | 0\r
- 3 | 1\r
- 4 | 1\r
- 5 | 1\r
- 6 | 1\r
- 7 | 1\r
- 10 | 1\r
- 3 | 2\r
- 4 | 2\r
- 10 | 2\r
- (15 rows)</programlisting>\r
+ grp | depth\r
+-----+-------\r
+ 1 | 0\r
+ 2 | 0\r
+ 3 | 0\r
+ 4 | 0\r
+ 5 | 0\r
+ 10 | 0\r
+ 3 | 1\r
+ 4 | 1\r
+ 5 | 1\r
+ 6 | 1\r
+ 7 | 1\r
+ 10 | 1\r
+ 3 | 2\r
+ 4 | 2\r
+ 10 | 2\r
+(15 rows)\r
+</programlisting>\r
</para></formalpara>\r
</simplesect>\r
<simplesect id="_paging_through_results_with_the_limit_and_offset_clauses">\r
example, in the following query we list the five most frequently used\r
circulation modifiers:</simpara>\r
<formalpara><title>Using the <literal>LIMIT</literal> clause to restrict results</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT circ_modifier, COUNT(circ_modifier)\r
- FROM asset.copy\r
- GROUP BY circ_modifier\r
- ORDER BY 2 DESC\r
- LIMIT 5\r
- ;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT circ_modifier, COUNT(circ_modifier)\r
+ FROM asset.copy\r
+ GROUP BY circ_modifier\r
+ ORDER BY 2 DESC\r
+ LIMIT 5\r
+;\r
\r
- circ_modifier | count\r
- ---------------+--------\r
- CIRC | 741995\r
- BOOK | 636199\r
- SER | 265906\r
- DOC | 191598\r
- LAW MONO | 126627\r
- (5 rows)</programlisting>\r
+ circ_modifier | count\r
+---------------+--------\r
+ CIRC | 741995\r
+ BOOK | 636199\r
+ SER | 265906\r
+ DOC | 191598\r
+ LAW MONO | 126627\r
+(5 rows)\r
+</programlisting>\r
</para></formalpara>\r
<simpara>When you use the <literal>LIMIT</literal> clause to restrict the total number of rows returned\r
by your query, you can also use the <literal>OFFSET</literal> clause to determine which subset\r
<simpara>In the following example, we use the <literal>OFFSET</literal> clause to get results 6 through\r
10 from the same query that we prevously executed.</simpara>\r
<formalpara><title>Using the <literal>OFFSET</literal> clause to return a specific subset of rows</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT circ_modifier, COUNT(circ_modifier)\r
- FROM asset.copy\r
- GROUP BY circ_modifier\r
- ORDER BY 2 DESC\r
- LIMIT 5\r
- OFFSET 5\r
- ;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT circ_modifier, COUNT(circ_modifier)\r
+ FROM asset.copy\r
+ GROUP BY circ_modifier\r
+ ORDER BY 2 DESC\r
+ LIMIT 5\r
+ OFFSET 5\r
+;\r
\r
- circ_modifier | count\r
- ---------------+--------\r
- LAW SERIAL | 102758\r
- DOCUMENTS | 86215\r
- BOOK_WEB | 63786\r
- MFORM SER | 39917\r
- REF | 34380\r
- (5 rows)</programlisting>\r
+ circ_modifier | count\r
+---------------+--------\r
+ LAW SERIAL | 102758\r
+ DOCUMENTS | 86215\r
+ BOOK_WEB | 63786\r
+ MFORM SER | 39917\r
+ REF | 34380\r
+(5 rows)\r
+</programlisting>\r
</para></formalpara>\r
</simplesect>\r
</section>\r
used to convert text values to upper-case, modifies the results in the\r
following set of queries:</simpara>\r
<formalpara><title>Using the UPPER() scalar function to convert text values to upper-case</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">-- First, without the UPPER() function for comparison\r
- SELECT shortname, name\r
- FROM actor.org_unit\r
- WHERE id < 4\r
- ;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+-- First, without the UPPER() function for comparison\r
+SELECT shortname, name\r
+ FROM actor.org_unit\r
+ WHERE id < 4\r
+;\r
\r
- shortname | name\r
- -----------+-----------------------\r
- CONS | Example Consortium\r
- SYS1 | Example System 1\r
- SYS2 | Example System 2\r
- (3 rows)\r
+ shortname | name\r
+-----------+-----------------------\r
+ CONS | Example Consortium\r
+ SYS1 | Example System 1\r
+ SYS2 | Example System 2\r
+(3 rows)\r
\r
- -- Now apply the UPPER() function to the name column\r
- SELECT shortname, UPPER(name)\r
- FROM actor.org_unit\r
- WHERE id < 4\r
- ;\r
+-- Now apply the UPPER() function to the name column\r
+SELECT shortname, UPPER(name)\r
+ FROM actor.org_unit\r
+ WHERE id < 4\r
+;\r
\r
- shortname | upper\r
- -----------+--------------------\r
- CONS | EXAMPLE CONSORTIUM\r
- SYS1 | EXAMPLE SYSTEM 1\r
- SYS2 | EXAMPLE SYSTEM 2\r
- (3 rows)</programlisting>\r
+ shortname | upper\r
+-----------+--------------------\r
+ CONS | EXAMPLE CONSORTIUM\r
+ SYS1 | EXAMPLE SYSTEM 1\r
+ SYS2 | EXAMPLE SYSTEM 2\r
+(3 rows)\r
+</programlisting>\r
</para></formalpara>\r
<simpara>There are so many scalar functions in PostgreSQL that we cannot cover them\r
all here, but we can list some of the most commonly used functions:</simpara>\r
returned by the main SELECT statement to only those locations that have an\r
<literal>opac_visible</literal> value of <literal>TRUE</literal>:</simpara>\r
<formalpara><title>Sub-select example</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT call_number\r
- FROM asset.copy\r
- WHERE deleted IS FALSE\r
- AND location IN (\r
- SELECT id\r
- FROM asset.copy_location\r
- WHERE opac_visible IS TRUE\r
- )\r
- ;</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT call_number\r
+ FROM asset.copy\r
+ WHERE deleted IS FALSE\r
+ AND location IN (\r
+ SELECT id\r
+ FROM asset.copy_location\r
+ WHERE opac_visible IS TRUE\r
+ )\r
+;\r
+</programlisting>\r
</para></formalpara>\r
<simpara>Sub-selects can be an approachable way to breaking down a problem that\r
requires matching values between different tables, and often result in\r
we have to fully qualify the column names in our queries with the schema and\r
table names.</simpara>\r
<formalpara><title>A simple inner join</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT *\r
- FROM actor.usr\r
- INNER JOIN actor.org_unit ON actor.usr.home_ou = actor.org_unit.id\r
- WHERE actor.org_unit.shortname = 'CONS'\r
- ;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT *\r
+ FROM actor.usr\r
+ INNER JOIN actor.org_unit ON actor.usr.home_ou = actor.org_unit.id\r
+ WHERE actor.org_unit.shortname = 'CONS'\r
+;\r
\r
- -[ RECORD 1 ]------------------+---------------------------------\r
- id | 1\r
- card | 1\r
- profile | 1\r
- usrname | admin\r
- email |\r
- ...\r
- mailing_address |\r
- billing_address |\r
- home_ou | 1\r
- ...\r
- claims_never_checked_out_count | 0\r
- id | 1\r
- parent_ou |\r
- ou_type | 1\r
- ill_address | 1\r
- holds_address | 1\r
- mailing_address | 1\r
- billing_address | 1\r
- shortname | CONS\r
- name | Example Consortium\r
- email |\r
- phone |\r
- opac_visible | t\r
- fiscal_calendar | 1</programlisting>\r
+-[ RECORD 1 ]------------------+---------------------------------\r
+id | 1\r
+card | 1\r
+profile | 1\r
+usrname | admin\r
+email |\r
+...\r
+mailing_address |\r
+billing_address |\r
+home_ou | 1\r
+...\r
+claims_never_checked_out_count | 0\r
+id | 1\r
+parent_ou |\r
+ou_type | 1\r
+ill_address | 1\r
+holds_address | 1\r
+mailing_address | 1\r
+billing_address | 1\r
+shortname | CONS\r
+name | Example Consortium\r
+email |\r
+phone |\r
+opac_visible | t\r
+fiscal_calendar | 1\r
+</programlisting>\r
</para></formalpara>\r
<simpara>Of course, you do not have to return every column from the joined tables;\r
you can (and should) continue to specify only the columns that you want to\r
joining the <literal>actor.org_unit</literal> table to give us access to the user’s home\r
library:</simpara>\r
<formalpara><title>Borrower Count by Profile (Adult, Child, etc)/Library</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT permission.grp_tree.name, actor.org_unit.name, COUNT(permission.grp_tree.name)\r
- FROM actor.usr\r
- INNER JOIN permission.grp_tree\r
- ON actor.usr.profile = permission.grp_tree.id\r
- INNER JOIN actor.org_unit\r
- ON actor.org_unit.id = actor.usr.home_ou\r
- WHERE actor.usr.deleted IS FALSE\r
- GROUP BY permission.grp_tree.name, actor.org_unit.name\r
- ORDER BY actor.org_unit.name, permission.grp_tree.name\r
- ;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT permission.grp_tree.name, actor.org_unit.name, COUNT(permission.grp_tree.name)\r
+ FROM actor.usr\r
+ INNER JOIN permission.grp_tree\r
+ ON actor.usr.profile = permission.grp_tree.id\r
+ INNER JOIN actor.org_unit\r
+ ON actor.org_unit.id = actor.usr.home_ou\r
+ WHERE actor.usr.deleted IS FALSE\r
+ GROUP BY permission.grp_tree.name, actor.org_unit.name\r
+ ORDER BY actor.org_unit.name, permission.grp_tree.name\r
+;\r
\r
- name | name | count\r
- -------+--------------------+-------\r
- Users | Example Consortium | 1\r
- (1 row)</programlisting>\r
+ name | name | count\r
+-------+--------------------+-------\r
+ Users | Example Consortium | 1\r
+(1 row)\r
+</programlisting>\r
</para></formalpara>\r
</simplesect>\r
<simplesect id="_aliases">\r
example, we can write the previous INNER JOIN statement example using aliases\r
instead of fully-qualified identifiers:</simpara>\r
<formalpara><title>Borrower Count by Profile (using aliases)</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT pgt.name AS "Profile", aou.name AS "Library", COUNT(pgt.name) AS "Count"\r
- FROM actor.usr au\r
- INNER JOIN permission.grp_tree pgt\r
- ON au.profile = pgt.id\r
- INNER JOIN actor.org_unit aou\r
- ON aou.id = au.home_ou\r
- WHERE au.deleted IS FALSE\r
- GROUP BY pgt.name, aou.name\r
- ORDER BY aou.name, pgt.name\r
- ;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT pgt.name AS "Profile", aou.name AS "Library", COUNT(pgt.name) AS "Count"\r
+ FROM actor.usr au\r
+ INNER JOIN permission.grp_tree pgt\r
+ ON au.profile = pgt.id\r
+ INNER JOIN actor.org_unit aou\r
+ ON aou.id = au.home_ou\r
+ WHERE au.deleted IS FALSE\r
+ GROUP BY pgt.name, aou.name\r
+ ORDER BY aou.name, pgt.name\r
+;\r
\r
- Profile | Library | Count\r
- ---------+--------------------+-------\r
- Users | Example Consortium | 1\r
- (1 row)</programlisting>\r
+ Profile | Library | Count\r
+---------+--------------------+-------\r
+ Users | Example Consortium | 1\r
+(1 row)\r
+</programlisting>\r
</para></formalpara>\r
<simpara>A nice side effect of declaring an alias for your columns is that the alias\r
is used as the column header in the results table. The previous version of\r
</listitem>\r
</itemizedlist>\r
<formalpara><title>Base tables for the OUTER JOIN examples</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT * FROM aaa;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT * FROM aaa;\r
\r
- id | stuff\r
- ----+-------\r
- 1 | one\r
- 2 | two\r
- 3 | three\r
- 4 | four\r
- 5 | five\r
- (5 rows)\r
+ id | stuff\r
+----+-------\r
+ 1 | one\r
+ 2 | two\r
+ 3 | three\r
+ 4 | four\r
+ 5 | five\r
+(5 rows)\r
\r
- SELECT * FROM bbb;\r
+SELECT * FROM bbb;\r
\r
- id | stuff | foo\r
- ----+-------+----------\r
- 1 | one | oneone\r
- 2 | two | twotwo\r
- 5 | five | fivefive\r
- 6 | six | sixsix\r
- (4 rows)</programlisting>\r
+ id | stuff | foo\r
+----+-------+----------\r
+ 1 | one | oneone\r
+ 2 | two | twotwo\r
+ 5 | five | fivefive\r
+ 6 | six | sixsix\r
+(4 rows)\r
+</programlisting>\r
</para></formalpara>\r
<formalpara><title>Example of a LEFT OUTER JOIN</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT * FROM aaa\r
- LEFT OUTER JOIN bbb ON aaa.id = bbb.id\r
- ;\r
- id | stuff | id | stuff | foo\r
- ----+-------+----+-------+----------\r
- 1 | one | 1 | one | oneone\r
- 2 | two | 2 | two | twotwo\r
- 3 | three | | |\r
- 4 | four | | |\r
- 5 | five | 5 | five | fivefive\r
- (5 rows)</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT * FROM aaa\r
+ LEFT OUTER JOIN bbb ON aaa.id = bbb.id\r
+;\r
+ id | stuff | id | stuff | foo\r
+----+-------+----+-------+----------\r
+ 1 | one | 1 | one | oneone\r
+ 2 | two | 2 | two | twotwo\r
+ 3 | three | | |\r
+ 4 | four | | |\r
+ 5 | five | 5 | five | fivefive\r
+(5 rows)\r
+</programlisting>\r
</para></formalpara>\r
<formalpara><title>Example of a RIGHT OUTER JOIN</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT * FROM aaa\r
- RIGHT OUTER JOIN bbb ON aaa.id = bbb.id\r
- ;\r
- id | stuff | id | stuff | foo\r
- ----+-------+----+-------+----------\r
- 1 | one | 1 | one | oneone\r
- 2 | two | 2 | two | twotwo\r
- 5 | five | 5 | five | fivefive\r
- | | 6 | six | sixsix\r
- (4 rows)</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT * FROM aaa\r
+ RIGHT OUTER JOIN bbb ON aaa.id = bbb.id\r
+;\r
+ id | stuff | id | stuff | foo\r
+----+-------+----+-------+----------\r
+ 1 | one | 1 | one | oneone\r
+ 2 | two | 2 | two | twotwo\r
+ 5 | five | 5 | five | fivefive\r
+ | | 6 | six | sixsix\r
+(4 rows)\r
+</programlisting>\r
</para></formalpara>\r
<formalpara><title>Example of a FULL OUTER JOIN</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT * FROM aaa\r
- FULL OUTER JOIN bbb ON aaa.id = bbb.id\r
- ;\r
- id | stuff | id | stuff | foo\r
- ----+-------+----+-------+----------\r
- 1 | one | 1 | one | oneone\r
- 2 | two | 2 | two | twotwo\r
- 3 | three | | |\r
- 4 | four | | |\r
- 5 | five | 5 | five | fivefive\r
- | | 6 | six | sixsix\r
- (6 rows)</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT * FROM aaa\r
+ FULL OUTER JOIN bbb ON aaa.id = bbb.id\r
+;\r
+ id | stuff | id | stuff | foo\r
+----+-------+----+-------+----------\r
+ 1 | one | 1 | one | oneone\r
+ 2 | two | 2 | two | twotwo\r
+ 3 | three | | |\r
+ 4 | four | | |\r
+ 5 | five | 5 | five | fivefive\r
+ | | 6 | six | sixsix\r
+(6 rows)\r
+</programlisting>\r
</para></formalpara>\r
</simplesect>\r
<simplesect id="_self_joins">\r
of columns with compatible data types: the union, intersection, and difference\r
operators.</simpara>\r
<formalpara><title>Base tables for the set operation examples</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT * FROM aaa;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT * FROM aaa;\r
\r
- id | stuff\r
- ----+-------\r
- 1 | one\r
- 2 | two\r
- 3 | three\r
- 4 | four\r
- 5 | five\r
- (5 rows)\r
+ id | stuff\r
+ ----+-------\r
+ 1 | one\r
+ 2 | two\r
+ 3 | three\r
+ 4 | four\r
+ 5 | five\r
+ (5 rows)\r
\r
- SELECT * FROM bbb;\r
+SELECT * FROM bbb;\r
\r
- id | stuff | foo\r
- ----+-------+----------\r
- 1 | one | oneone\r
- 2 | two | twotwo\r
- 5 | five | fivefive\r
- 6 | six | sixsix\r
- (4 rows)</programlisting>\r
+ id | stuff | foo\r
+ ----+-------+----------\r
+ 1 | one | oneone\r
+ 2 | two | twotwo\r
+ 5 | five | fivefive\r
+ 6 | six | sixsix\r
+(4 rows)\r
+</programlisting>\r
</para></formalpara>\r
<simplesect id="_union">\r
<title>Union</title>\r
does not return any duplicate rows. To return duplicate rows, use the\r
<literal>UNION ALL</literal> operator.</simpara>\r
<formalpara><title>Example of a UNION set operation</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">-- The parentheses are not required, but are intended to help\r
- -- illustrate the sets participating in the set operation\r
- (\r
- SELECT id, stuff\r
- FROM aaa\r
- )\r
- UNION\r
- (\r
- SELECT id, stuff\r
- FROM bbb\r
- )\r
- ORDER BY 1\r
- ;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+-- The parentheses are not required, but are intended to help\r
+-- illustrate the sets participating in the set operation\r
+(\r
+ SELECT id, stuff\r
+ FROM aaa\r
+)\r
+UNION\r
+(\r
+ SELECT id, stuff\r
+ FROM bbb\r
+)\r
+ORDER BY 1\r
+;\r
\r
- id | stuff\r
- ----+-------\r
- 1 | one\r
- 2 | two\r
- 3 | three\r
- 4 | four\r
- 5 | five\r
- 6 | six\r
- (6 rows)</programlisting>\r
+ id | stuff\r
+----+-------\r
+ 1 | one\r
+ 2 | two\r
+ 3 | three\r
+ 4 | four\r
+ 5 | five\r
+ 6 | six\r
+(6 rows)\r
+</programlisting>\r
</para></formalpara>\r
</simplesect>\r
<simplesect id="_intersection">\r
both the left-hand and right-hand tables. To return duplicate rows, use the\r
<literal>INTERSECT ALL</literal> operator.</simpara>\r
<formalpara><title>Example of an INTERSECT set operation</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">(\r
- SELECT id, stuff\r
- FROM aaa\r
- )\r
- INTERSECT\r
- (\r
- SELECT id, stuff\r
- FROM bbb\r
- )\r
- ORDER BY 1\r
- ;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+(\r
+ SELECT id, stuff\r
+ FROM aaa\r
+)\r
+INTERSECT\r
+(\r
+ SELECT id, stuff\r
+ FROM bbb\r
+)\r
+ORDER BY 1\r
+;\r
\r
- id | stuff\r
- ----+-------\r
- 1 | one\r
- 2 | two\r
- 5 | five\r
- (3 rows)</programlisting>\r
+ id | stuff\r
+----+-------\r
+ 1 | one\r
+ 2 | two\r
+ 5 | five\r
+(3 rows)\r
+</programlisting>\r
</para></formalpara>\r
</simplesect>\r
<simplesect id="_difference">\r
exist in the right-hand table. You are effectively subtracting the common\r
rows from the left-hand table.</simpara>\r
<formalpara><title>Example of an EXCEPT set operation</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">(\r
- SELECT id, stuff\r
- FROM aaa\r
- )\r
- EXCEPT\r
- (\r
- SELECT id, stuff\r
- FROM bbb\r
- )\r
- ORDER BY 1\r
- ;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+(\r
+ SELECT id, stuff\r
+ FROM aaa\r
+)\r
+EXCEPT\r
+(\r
+ SELECT id, stuff\r
+ FROM bbb\r
+)\r
+ORDER BY 1\r
+;\r
\r
- id | stuff\r
- ----+-------\r
- 3 | three\r
- 4 | four\r
- (2 rows)\r
+ id | stuff\r
+----+-------\r
+ 3 | three\r
+ 4 | four\r
+(2 rows)\r
\r
- -- Order matters: switch the left-hand and right-hand tables\r
- -- and you get a different result\r
- (\r
- SELECT id, stuff\r
- FROM bbb\r
- )\r
- EXCEPT\r
- (\r
- SELECT id, stuff\r
- FROM aaa\r
- )\r
- ORDER BY 1\r
- ;\r
+-- Order matters: switch the left-hand and right-hand tables\r
+-- and you get a different result\r
+(\r
+ SELECT id, stuff\r
+ FROM bbb\r
+)\r
+EXCEPT\r
+(\r
+ SELECT id, stuff\r
+ FROM aaa\r
+)\r
+ORDER BY 1\r
+;\r
\r
- id | stuff\r
- ----+-------\r
- 6 | six\r
- (1 row)</programlisting>\r
+ id | stuff\r
+----+-------\r
+ 6 | six\r
+(1 row)\r
+</programlisting>\r
</para></formalpara>\r
</simplesect>\r
</simplesect>\r
and a <literal>SELECT</literal> statement on which the view is built.</simpara>\r
<simpara>The following example creates a view based on our borrower profile count:</simpara>\r
<formalpara><title>Creating a view</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">CREATE VIEW actor.borrower_profile_count AS\r
- SELECT pgt.name AS "Profile", aou.name AS "Library", COUNT(pgt.name) AS "Count"\r
- FROM actor.usr au\r
- INNER JOIN permission.grp_tree pgt\r
- ON au.profile = pgt.id\r
- INNER JOIN actor.org_unit aou\r
- ON aou.id = au.home_ou\r
- WHERE au.deleted IS FALSE\r
- GROUP BY pgt.name, aou.name\r
- ORDER BY aou.name, pgt.name\r
- ;</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+CREATE VIEW actor.borrower_profile_count AS\r
+ SELECT pgt.name AS "Profile", aou.name AS "Library", COUNT(pgt.name) AS "Count"\r
+ FROM actor.usr au\r
+ INNER JOIN permission.grp_tree pgt\r
+ ON au.profile = pgt.id\r
+ INNER JOIN actor.org_unit aou\r
+ ON aou.id = au.home_ou\r
+ WHERE au.deleted IS FALSE\r
+ GROUP BY pgt.name, aou.name\r
+ ORDER BY aou.name, pgt.name\r
+;\r
+</programlisting>\r
</para></formalpara>\r
<simpara>When you subsequently select results from the view, you can apply additional\r
<literal>WHERE</literal> clauses to filter the results, or <literal>ORDER BY</literal> clauses to change the\r
Then we issue a <literal>SELECT</literal> statement with a <literal>WHERE</literal> clause to further filter the\r
results.</simpara>\r
<formalpara><title>Selecting results from a view</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT * FROM actor.borrower_profile_count;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT * FROM actor.borrower_profile_count;\r
\r
- Profile | Library | Count\r
- ----------------------------+----------------------------+-------\r
- Faculty | University Library | 208\r
- Graduate | University Library | 16\r
- Patrons | University Library | 62\r
- ...\r
+ Profile | Library | Count\r
+----------------------------+----------------------------+-------\r
+ Faculty | University Library | 208\r
+ Graduate | University Library | 16\r
+ Patrons | University Library | 62\r
+...\r
\r
- -- You can still filter your results with WHERE clauses\r
- SELECT *\r
- FROM actor.borrower_profile_count\r
- WHERE "Profile" = 'Faculty';\r
+-- You can still filter your results with WHERE clauses\r
+SELECT *\r
+ FROM actor.borrower_profile_count\r
+ WHERE "Profile" = 'Faculty';\r
\r
- Profile | Library | Count\r
- ---------+----------------------------+-------\r
- Faculty | University Library | 208\r
- Faculty | College Library | 64\r
- Faculty | College Library 2 | 102\r
- Faculty | University Library 2 | 776\r
- (4 rows)</programlisting>\r
+ Profile | Library | Count\r
+---------+----------------------------+-------\r
+ Faculty | University Library | 208\r
+ Faculty | College Library | 64\r
+ Faculty | College Library 2 | 102\r
+ Faculty | University Library 2 | 776\r
+(4 rows)\r
+</programlisting>\r
</para></formalpara>\r
</simplesect>\r
<simplesect id="_inheritance">\r
row in the <literal>biblio.record_entry</literal> table with every row in the <literal>metabib.full_rec</literal>\r
view:</simpara>\r
<formalpara><title>Query plan for a terrible query</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">EXPLAIN SELECT *\r
- FROM biblio.record_entry\r
- FULL OUTER JOIN metabib.full_rec ON 1=1\r
- ;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+EXPLAIN SELECT *\r
+ FROM biblio.record_entry\r
+ FULL OUTER JOIN metabib.full_rec ON 1=1\r
+;\r
\r
- QUERY PLAN\r
- -------------------------------------------------------------------------------//\r
- Merge Full Join (cost=0.00..4959156437783.60 rows=132415734100864 width=1379)\r
- -> Seq Scan on record_entry (cost=0.00..400634.16 rows=2013416 width=1292)\r
- -> Seq Scan on real_full_rec (cost=0.00..1640972.04 rows=65766704 width=87)\r
- (3 rows)</programlisting>\r
+ QUERY PLAN\r
+-------------------------------------------------------------------------------//\r
+ Merge Full Join (cost=0.00..4959156437783.60 rows=132415734100864 width=1379)\r
+ -> Seq Scan on record_entry (cost=0.00..400634.16 rows=2013416 width=1292)\r
+ -> Seq Scan on real_full_rec (cost=0.00..1640972.04 rows=65766704 width=87)\r
+(3 rows)\r
+</programlisting>\r
</para></formalpara>\r
<simpara>This query plan shows that the query would return 132415734100864 rows, and it\r
plans to accomplish what you asked for by sequentially scanning (<emphasis>Seq Scan</emphasis>)\r
the left-hand table with every row in the right-hand table and take the saner\r
approach of using an <literal>INNER JOIN</literal> where the join condition is on the record ID.</simpara>\r
<formalpara><title>Query plan for a less terrible query</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">EXPLAIN SELECT *\r
- FROM biblio.record_entry bre\r
- INNER JOIN metabib.full_rec mfr ON mfr.record = bre.id;\r
- QUERY PLAN\r
- ----------------------------------------------------------------------------------------//\r
- Hash Join (cost=750229.86..5829273.98 rows=65766704 width=1379)\r
- Hash Cond: (real_full_rec.record = bre.id)\r
- -> Seq Scan on real_full_rec (cost=0.00..1640972.04 rows=65766704 width=87)\r
- -> Hash (cost=400634.16..400634.16 rows=2013416 width=1292)\r
- -> Seq Scan on record_entry bre (cost=0.00..400634.16 rows=2013416 width=1292)\r
- (5 rows)</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+EXPLAIN SELECT *\r
+ FROM biblio.record_entry bre\r
+ INNER JOIN metabib.full_rec mfr ON mfr.record = bre.id;\r
+ QUERY PLAN\r
+----------------------------------------------------------------------------------------//\r
+ Hash Join (cost=750229.86..5829273.98 rows=65766704 width=1379)\r
+ Hash Cond: (real_full_rec.record = bre.id)\r
+ -> Seq Scan on real_full_rec (cost=0.00..1640972.04 rows=65766704 width=87)\r
+ -> Hash (cost=400634.16..400634.16 rows=2013416 width=1292)\r
+ -> Seq Scan on record_entry bre (cost=0.00..400634.16 rows=2013416 width=1292)\r
+(5 rows)\r
+</programlisting>\r
</para></formalpara>\r
<simpara>This time, we will return 65766704 rows - still way too many rows. We forgot\r
to include a <literal>WHERE</literal> clause to limit the results to something meaningful. In\r
the following example, we will limit the results to deleted records that were\r
modified in the last month.</simpara>\r
<formalpara><title>Query plan for a realistic query</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">EXPLAIN SELECT *\r
- FROM biblio.record_entry bre\r
- INNER JOIN metabib.full_rec mfr ON mfr.record = bre.id\r
- WHERE bre.deleted IS TRUE\r
- AND DATE_TRUNC('MONTH', bre.edit_date) >\r
- DATE_TRUNC ('MONTH', NOW() - '1 MONTH'::INTERVAL)\r
- ;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+EXPLAIN SELECT *\r
+ FROM biblio.record_entry bre\r
+ INNER JOIN metabib.full_rec mfr ON mfr.record = bre.id\r
+ WHERE bre.deleted IS TRUE\r
+ AND DATE_TRUNC('MONTH', bre.edit_date) >\r
+ DATE_TRUNC ('MONTH', NOW() - '1 MONTH'::INTERVAL)\r
+;\r
\r
- QUERY PLAN\r
- ----------------------------------------------------------------------------------------//\r
- Hash Join (cost=5058.86..2306218.81 rows=201669 width=1379)\r
- Hash Cond: (real_full_rec.record = bre.id)\r
- -> Seq Scan on real_full_rec (cost=0.00..1640972.04 rows=65766704 width=87)\r
- -> Hash (cost=4981.69..4981.69 rows=6174 width=1292)\r
- -> Index Scan using biblio_record_entry_deleted on record_entry bre\r
- (cost=0.00..4981.69 rows=6174 width=1292)\r
- Index Cond: (deleted = true)\r
- Filter: ((deleted IS TRUE) AND (date_trunc('MONTH'::text, edit_date)\r
- > date_trunc('MONTH'::text, (now() - '1 mon'::interval))))\r
- (7 rows)</programlisting>\r
+ QUERY PLAN\r
+----------------------------------------------------------------------------------------//\r
+ Hash Join (cost=5058.86..2306218.81 rows=201669 width=1379)\r
+ Hash Cond: (real_full_rec.record = bre.id)\r
+ -> Seq Scan on real_full_rec (cost=0.00..1640972.04 rows=65766704 width=87)\r
+ -> Hash (cost=4981.69..4981.69 rows=6174 width=1292)\r
+ -> Index Scan using biblio_record_entry_deleted on record_entry bre\r
+ (cost=0.00..4981.69 rows=6174 width=1292)\r
+ Index Cond: (deleted = true)\r
+ Filter: ((deleted IS TRUE) AND (date_trunc('MONTH'::text, edit_date)\r
+ > date_trunc('MONTH'::text, (now() - '1 mon'::interval))))\r
+(7 rows)\r
+</programlisting>\r
</para></formalpara>\r
<simpara>We can see that the number of rows returned is now only 201669; that’s\r
something we can work with. Also, the overall cost of the query is 2306218,\r
flattened MARC subfields is a fairly common operation, we could create a\r
new index and see if that speeds up our query plan.</simpara>\r
<formalpara><title>Query plan with optimized access via a new index</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">-- This index will take a long time to create on a large database\r
- -- of bibliographic records\r
- CREATE INDEX bib_record_idx ON metabib.real_full_rec (record);\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+-- This index will take a long time to create on a large database\r
+-- of bibliographic records\r
+CREATE INDEX bib_record_idx ON metabib.real_full_rec (record);\r
\r
- EXPLAIN SELECT *\r
- FROM biblio.record_entry bre\r
- INNER JOIN metabib.full_rec mfr ON mfr.record = bre.id\r
- WHERE bre.deleted IS TRUE\r
- AND DATE_TRUNC('MONTH', bre.edit_date) >\r
- DATE_TRUNC ('MONTH', NOW() - '1 MONTH'::INTERVAL)\r
- ;\r
+EXPLAIN SELECT *\r
+ FROM biblio.record_entry bre\r
+ INNER JOIN metabib.full_rec mfr ON mfr.record = bre.id\r
+ WHERE bre.deleted IS TRUE\r
+ AND DATE_TRUNC('MONTH', bre.edit_date) >\r
+ DATE_TRUNC ('MONTH', NOW() - '1 MONTH'::INTERVAL)\r
+;\r
\r
- QUERY PLAN\r
- ----------------------------------------------------------------------------------------//\r
- Nested Loop (cost=0.00..1558330.46 rows=201669 width=1379)\r
- -> Index Scan using biblio_record_entry_deleted on record_entry bre\r
- (cost=0.00..4981.69 rows=6174 width=1292)\r
- Index Cond: (deleted = true)\r
- Filter: ((deleted IS TRUE) AND (date_trunc('MONTH'::text, edit_date) >\r
- date_trunc('MONTH'::text, (now() - '1 mon'::interval))))\r
- -> Index Scan using bib_record_idx on real_full_rec\r
- (cost=0.00..240.89 rows=850 width=87)\r
- Index Cond: (real_full_rec.record = bre.id)\r
- (6 rows)</programlisting>\r
+ QUERY PLAN\r
+----------------------------------------------------------------------------------------//\r
+ Nested Loop (cost=0.00..1558330.46 rows=201669 width=1379)\r
+ -> Index Scan using biblio_record_entry_deleted on record_entry bre\r
+ (cost=0.00..4981.69 rows=6174 width=1292)\r
+ Index Cond: (deleted = true)\r
+ Filter: ((deleted IS TRUE) AND (date_trunc('MONTH'::text, edit_date) >\r
+ date_trunc('MONTH'::text, (now() - '1 mon'::interval))))\r
+ -> Index Scan using bib_record_idx on real_full_rec\r
+ (cost=0.00..240.89 rows=850 width=87)\r
+ Index Cond: (real_full_rec.record = bre.id)\r
+(6 rows)\r
+</programlisting>\r
</para></formalpara>\r
<simpara>We can see that the resulting number of rows is still the same (201669), but\r
the execution estimate has dropped to 1558330 because the query planner can\r
<simpara>Of course, as with the rest of SQL, you can replace individual column values\r
with one or more use sub-selects:</simpara>\r
<formalpara><title>Inserting rows using sub-selects instead of integers</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">INSERT INTO permission.usr_grp_map (usr, grp)\r
- VALUES (\r
- (SELECT id FROM actor.usr\r
- WHERE family_name = 'Scott' AND first_given_name = 'Daniel'),\r
- (SELECT id FROM permission.grp_tree\r
- WHERE name = 'Local System Administrator')\r
- ), (\r
- (SELECT id FROM actor.usr\r
- WHERE family_name = 'Scott' AND first_given_name = 'Daniel'),\r
- (SELECT id FROM permission.grp_tree\r
- WHERE name = 'Circulator')\r
- )\r
- ;</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+INSERT INTO permission.usr_grp_map (usr, grp)\r
+ VALUES (\r
+ (SELECT id FROM actor.usr\r
+ WHERE family_name = 'Scott' AND first_given_name = 'Daniel'),\r
+ (SELECT id FROM permission.grp_tree\r
+ WHERE name = 'Local System Administrator')\r
+ ), (\r
+ (SELECT id FROM actor.usr\r
+ WHERE family_name = 'Scott' AND first_given_name = 'Daniel'),\r
+ (SELECT id FROM permission.grp_tree\r
+ WHERE name = 'Circulator')\r
+ )\r
+;\r
+</programlisting>\r
</para></formalpara>\r
</simplesect>\r
<simplesect id="_inserting_data_using_a_select_statement">\r
the <literal>grp</literal> column value based on the <literal>id</literal> column values returned from\r
<literal>permission.grp_tree</literal>:</simpara>\r
<formalpara><title>Inserting rows via a <literal>SELECT</literal> statement</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">INSERT INTO permission.usr_grp_map (usr, grp)\r
- SELECT 1, id\r
- FROM permission.grp_tree\r
- WHERE id > 2\r
- ;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+INSERT INTO permission.usr_grp_map (usr, grp)\r
+ SELECT 1, id\r
+ FROM permission.grp_tree\r
+ WHERE id > 2\r
+;\r
\r
- INSERT 0 6</programlisting>\r
+INSERT 0 6\r
+</programlisting>\r
</para></formalpara>\r
</simplesect>\r
<simplesect id="_deleting_rows">\r
<literal>permission.grp_perm_map</literal> table where the permission maps to\r
<literal>UPDATE_ORG_UNIT_CLOSING</literal> and the group is anything other than administrators:</simpara>\r
<formalpara><title>Deleting rows from a table</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">DELETE FROM permission.grp_perm_map\r
- WHERE grp IN (\r
- SELECT id\r
- FROM permission.grp_tree\r
- WHERE name != 'Local System Administrator'\r
- ) AND perm = (\r
- SELECT id\r
- FROM permission.perm_list\r
- WHERE code = 'UPDATE_ORG_UNIT_CLOSING'\r
- )\r
- ;</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+DELETE FROM permission.grp_perm_map\r
+ WHERE grp IN (\r
+ SELECT id\r
+ FROM permission.grp_tree\r
+ WHERE name != 'Local System Administrator'\r
+ ) AND perm = (\r
+ SELECT id\r
+ FROM permission.perm_list\r
+ WHERE code = 'UPDATE_ORG_UNIT_CLOSING'\r
+ )\r
+;\r
+</programlisting>\r
</para></formalpara>\r
<note><simpara>There are two main reasons that a <literal>DELETE</literal> statement may not actually\r
delete rows from a table, even when the rows meet the conditional clause.</simpara></note>\r
<orderedlist numeration="arabic">\r
<listitem>\r
-\r
<simpara>\r
If the row contains a value that is the target of a relational constraint,\r
for example, if another table has a foreign key pointing at your target\r
<simplesect id="_monthly_circulation_stats_by_collection_code_library">\r
<title>Monthly circulation stats by collection code / library</title>\r
<formalpara><title>Monthly Circulation Stats by Collection Code/Library</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT COUNT(acirc.id) AS "COUNT", aou.name AS "Library", acl.name AS "Copy Location"\r
- FROM asset.copy ac\r
- INNER JOIN asset.copy_location acl ON ac.location = acl.id\r
- INNER JOIN action.circulation acirc ON acirc.target_copy = ac.id\r
- INNER JOIN actor.org_unit aou ON acirc.circ_lib = aou.id\r
- WHERE DATE_TRUNC('MONTH', acirc.create_time) = DATE_TRUNC('MONTH', NOW() - INTERVAL '3 month')\r
- AND acirc.desk_renewal IS FALSE\r
- AND acirc.opac_renewal IS FALSE\r
- AND acirc.phone_renewal IS FALSE\r
- GROUP BY aou.name, acl.name\r
- ORDER BY aou.name, acl.name, 1\r
- ;</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT COUNT(acirc.id) AS "COUNT", aou.name AS "Library", acl.name AS "Copy Location"\r
+ FROM asset.copy ac\r
+ INNER JOIN asset.copy_location acl ON ac.location = acl.id\r
+ INNER JOIN action.circulation acirc ON acirc.target_copy = ac.id\r
+ INNER JOIN actor.org_unit aou ON acirc.circ_lib = aou.id\r
+ WHERE DATE_TRUNC('MONTH', acirc.create_time) = DATE_TRUNC('MONTH', NOW() - INTERVAL '3 month')\r
+ AND acirc.desk_renewal IS FALSE\r
+ AND acirc.opac_renewal IS FALSE\r
+ AND acirc.phone_renewal IS FALSE\r
+ GROUP BY aou.name, acl.name\r
+ ORDER BY aou.name, acl.name, 1\r
+;\r
+</programlisting>\r
</para></formalpara>\r
</simplesect>\r
<simplesect id="_monthly_circulation_stats_by_borrower_stat_library">\r
<title>Monthly circulation stats by borrower stat / library</title>\r
<formalpara><title>Monthly Circulation Stats by Borrower Stat/Library</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT COUNT(acirc.id) AS "COUNT", aou.name AS "Library", asceum.stat_cat_entry AS "Borrower Stat"\r
- FROM action.circulation acirc\r
- INNER JOIN actor.org_unit aou ON acirc.circ_lib = aou.id\r
- INNER JOIN actor.stat_cat_entry_usr_map asceum ON asceum.target_usr = acirc.usr\r
- INNER JOIN actor.stat_cat astat ON asceum.stat_cat = astat.id\r
- WHERE DATE_TRUNC('MONTH', acirc.create_time) = DATE_TRUNC('MONTH', NOW() - INTERVAL '3 month')\r
- AND astat.name = 'Preferred language'\r
- AND acirc.desk_renewal IS FALSE\r
- AND acirc.opac_renewal IS FALSE\r
- AND acirc.phone_renewal IS FALSE\r
- GROUP BY aou.name, asceum.stat_cat_entry\r
- ORDER BY aou.name, asceum.stat_cat_entry, 1\r
- ;</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT COUNT(acirc.id) AS "COUNT", aou.name AS "Library", asceum.stat_cat_entry AS "Borrower Stat"\r
+ FROM action.circulation acirc\r
+ INNER JOIN actor.org_unit aou ON acirc.circ_lib = aou.id\r
+ INNER JOIN actor.stat_cat_entry_usr_map asceum ON asceum.target_usr = acirc.usr\r
+ INNER JOIN actor.stat_cat astat ON asceum.stat_cat = astat.id\r
+ WHERE DATE_TRUNC('MONTH', acirc.create_time) = DATE_TRUNC('MONTH', NOW() - INTERVAL '3 month')\r
+ AND astat.name = 'Preferred language'\r
+ AND acirc.desk_renewal IS FALSE\r
+ AND acirc.opac_renewal IS FALSE\r
+ AND acirc.phone_renewal IS FALSE\r
+ GROUP BY aou.name, asceum.stat_cat_entry\r
+ ORDER BY aou.name, asceum.stat_cat_entry, 1\r
+;\r
+</programlisting>\r
</para></formalpara>\r
</simplesect>\r
<simplesect id="_monthly_intralibrary_loan_stats_by_library">\r
<title>Monthly intralibrary loan stats by library</title>\r
<formalpara><title>Monthly Intralibrary Loan Stats by Library</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT aou.name AS "Library", COUNT(acirc.id)\r
- FROM action.circulation acirc\r
- INNER JOIN actor.org_unit aou ON acirc.circ_lib = aou.id\r
- INNER JOIN asset.copy ac ON acirc.target_copy = ac.id\r
- INNER JOIN asset.call_number acn ON ac.call_number = acn.id\r
- WHERE acirc.circ_lib != acn.owning_lib\r
- AND DATE_TRUNC('MONTH', acirc.create_time) = DATE_TRUNC('MONTH', NOW() - INTERVAL '3 month')\r
- AND acirc.desk_renewal IS FALSE\r
- AND acirc.opac_renewal IS FALSE\r
- AND acirc.phone_renewal IS FALSE\r
- GROUP by aou.name\r
- ORDER BY aou.name, 2\r
- ;</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT aou.name AS "Library", COUNT(acirc.id)\r
+ FROM action.circulation acirc\r
+ INNER JOIN actor.org_unit aou ON acirc.circ_lib = aou.id\r
+ INNER JOIN asset.copy ac ON acirc.target_copy = ac.id\r
+ INNER JOIN asset.call_number acn ON ac.call_number = acn.id\r
+ WHERE acirc.circ_lib != acn.owning_lib\r
+ AND DATE_TRUNC('MONTH', acirc.create_time) = DATE_TRUNC('MONTH', NOW() - INTERVAL '3 month')\r
+ AND acirc.desk_renewal IS FALSE\r
+ AND acirc.opac_renewal IS FALSE\r
+ AND acirc.phone_renewal IS FALSE\r
+ GROUP by aou.name\r
+ ORDER BY aou.name, 2\r
+;\r
+</programlisting>\r
</para></formalpara>\r
</simplesect>\r
<simplesect id="_monthly_borrowers_added_by_profile_adult_child_etc_library">\r
<title>Monthly borrowers added by profile (adult, child, etc) / library</title>\r
<formalpara><title>Monthly Borrowers Added by Profile (Adult, Child, etc)/Library</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT pgt.name AS "Profile", aou.name AS "Library", COUNT(pgt.name) AS "Count"\r
- FROM actor.usr au\r
- INNER JOIN permission.grp_tree pgt\r
- ON au.profile = pgt.id\r
- INNER JOIN actor.org_unit aou\r
- ON aou.id = au.home_ou\r
- WHERE au.deleted IS FALSE\r
- AND DATE_TRUNC('MONTH', au.create_date) = DATE_TRUNC('MONTH', NOW() - '3 months'::interval)\r
- GROUP BY pgt.name, aou.name\r
- ORDER BY aou.name, pgt.name\r
- ;</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT pgt.name AS "Profile", aou.name AS "Library", COUNT(pgt.name) AS "Count"\r
+ FROM actor.usr au\r
+ INNER JOIN permission.grp_tree pgt\r
+ ON au.profile = pgt.id\r
+ INNER JOIN actor.org_unit aou\r
+ ON aou.id = au.home_ou\r
+ WHERE au.deleted IS FALSE\r
+ AND DATE_TRUNC('MONTH', au.create_date) = DATE_TRUNC('MONTH', NOW() - '3 months'::interval)\r
+ GROUP BY pgt.name, aou.name\r
+ ORDER BY aou.name, pgt.name\r
+;\r
+</programlisting>\r
</para></formalpara>\r
</simplesect>\r
<simplesect id="_borrower_count_by_profile_adult_child_etc_library">\r
<title>Borrower count by profile (adult, child, etc) / library</title>\r
<formalpara><title>Borrower Count by Profile (Adult, Child, etc)/Library</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT pgt.name AS "Profile", aou.name AS "Library", COUNT(pgt.name) AS "Count"\r
- FROM actor.usr au\r
- INNER JOIN permission.grp_tree pgt\r
- ON au.profile = pgt.id\r
- INNER JOIN actor.org_unit aou\r
- ON aou.id = au.home_ou\r
- WHERE au.deleted IS FALSE\r
- GROUP BY pgt.name, aou.name\r
- ORDER BY aou.name, pgt.name\r
- ;</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT pgt.name AS "Profile", aou.name AS "Library", COUNT(pgt.name) AS "Count"\r
+ FROM actor.usr au\r
+ INNER JOIN permission.grp_tree pgt\r
+ ON au.profile = pgt.id\r
+ INNER JOIN actor.org_unit aou\r
+ ON aou.id = au.home_ou\r
+ WHERE au.deleted IS FALSE\r
+ GROUP BY pgt.name, aou.name\r
+ ORDER BY aou.name, pgt.name\r
+;\r
+</programlisting>\r
</para></formalpara>\r
</simplesect>\r
<simplesect id="_monthly_items_added_by_collection_library">\r
<title>Monthly items added by collection / library</title>\r
- <simpara>We define a "collection" as a shelving location in Evergreen.</simpara>\r
+ <simpara>We define a <quote>collection</quote> as a shelving location in Evergreen.</simpara>\r
<formalpara><title>Monthly Items Added by Collection/Library</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">SELECT aou.name AS "Library", acl.name, COUNT(ac.barcode)\r
- FROM actor.org_unit aou\r
- INNER JOIN asset.call_number acn ON acn.owning_lib = aou.id\r
- INNER JOIN asset.copy ac ON ac.call_number = acn.id\r
- INNER JOIN asset.copy_location acl ON ac.location = acl.id\r
- WHERE ac.deleted IS FALSE\r
- AND acn.deleted IS FALSE\r
- AND DATE_TRUNC('MONTH', ac.create_date) = DATE_TRUNC('MONTH', NOW() - '1 month'::interval)\r
- GROUP BY aou.name, acl.name\r
- ORDER BY aou.name, acl.name\r
- ;</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+SELECT aou.name AS "Library", acl.name, COUNT(ac.barcode)\r
+ FROM actor.org_unit aou\r
+ INNER JOIN asset.call_number acn ON acn.owning_lib = aou.id\r
+ INNER JOIN asset.copy ac ON ac.call_number = acn.id\r
+ INNER JOIN asset.copy_location acl ON ac.location = acl.id\r
+ WHERE ac.deleted IS FALSE\r
+ AND acn.deleted IS FALSE\r
+ AND DATE_TRUNC('MONTH', ac.create_date) = DATE_TRUNC('MONTH', NOW() - '1 month'::interval)\r
+ GROUP BY aou.name, acl.name\r
+ ORDER BY aou.name, acl.name\r
+;\r
+</programlisting>\r
</para></formalpara>\r
</simplesect>\r
<simplesect id="_hold_purchase_alert_by_library">\r
bibliographic record ID and display the title / author information for those\r
records that have more than a given threshold of holds.</simpara>\r
<formalpara><title>Hold Purchase Alert by Library</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">-- Title holds\r
- SELECT all_holds.bib_id, aou.name, rmsr.title, rmsr.author, COUNT(all_holds.bib_id)\r
- FROM\r
- (\r
- (\r
- SELECT target, request_lib\r
- FROM action.hold_request\r
- WHERE hold_type = 'T'\r
- AND fulfillment_time IS NULL\r
- AND cancel_time IS NULL\r
- )\r
- UNION ALL\r
- -- Volume holds\r
- (\r
- SELECT bre.id, request_lib\r
- FROM action.hold_request ahr\r
- INNER JOIN asset.call_number acn ON ahr.target = acn.id\r
- INNER JOIN biblio.record_entry bre ON acn.record = bre.id\r
- WHERE ahr.hold_type = 'V'\r
- AND ahr.fulfillment_time IS NULL\r
- AND ahr.cancel_time IS NULL\r
- )\r
- UNION ALL\r
- -- Copy holds\r
- (\r
- SELECT bre.id, request_lib\r
- FROM action.hold_request ahr\r
- INNER JOIN asset.copy ac ON ahr.target = ac.id\r
- INNER JOIN asset.call_number acn ON ac.call_number = acn.id\r
- INNER JOIN biblio.record_entry bre ON acn.record = bre.id\r
- WHERE ahr.hold_type = 'C'\r
- AND ahr.fulfillment_time IS NULL\r
- AND ahr.cancel_time IS NULL\r
- )\r
- ) AS all_holds(bib_id, request_lib)\r
- INNER JOIN reporter.materialized_simple_record rmsr\r
- INNER JOIN actor.org_unit aou ON aou.id = all_holds.request_lib\r
- ON rmsr.id = all_holds.bib_id\r
- GROUP BY all_holds.bib_id, aou.name, rmsr.id, rmsr.title, rmsr.author\r
- HAVING COUNT(all_holds.bib_id) > 2\r
- ORDER BY aou.name\r
- ;</programlisting>\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+-- Title holds\r
+SELECT all_holds.bib_id, aou.name, rmsr.title, rmsr.author, COUNT(all_holds.bib_id)\r
+ FROM\r
+ (\r
+ (\r
+ SELECT target, request_lib\r
+ FROM action.hold_request\r
+ WHERE hold_type = 'T'\r
+ AND fulfillment_time IS NULL\r
+ AND cancel_time IS NULL\r
+ )\r
+ UNION ALL\r
+ -- Volume holds\r
+ (\r
+ SELECT bre.id, request_lib\r
+ FROM action.hold_request ahr\r
+ INNER JOIN asset.call_number acn ON ahr.target = acn.id\r
+ INNER JOIN biblio.record_entry bre ON acn.record = bre.id\r
+ WHERE ahr.hold_type = 'V'\r
+ AND ahr.fulfillment_time IS NULL\r
+ AND ahr.cancel_time IS NULL\r
+ )\r
+ UNION ALL\r
+ -- Copy holds\r
+ (\r
+ SELECT bre.id, request_lib\r
+ FROM action.hold_request ahr\r
+ INNER JOIN asset.copy ac ON ahr.target = ac.id\r
+ INNER JOIN asset.call_number acn ON ac.call_number = acn.id\r
+ INNER JOIN biblio.record_entry bre ON acn.record = bre.id\r
+ WHERE ahr.hold_type = 'C'\r
+ AND ahr.fulfillment_time IS NULL\r
+ AND ahr.cancel_time IS NULL\r
+ )\r
+ ) AS all_holds(bib_id, request_lib)\r
+ INNER JOIN reporter.materialized_simple_record rmsr\r
+ INNER JOIN actor.org_unit aou ON aou.id = all_holds.request_lib\r
+ ON rmsr.id = all_holds.bib_id\r
+ GROUP BY all_holds.bib_id, aou.name, rmsr.id, rmsr.title, rmsr.author\r
+ HAVING COUNT(all_holds.bib_id) > 2\r
+ ORDER BY aou.name\r
+;\r
+</programlisting>\r
</para></formalpara>\r
</simplesect>\r
<simplesect id="_update_borrower_records_with_a_different_home_library">\r
<simpara>Then we issue an <literal>UPDATE</literal> statement to set the home library for patrons with a\r
physical address with a city that matches the city names in our staging table.</simpara>\r
<formalpara><title>Update borrower records with a different home library</title><para>\r
- <programlisting language="sql" linenumbering="unnumbered">CREATE SCHEMA staging;\r
- CREATE TABLE staging.city_home_ou_map (city TEXT, ou_shortname TEXT,\r
- FOREIGN KEY (ou_shortname) REFERENCES actor.org_unit (shortname));\r
- INSERT INTO staging.city_home_ou_map (city, ou_shortname)\r
- VALUES ('Southbury', 'BR1'), ('Middlebury', 'BR2'), ('Hartford', 'BR3');\r
- BEGIN;\r
+<programlisting language="sql" linenumbering="unnumbered">\r
+CREATE SCHEMA staging;\r
+CREATE TABLE staging.city_home_ou_map (city TEXT, ou_shortname TEXT,\r
+ FOREIGN KEY (ou_shortname) REFERENCES actor.org_unit (shortname));\r
+INSERT INTO staging.city_home_ou_map (city, ou_shortname)\r
+ VALUES ('Southbury', 'BR1'), ('Middlebury', 'BR2'), ('Hartford', 'BR3');\r
+BEGIN;\r
\r
- UPDATE actor.usr au SET home_ou = COALESCE(\r
- (\r
- SELECT aou.id\r
- FROM actor.org_unit aou\r
- INNER JOIN staging.city_home_ou_map schom ON schom.ou_shortname = aou.shortname\r
- INNER JOIN actor.usr_address aua ON aua.city = schom.city\r
- WHERE au.id = aua.usr\r
- GROUP BY aou.id\r
- ), home_ou)\r
- WHERE (\r
- SELECT aou.id\r
- FROM actor.org_unit aou\r
- INNER JOIN staging.city_home_ou_map schom ON schom.ou_shortname = aou.shortname\r
- INNER JOIN actor.usr_address aua ON aua.city = schom.city\r
- WHERE au.id = aua.usr\r
- GROUP BY aou.id\r
- ) IS NOT NULL;</programlisting>\r
+UPDATE actor.usr au SET home_ou = COALESCE(\r
+ (\r
+ SELECT aou.id\r
+ FROM actor.org_unit aou\r
+ INNER JOIN staging.city_home_ou_map schom ON schom.ou_shortname = aou.shortname\r
+ INNER JOIN actor.usr_address aua ON aua.city = schom.city\r
+ WHERE au.id = aua.usr\r
+ GROUP BY aou.id\r
+ ), home_ou)\r
+WHERE (\r
+ SELECT aou.id\r
+ FROM actor.org_unit aou\r
+ INNER JOIN staging.city_home_ou_map schom ON schom.ou_shortname = aou.shortname\r
+ INNER JOIN actor.usr_address aua ON aua.city = schom.city\r
+ WHERE au.id = aua.usr\r
+ GROUP BY aou.id\r
+) IS NOT NULL;\r
+</programlisting>\r
</para></formalpara>\r
</simplesect>\r
</section>\r
</programlisting>\r
<para>In this minimal example we select from only one table. Later we will see how to join multiple tables.</para>\r
<para>Since we don't supply a WHERE clause, json_query constructs a default WHERE clause for us, including all the available columns. The resulting SQL looks like this:</para>\r
- <programlisting>\r
- SELECT\r
- "aou".billing_address AS "billing_address",\r
- "aou".holds_address AS "holds_address",\r
- "aou".id AS "id",\r
- "aou".ill_address AS "ill_address",\r
- "aou".mailing_address AS "mailing_address",\r
- "aou".name AS "name",\r
- "aou".ou_type AS "ou_type",\r
- "aou".parent_ou AS "parent_ou",\r
- "aou".shortname AS "shortname",\r
- "aou".email AS "email",\r
- "aou".phone AS "phone",\r
- "aou".opac_visible AS "opac_visible"\r
- FROM\r
- actor.org_unit AS "aou" ; \r
- </programlisting>\r
+<programlisting language="sql">\r
+SELECT\r
+ "aou".billing_address AS "billing_address",\r
+ "aou".holds_address AS "holds_address",\r
+ "aou".id AS "id",\r
+ "aou".ill_address AS "ill_address",\r
+ "aou".mailing_address AS "mailing_address",\r
+ "aou".name AS "name",\r
+ "aou".ou_type AS "ou_type",\r
+ "aou".parent_ou AS "parent_ou",\r
+ "aou".shortname AS "shortname",\r
+ "aou".email AS "email",\r
+ "aou".phone AS "phone",\r
+ "aou".opac_visible AS "opac_visible"\r
+FROM\r
+ actor.org_unit AS "aou" ; \r
+</programlisting>\r
</simplesect>\r
<simplesect>\r
<title>Default SELECT Clauses</title>\r
<simplesect>\r
<title>The SELECT Clause</title>\r
<para>The following variation also produces a default SELECT clause:</para>\r
- <programlisting>\r
- {\r
- "from":"aou",\r
- "select": {\r
- "aou":"*"\r
- }\r
- }\r
- </programlisting>\r
+<programlisting>\r
+{\r
+ "from":"aou",\r
+ "select": {\r
+ "aou":"*"\r
+ }\r
+}\r
+</programlisting>\r
<para>...and so does this one:</para>\r
- <programlisting>\r
- {\r
- "select": {\r
- "aou":null\r
- },\r
- "from":"aou"\r
- }\r
- </programlisting>\r
+<programlisting>\r
+{\r
+ "select": {\r
+ "aou":null\r
+ },\r
+ "from":"aou"\r
+}\r
+</programlisting>\r
<para>While this syntax may not be terribly useful, it does illustrate the minimal structure of a SELECT clause in a JSON query: an entry in the outermost JSON object, \r
with a key of <quote>select</quote>. The value associated with this key is another JSON object, whose keys are class names.</para>\r
<para>(These two examples also illustrate another point: unlike SQL, a JSON query doesn't care whether the FROM clause or the SELECT clause comes first.)</para>\r
<para>Usually you don't want the default SELECT clause. Here's how to select only some of the columns:</para>\r
- <programlisting>\r
- {\r
- "from":"aou",\r
- "select": {\r
- "aou":[ "id", "name" ]\r
- }\r
- }\r
- </programlisting>\r
+<programlisting>\r
+{\r
+ "from":"aou",\r
+ "select": {\r
+ "aou":[ "id", "name" ]\r
+ }\r
+}\r
+</programlisting>\r
<para>The value associated with the class name is an array of column names. If you select columns from multiple tables (not shown here), you'll need a separate entry for each table, \r
and a separate column list for each entry.</para>\r
<para>The previous example results in the following SQL:</para>\r
- <programlisting>\r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou" ;\r
- </programlisting>\r
+<programlisting>\r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou" ;\r
+</programlisting>\r
</simplesect>\r
<simplesect>\r
<title>Fancier SELECT Clauses</title>\r
</listitem> \r
</itemizedlist> \r
<para>This example assigns a different column alias:</para>\r
- <programlisting>\r
- {\r
- "from":"aou",\r
- "select": {\r
- "aou": [\r
- "id",\r
- { "column":"name", "alias":"org_name" }\r
- ]\r
- }\r
- }\r
+<programlisting>\r
+{\r
+ "from":"aou",\r
+ "select": {\r
+ "aou": [\r
+ "id",\r
+ { "column":"name", "alias":"org_name" }\r
+ ]\r
+ }\r
+}\r
\r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "org_name"\r
- FROM\r
- actor.org_unit AS "aou" ;\r
- </programlisting>\r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "org_name"\r
+FROM\r
+ actor.org_unit AS "aou" ;\r
+</programlisting>\r
<para>In this case, changing the column alias doesn't accomplish much. But if we were joining to the actor.org_unit_type table, which also has a "name" column, we could \r
use different aliases to distinguish them.</para>\r
<para>The following example uses a function to raise a column to upper case:</para>\r
- <programlisting>\r
- {\r
- "from":"aou",\r
- "select": {\r
- "aou": [\r
- "id",\r
- { "column":"name", "transform":"upper" }\r
- ]\r
- }\r
- }\r
- \r
- SELECT\r
- "aou".id AS "id",\r
- upper("aou".name ) AS "name"\r
- FROM\r
- actor.org_unit AS "aou" ;\r
- </programlisting>\r
- <para>Here we take a substring of the name, using the "params" element to pass parameters:</para>\r
+<programlisting>\r
+{\r
+ "from":"aou",\r
+ "select": {\r
+ "aou": [\r
+ "id",\r
+ { "column":"name", "transform":"upper" }\r
+ ]\r
+ }\r
+}\r
+ \r
+SELECT\r
+ "aou".id AS "id",\r
+ upper("aou".name ) AS "name"\r
+FROM\r
+ actor.org_unit AS "aou" ;\r
+</programlisting>\r
+ <para>Here we take a substring of the name, using the <emphasis role="italics">params</emphasis> element to pass parameters:</para>\r
<programlisting>\r
{\r
"from":"aou",\r
FROM\r
actor.org_unit AS "aou" ;\r
</programlisting>\r
- <para>The parameters specified with <quote>params</quote> are inserted after the applicable column (<quote>name</quote> in this case), which is always the first parameter. They are always passed as strings, i.e. enclosed in quotes, even if the JSON expresses them as numbers. PostgreSQL will ordinarily coerce them to the right type. However if the function name is overloaded to accept different types, PostgreSQL may invoke a function other than the one intended.</para>\r
+ <para>The parameters specified with <emphasis role="italics">params</emphasis> are inserted after the applicable column (<emphasis role="italics">name</emphasis> in this case), \r
+ which is always the first parameter. They are always passed as strings, i.e. enclosed in quotes, even if the JSON expresses them as numbers. PostgreSQL will ordinarily \r
+ coerce them to the right type. However if the function name is overloaded to accept different types, PostgreSQL may invoke a function other than the one intended.</para>\r
<para>Finally we call a fictitious function "frobozz" that returns multiple columns, where we want only one of them:</para>\r
- <programlisting>\r
- {\r
- "from":"aou",\r
- "select": {\r
- "aou": [\r
- "id", {\r
- "column":"name",\r
- "transform":"frobozz",\r
- "result_field":"zamzam"\r
- }\r
- ]\r
- }\r
- }\r
- \r
- SELECT\r
- "aou".id AS "id",\r
- (frobozz("aou".name ))."zamzam" AS "name"\r
- FROM\r
- actor.org_unit AS "aou" ;\r
- </programlisting>\r
- <para>The "frobozz" function doesn't actually exist, but json_query doesn't know that. The query won't fail until json_query tries to execute it in \r
+<programlisting>\r
+{\r
+ "from":"aou",\r
+ "select": {\r
+ "aou": [\r
+ "id", {\r
+ "column":"name",\r
+ "transform":"frobozz",\r
+ "result_field":"zamzam"\r
+ }\r
+ ]\r
+ }\r
+}\r
+ \r
+SELECT\r
+ "aou".id AS "id",\r
+ (frobozz("aou".name ))."zamzam" AS "name"\r
+FROM\r
+ actor.org_unit AS "aou" ;\r
+</programlisting>\r
+ <para>The <emphasis role="italics">frobozz</emphasis> function doesn't actually exist, but json_query doesn't know that. The query won't fail until json_query tries to execute it in \r
the database.</para>\r
</simplesect>\r
<simplesect>\r
to them (and it has to be the first parameter).</para> \r
<para>You can't use a CASE expression. Instead, the client application can do the equivalent branching for itself.</para>\r
<para>You can't select a subquery. In raw SQL you can do something like the following:</para>\r
- <programlisting>\r
- SELECT\r
- id,\r
- name,\r
- (\r
- SELECT name\r
- FROM actor.org_unit_type AS aout\r
- WHERE aout.id = aou.ou_type\r
- ) AS type_name\r
- FROM\r
- actor.org_unit AS aou;\r
- </programlisting>\r
+<programlisting>\r
+SELECT\r
+ id,\r
+ name,\r
+ (\r
+ SELECT name\r
+ FROM actor.org_unit_type AS aout\r
+ WHERE aout.id = aou.ou_type\r
+ ) AS type_name\r
+FROM\r
+ actor.org_unit AS aou;\r
+</programlisting>\r
<para>This contrived example is not very realistic. Normally you would use a JOIN in this case, and that's what you should do in a JSON query. Other cases may not be so \r
easy to solve.</para>\r
</simplesect>\r
<simplesect>\r
<title>The WHERE Clause</title>\r
<para>Most queries need a WHERE clause, as in this simple example:</para>\r
- <programlisting>\r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where": {\r
- "parent_ou":"3"\r
- }\r
- }\r
- </programlisting>\r
+<programlisting>\r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where": {\r
+ "parent_ou":"3"\r
+ }\r
+}\r
+</programlisting>\r
<para>Like the SELECT clause, the WHERE clause gets its own entry in the top-level object of a JSON query. The key is <quote>where</quote>, and the associated value is either \r
an object (as shown here) or an array (to be discussed a bit later). Each entry in the object is a separate condition.</para>\r
<para>In this case, we use a special shortcut for expressing an equality condition. The column name is on the left of the colon, and the value to which we are equating it is on \r
the right.</para>\r
<para>Here's the resulting SQL:</para>\r
- <programlisting>\r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- "aou".parent_ou = 3;\r
- </programlisting>\r
+<programlisting>\r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+ "aou".parent_ou = 3;\r
+</programlisting>\r
<para>Like the SELECT clause, the generated WHERE clause qualifies each column name with the alias of the relevant table.</para>\r
<para>If you want to compare a column to NULL, put <quote>null</quote> (without quotation marks) to the right of the colon instead of a literal value. The \r
resulting SQL will include <quote>IS NULL</quote> instead of an equals sign.</para>\r
<simplesect>\r
<title>Other Kinds of Comparisons</title>\r
<para>Here's the same query (which generates the same SQL) without the special shortcut:</para>\r
- <programlisting>\r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where": {\r
- "parent_ou":{ "=":3 }\r
- }\r
- }\r
- </programlisting>\r
+<programlisting>\r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where": {\r
+ "parent_ou":{ "=":3 }\r
+ }\r
+}\r
+</programlisting>\r
<para>We still have an entry whose key is the column name, but this time the associated value is another JSON object. It must contain exactly one entry, \r
with the comparison operator on the left of the colon, and the value to be compared on the right.</para>\r
<para>The same syntax works for other kinds of comparison operators. For example:</para>\r
- <programlisting>\r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where": {\r
- "parent_ou":{ ">":3 }\r
- }\r
- }\r
- </programlisting>\r
+<programlisting>\r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where": {\r
+ "parent_ou":{ ">":3 }\r
+ }\r
+}\r
+</programlisting>\r
<para>...turns into:</para>\r
- <programlisting>\r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- "aou".parent_ou > 3 ;\r
- </programlisting>\r
+<programlisting>\r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+ "aou".parent_ou > 3 ;\r
+</programlisting>\r
<para>The condition '<quote>=</quote>:null' turns into IS NULL. Any other operator used with <quote>null</quote> turns into IS NOT NULL.</para>\r
<para>You can use most of the comparison operators recognized by PostgreSQL:</para>\r
<literallayout> \r
any semicolons or white space, in order to prevent certain kinds of SQL injection. It also allows "similar to" as a special exception.</para>\r
<para>As a result, you can slip an operator of your own devising into the SQL, so long as it doesn't contain any semicolons or white space, and doesn't create invalid syntax. \r
Here's a contrived and rather silly example:</para>\r
- <programlisting>\r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where": {\r
- "parent_ou":{ "<2+":3 }\r
- }\r
- }\r
- </programlisting>\r
+<programlisting>\r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where": {\r
+ "parent_ou":{ "<2+":3 }\r
+ }\r
+}\r
+</programlisting>\r
<para>...which results in the following SQL:</para>\r
- <programlisting>\r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- "aou".parent_ou <2+ 3;\r
- </programlisting>\r
+<programlisting>\r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+ "aou".parent_ou <2+ 3;\r
+</programlisting>\r
<para>It's hard to come up with a realistic case where this hack would be useful, but it could happen.</para>\r
</simplesect>\r
-\r
-\r
<simplesect>\r
<title>Comparing One Column to Another</title>\r
<para>Here's how to put another column on the right hand side of a comparison:</para>\r
- \r
- <programlisting>\r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where": {\r
- "id": { ">": { "+aou":"parent_ou" } }\r
- }\r
- };\r
- </programlisting>\r
+<programlisting>\r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where": {\r
+ "id": { ">": { "+aou":"parent_ou" } }\r
+ }\r
+};\r
+</programlisting>\r
<para>This syntax is similar to the previous examples, except that instead of comparing to a literal value, we compare to an object. This object has only a single entry, \r
whose key is a table alias preceded by a leading plus sign. The associated value is the name of the column.</para>\r
<para>Here's the resulting SQL:</para>\r
- <programlisting>\r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- (\r
- "aou".id > ( "aou".parent_ou )\r
- );\r
- </programlisting>\r
+<programlisting>\r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+(\r
+ "aou".id > ( "aou".parent_ou )\r
+);\r
+</programlisting>\r
<para>The table alias must correspond to the appropriate table. Since json_query doesn't validate the choice of alias, it won't detect an invalid alias until it tries to \r
execute the query. In this simple example there's only one table to choose from. The choice of alias is more important in a subquery or join.</para>\r
<para>The leading plus sign, combined with a table alias, can be used in other situations to designate the table to which a column belongs. We shall defer a discussion of \r
<simplesect>\r
<title>Testing Boolean Columns</title>\r
<para>In SQL, there are several ways to test a boolean column such as actor.org_unit.opac_visible. The most obvious way is to compare it to true or false:</para>\r
- <programlisting>\r
- SELECT\r
- id\r
- FROM\r
- actor.org_unit\r
- WHERE\r
- opac_visible = true;\r
- </programlisting>\r
+<programlisting>\r
+SELECT\r
+ id\r
+FROM\r
+ actor.org_unit\r
+WHERE\r
+ opac_visible = true;\r
+</programlisting>\r
<para>In a JSON query this approach doesn't work. If you try it, the "= true" test will turn into IS NULL. Don't do that. Instead, use a leading plus sign, as described in \r
the preceding section, to treat the boolean column as a stand-alone condition:</para>\r
- <programlisting>\r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id" ] },\r
- "where": {\r
- "+aou":"opac_visible"\r
- }\r
- }\r
- </programlisting>\r
-\r
+<programlisting>\r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id" ] },\r
+ "where": {\r
+ "+aou":"opac_visible"\r
+ }\r
+}\r
+</programlisting>\r
<para>Result:</para>\r
- <programlisting>\r
- SELECT\r
- "aou".id AS "id"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- "aou".opac_visible ;\r
- </programlisting>\r
+<programlisting>\r
+SELECT\r
+ "aou".id AS "id"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+ "aou".opac_visible ;\r
+</programlisting>\r
<para>If you need to test for falsity, then write a test for truth and negate it with the "-not" operator. We will discuss the "-not" operator later, but here's a preview:</para>\r
- <programlisting>\r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id" ] },\r
- "where": {\r
- "-not": {\r
- "+aou":"opac_visible"\r
- }\r
- }\r
- } \r
+<programlisting>\r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id" ] },\r
+ "where": {\r
+ "-not": {\r
+ "+aou":"opac_visible"\r
+ }\r
+ }\r
+} \r
\r
- SELECT\r
- "aou".id AS "id"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- NOT ( "aou".opac_visible ); \r
- </programlisting>\r
+SELECT\r
+ "aou".id AS "id"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+ NOT ( "aou".opac_visible ); \r
+</programlisting>\r
<para>You can also compare a boolean column directly to a more complex condition:</para>\r
- <programlisting>\r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id" ] },\r
- "where": {\r
- "opac_visible": {\r
- "=": { "parent_ou":{ ">":3 } }\r
- }\r
- }\r
+<programlisting>\r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id" ] },\r
+ "where": {\r
+ "opac_visible": {\r
+ "=": { "parent_ou":{ ">":3 } }\r
}\r
- </programlisting>\r
+ }\r
+}\r
+</programlisting>\r
<para>Here we compare a boolean column, not to a literal value, but to a boolean expression. The resulting SQL looks a little goofy, but it works:</para>\r
- <programlisting>\r
- SELECT\r
- "aou".id AS "id"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- (\r
- "aou".opac_visible = ( "aou".parent_ou > 3 )\r
- );\r
- </programlisting>\r
+<programlisting>\r
+SELECT\r
+ "aou".id AS "id"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+ (\r
+ "aou".opac_visible = ( "aou".parent_ou > 3 )\r
+ );\r
+</programlisting>\r
<para>In this case we compare the boolean column to a single simple condition. However you can include additional complications -- multiple conditions, IN lists, \r
BETWEEN clauses, and other features as described below.</para>\r
</simplesect>\r
<simplesect>\r
<title>Multiple Conditions</title>\r
<para>If you need multiple conditions, just add them to the "where" object, separated by commas:</para>\r
- <programlisting>\r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where": {\r
- "parent_ou":{ ">":3 },\r
- "id":{ "<>":7 }\r
- }\r
- }\r
- </programlisting>\r
+<programlisting>\r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where": {\r
+ "parent_ou":{ ">":3 },\r
+ "id":{ "<>":7 }\r
+ }\r
+}\r
+</programlisting>\r
<para>The generated SQL connects the conditions with AND:</para>\r
- <programlisting>\r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- "aou".parent_ou g 3\r
- AND "aou".id <> 7;\r
- </programlisting>\r
+<programlisting>\r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+ "aou".parent_ou g 3\r
+ AND "aou".id <> 7;\r
+</programlisting>\r
<para>Later we will see how to use OR instead of AND.</para>\r
</simplesect>\r
<simplesect>\r
<title>Using Arrays</title>\r
<para>Here's a puzzler. Suppose you need two conditions for the same column. How do you code them in the same WHERE clause? For example, suppose you want something like this:</para>\r
- <programlisting>\r
- SELECT\r
- id,\r
- name\r
- FROM\r
- actor.org_unit\r
- WHERE\r
- parent_ou > 3\r
- AND parent_ou <> 7;\r
- </programlisting>\r
+<programlisting>\r
+SELECT\r
+ id,\r
+ name\r
+FROM\r
+ actor.org_unit\r
+WHERE\r
+ parent_ou > 3\r
+ AND parent_ou <> 7;\r
+</programlisting>\r
<para>You might try a WHERE clause like this:</para>\r
- <programlisting>\r
- "where": {\r
- "parent_ou":{ ">":3 },\r
- "parent_ou":{ "<>":7 }\r
- }\r
- </programlisting>\r
+<programlisting>\r
+"where": {\r
+ "parent_ou":{ ">":3 },\r
+ "parent_ou":{ "<>":7 }\r
+ }\r
+</programlisting>\r
<para>Nope. Won't work. According to JSON rules, two entries in the same object can't have the same key.</para>\r
<para>After slapping yourself in the forehead, you try something a little smarter:</para>\r
- <programlisting>\r
- "where": {\r
- "parent_ou": {\r
- ">":3,\r
- "<>":7\r
- }\r
- }\r
- </programlisting>\r
+<programlisting>\r
+"where": {\r
+ "parent_ou": {\r
+ ">":3,\r
+ "<>":7\r
+ }\r
+}\r
+</programlisting>\r
<para>Nice try, but that doesn't work either. Maybe it ought to work -- at least it's legal JSON -- but, no.</para>\r
<para>Here's what works:</para>\r
- <programlisting>\r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where": [\r
- { "parent_ou":{ ">":3 } },\r
- { "parent_ou":{ "<>":7 } }\r
- ]\r
- }\r
- </programlisting>\r
+<programlisting>\r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where": [\r
+ { "parent_ou":{ ">":3 } },\r
+ { "parent_ou":{ "<>":7 } }\r
+ ]\r
+}\r
+</programlisting>\r
<para>We wrapped the two conditions into two separate JSON objects, and then wrapped those objects together into a JSON array. The resulting SQL looks like this:</para>\r
- <programlisting>\r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- ( "aou".parent_ou > 3 )\r
- AND\r
- ( "aou".parent_ou <> 7 );\r
- </programlisting>\r
+<programlisting>\r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+ ( "aou".parent_ou > 3 )\r
+AND\r
+ ( "aou".parent_ou <> 7 );\r
+</programlisting>\r
<para>That's not quite what we were hoping for, because the extra parentheses are so ugly. But they're harmless. This will do.</para>\r
<para>If you're in the mood, you can use arrays to as many parentheses as you like, even if there is only one condition inside:</para>\r
- <programlisting>\r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where":\r
- [[[[[[\r
- {\r
- "parent_ou":{ ">":3 }\r
- },\r
- ]]]]]]\r
- } \r
- </programlisting>\r
+<programlisting>\r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where":\r
+ [[[[[[\r
+ {\r
+ "parent_ou":{ ">":3 }\r
+ },\r
+ ]]]]]]\r
+} \r
+</programlisting>\r
<para>...yields:</para>\r
- <programlisting>\r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- ( ( ( ( ( ( "aou".parent_ou > 3 ) ) ) ) ) );\r
- </programlisting>\r
+<programlisting>\r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+ ( ( ( ( ( ( "aou".parent_ou > 3 ) ) ) ) ) );\r
+</programlisting>\r
</simplesect>\r
<simplesect>\r
<title>How to OR</title>\r
<para>By default, json_query combines conditions with AND. When you need OR, here's how to do it:</para> \r
- <programlisting>\r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where": {\r
- "-or": {\r
- "id":2,\r
- "parent_ou":3\r
- }\r
- }\r
+<programlisting>\r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where": {\r
+ "-or": {\r
+ "id":2,\r
+ "parent_ou":3\r
}\r
- </programlisting>\r
- <para>We use "-or" as the key, with the conditions to be ORed in an associated object. The leading minus sign is there to make sure that the operator isn't confused with a \r
+ }\r
+}\r
+</programlisting>\r
+ <para>We use <quote>-or</quote> as the key, with the conditions to be ORed in an associated object. The leading minus sign is there to make sure that the operator isn't confused with a \r
column name. Later we'll see some other operators with leading minus signs. In a couple of spots we even use plus signs.</para>\r
<para>Here are the results from the above example:</para>\r
- <programlisting>\r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- (\r
- "aou".id = 2\r
- OR "aou".parent_ou = 3\r
- );\r
- </programlisting>\r
- <para>The conditions paired with "-or" are linked by OR and enclosed in parentheses.</para>\r
+<programlisting>\r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+ (\r
+ "aou".id = 2\r
+ OR "aou".parent_ou = 3\r
+ );\r
+</programlisting>\r
+ <para>The conditions paired with <quote>-or</quote> are linked by OR and enclosed in parentheses.</para>\r
<para>Here's how to do the same thing using an array, except that it produces an extra layer of parentheses:</para>\r
- <programlisting>\r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where": {\r
- "-or": [\r
- { "id":2 },\r
- { "parent_ou":3 }\r
- ]\r
- }\r
- }\r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- (\r
- ( "aou".id = 2 )\r
- OR ( "aou".parent_ou = 3 )\r
- );\r
- </programlisting>\r
- <para>It's possible, though not very useful, to have only a single condition subject to the "-or" operator. In that case, the condition appears by itself, since there's nothing \r
+<programlisting>\r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where": {\r
+ "-or": [\r
+ { "id":2 },\r
+ { "parent_ou":3 }\r
+ ]\r
+ }\r
+}\r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+ (\r
+ ( "aou".id = 2 )\r
+ OR ( "aou".parent_ou = 3 )\r
+ );\r
+</programlisting>\r
+ <para>It's possible, though not very useful, to have only a single condition subject to the <quote>-or</quote> operator. In that case, the condition appears by itself, since there's nothing \r
to OR it to. This trick is another way to add an extraneous layer of parentheses.</para>\r
</simplesect>\r
<simplesect>\r
<title>Another way to AND</title>\r
- <para>You can also use the <quote>-and</quote> operator. It works just like "-or", except that it combines conditions with AND instead of OR. Since AND is the default, we don't usually \r
+ <para>You can also use the <quote>-and</quote> operator. It works just like <quote>-or</quote>, except that it combines conditions with AND instead of OR. Since AND is the default, we don't usually \r
need a separate operator for it, but it's available.</para>\r
<para>In rare cases, nothing else will do -- you can't include two conditions in the same list because of the duplicate key problem, but you can't combine them with \r
arrays either. In particular, you might need to combine them within an expression that you're comparing to a boolean column (see the subsection above on Testing Boolean Columns).</para>\r
<simplesect>\r
<title>Negation with NOT</title>\r
<para>The <quote>-not</quote> operator negates a condition or set of conditions. For example:</para>\r
- <programlisting>\r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where": {\r
- "-not": {\r
- "id":{ ">":2 },\r
- "parent_ou":3\r
- }\r
- }\r
+<programlisting>\r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where": {\r
+ "-not": {\r
+ "id":{ ">":2 },\r
+ "parent_ou":3\r
}\r
- \r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- NOT\r
- (\r
- "aou".id > 2\r
- AND "aou".parent_ou = 3\r
- );\r
- </programlisting>\r
+ }\r
+}\r
+ \r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+ NOT\r
+ (\r
+ "aou".id > 2\r
+ AND "aou".parent_ou = 3\r
+ );\r
+</programlisting>\r
<para>In this example we merely negate a combination of two comparisons. However the condition to be negated may be as complicated as it needs to be. Anything that can be \r
subject to <quote>where</quote> can be subject to <quote>-not</quote>.</para>\r
<para>In most cases you can achieve the same result by other means. However the <quote>-not</quote> operator is the only way to represent NOT BETWEEN \r
<title>EXISTS with Subqueries</title>\r
<para>Two other operators carry a leading minus sign: <quote>-exists</quote> and its negation <quote>-not-exists</quote>. These operators apply to subqueries, which have the \r
same format as a full query. For example:</para>\r
- <programlisting> \r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where": {\r
- "-exists": {\r
- "from":"asv",\r
- "select":{ "asv":[ "id" ] },\r
- "where": {\r
- "owner":7\r
- }\r
- }\r
- }\r
+<programlisting> \r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where": {\r
+ "-exists": {\r
+ "from":"asv",\r
+ "select":{ "asv":[ "id" ] },\r
+ "where": {\r
+ "owner":7\r
+ }\r
}\r
- \r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- EXISTS\r
- (\r
- SELECT "asv".id AS "id"\r
- FROM action.survey AS "asv"\r
- WHERE "asv".owner = 7\r
- );\r
- </programlisting>\r
+ }\r
+}\r
+ \r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+EXISTS\r
+ (\r
+ SELECT "asv".id AS "id"\r
+ FROM action.survey AS "asv"\r
+ WHERE "asv".owner = 7\r
+ );\r
+</programlisting>\r
<para>This kind of subquery is of limited use, because its WHERE clause doesn't have anything to do with the main query. It just shuts down the main query altogether \r
if it isn't satisfied.</para>\r
<para>More typical is a correlated subquery, whose WHERE clause refers to a row from the main query. For example:</para>\r
- <programlisting> \r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where": {\r
- "-exists": {\r
- "from":"asv",\r
- "select":{ "asv":[ "id" ] },\r
- "where": {\r
- "owner":{ "=":{ "+aou":"id" }}\r
- }\r
- }\r
- }\r
- } \r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where": {\r
+ "-exists": {\r
+ "from":"asv",\r
+ "select":{ "asv":[ "id" ] },\r
+ "where": {\r
+ "owner":{ "=":{ "+aou":"id" }}\r
+ }\r
+ }\r
+ }\r
+} \r
+</programlisting>\r
<para>Note the use of <quote>+aou</quote> to qualify the id column in the inner WHERE clause.</para>\r
- <programlisting> \r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- EXISTS\r
- (\r
- SELECT "asv".id AS "id"\r
- FROM action.survey AS "asv"\r
- WHERE ("asv".owner = ( "aou".id ))\r
- );\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+ EXISTS\r
+ (\r
+ SELECT "asv".id AS "id"\r
+ FROM action.survey AS "asv"\r
+ WHERE ("asv".owner = ( "aou".id ))\r
+ );\r
+</programlisting>\r
<para>This latter example illustrates the syntax, but in practice, it would probably be more natural to use an IN clause with a subquery (to be discussed later).</para>\r
</simplesect>\r
<simplesect>\r
<title>BETWEEN Clauses</title>\r
<para>Here's how to express a BETWEEN clause:</para>\r
- <programlisting> \r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id" ] },\r
- "where": {\r
- "parent_ou": { "between":[ 3, 7 ] }\r
- }\r
- }\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id" ] },\r
+ "where": {\r
+ "parent_ou": { "between":[ 3, 7 ] }\r
+ }\r
+}\r
+</programlisting>\r
<para>The value associated with the column name is an object with a single entry, whose key is "between". The corresponding value is an array with exactly two values, defining the \r
range to be tested.</para>\r
<para>The range bounds must be either numbers or string literals. Although SQL allows them to be null, a null doesn't make sense in this context, because a null never matches \r
anything. Consequently json_query doesn't allow them.</para>\r
<para>The resulting SQL is just what you would expect:</para>\r
- <programlisting> \r
- SELECT\r
- "aou".id AS "id"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- parent_ou BETWEEN '3' AND '7';\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ "aou".id AS "id"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+ parent_ou BETWEEN '3' AND '7';\r
+</programlisting>\r
</simplesect>\r
<simplesect>\r
<title>IN and NOT IN Lists</title>\r
<para>There are two ways to code an IN list. One way is simply to include the list of values in an array:</para>\r
- <programlisting> \r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where": {\r
- "parent_ou": [ 3, 5, 7 ]\r
- }\r
- }\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where": {\r
+ "parent_ou": [ 3, 5, 7 ]\r
+ }\r
+}\r
+</programlisting>\r
<para>As with a BETWEEN clause, the values in the array must be numbers or string literals. Nulls aren't allowed. Here's the resulting SQL, which again is just what \r
you would expect:</para>\r
- <programlisting> \r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- "aou".parent_ou IN (3, 5, 7);\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+ "aou".parent_ou IN (3, 5, 7);\r
+</programlisting>\r
<para>The other way is similar to the syntax shown above for a BETWEEN clause, except that the array may include any non-zero number of values:</para>\r
- <programlisting> \r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where": {\r
- "parent_ou": { "in": [ 3, 5, 7 ] }\r
- }\r
- }\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where": {\r
+ "parent_ou": { "in": [ 3, 5, 7 ] }\r
+ }\r
+}\r
+</programlisting>\r
<para>This version results in the same SQL as the first one.</para>\r
<para>For a NOT IN list, you can use the latter format, using the <quote>not in</quote> operator instead of <quote>in</quote>. Alternatively, you can use either format together with \r
the <quote>-not</quote> operator.</para> \r
<title>IN and NOT IN Clauses with Subqueries</title>\r
<para>For an IN clause with a subquery, the syntax is similar to the second of the two formats for an IN list (see the previous subsection). The "in" or "not in" operator \r
is paired, not with an array of values, but with an object representing the subquery. For example:</para>\r
- <programlisting> \r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where": {\r
- "id": {\r
- "in": {\r
- "from":"asv",\r
- "select":{ "asv":[ "owner" ] },\r
- "where":{ "name":"Voter Registration" }\r
- }\r
- }\r
- }\r
- }\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where": {\r
+ "id": {\r
+ "in": {\r
+ "from":"asv",\r
+ "select":{ "asv":[ "owner" ] },\r
+ "where":{ "name":"Voter Registration" }\r
+ }\r
+ }\r
+ }\r
+}\r
+</programlisting>\r
<para>The results:</para>\r
- <programlisting> \r
+<programlisting> \r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+ "aou".id IN\r
+ (\r
SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name"\r
+ "asv".owner AS "owner"\r
FROM\r
- actor.org_unit AS "aou"\r
+ action.survey AS "asv"\r
WHERE\r
- "aou".id IN\r
- (\r
- SELECT\r
- "asv".owner AS "owner"\r
- FROM\r
- action.survey AS "asv"\r
- WHERE\r
- "asv".name = 'Voter Registration'\r
- );\r
- </programlisting>\r
+ "asv".name = 'Voter Registration'\r
+ );\r
+</programlisting>\r
<para>In SQL the subquery may select multiple columns, but in a JSON query it can select only a single column.</para>\r
<para>For a NOT IN clause with a subquery, use the <quote>not in</quote> operator instead of <quote>in</quote>.</para>\r
</simplesect>\r
<simplesect>\r
<title>Comparing to a Function</title>\r
<para>Here's how to compare a column to a function call:</para>\r
- <programlisting> \r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where": {\r
- "id":{ ">":[ "sqrt", 16 ] }\r
- }\r
- }\r
- </programlisting>\r
- <para>A comparison operator (">" in this case) is paired with an array. The first entry in the array must be a string giving the name of the function. The remaining parameters, \r
+<programlisting> \r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where": {\r
+ "id":{ ">":[ "sqrt", 16 ] }\r
+ }\r
+}\r
+</programlisting>\r
+ <para>A comparison operator (<quote>></quote> in this case) is paired with an array. The first entry in the array must be a string giving the name of the function. The remaining parameters, \r
if any, are the parameters. They may be strings, numbers, or nulls. The resulting SQL for this example:</para>\r
- <programlisting> \r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- "aou".id > sqrt( '16' );\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+ "aou".id > sqrt( '16' );\r
+</programlisting>\r
<para>All parameters are passed as quoted strings -- even if, as in this case, they are really numbers.</para>\r
<para>This syntax is somewhat limited in that the function parameters must be constants (hence the use of a silly example).</para>\r
</simplesect>\r
<para>In the discussion of the SELECT clause, we saw how you could transform the value of a selected column by passing it to a function. In the WHERE clause, you can \r
use similar syntax to transform the value of a column before comparing it to something else.</para>\r
<para>For example:</para>\r
- <programlisting> \r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where": {\r
- "name": {\r
- "=": {\r
- "transform":"upper",\r
- "value":"CARTER BRANCH"\r
- }\r
- }\r
- }\r
+<programlisting> \r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where": {\r
+ "name": {\r
+ "=": {\r
+ "transform":"upper",\r
+ "value":"CARTER BRANCH"\r
+ }\r
}\r
- </programlisting>\r
+ }\r
+}\r
+</programlisting>\r
<para>The "transform" entry gives the name of the function that we will use on the left side of the comparison. The "value" entry designates the value on the right side \r
of the comparison.</para>\r
- <programlisting> \r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- upper("aou".name ) = 'CARTER BRANCH' ;\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+ upper("aou".name ) = 'CARTER BRANCH' ;\r
+</programlisting>\r
<para>As in the SELECT clause, you can pass literal values or nulls to the function as additional parameters by using an array tagged as <quote>params</quote>:</para>\r
- <programlisting> \r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where": {\r
- "name": {\r
- "=": {\r
- "transform":"substr",\r
- "params":[ 1, 6 ],\r
- "value":"CARTER"\r
- }\r
- }\r
- }\r
+<programlisting> \r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where": {\r
+ "name": {\r
+ "=": {\r
+ "transform":"substr",\r
+ "params":[ 1, 6 ],\r
+ "value":"CARTER"\r
+ }\r
}\r
- \r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- substr("aou".name,'1','6' ) = 'CARTER' ;\r
- </programlisting>\r
+ }\r
+}\r
+ \r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+ substr("aou".name,'1','6' ) = 'CARTER' ;\r
+</programlisting>\r
<para>The first parameter is always the column name, qualified by the class name, followed by any additional parameters (which are always enclosed in quotes even if they \r
are numeric).</para>\r
<para>As in the SELECT clause: if the function returns multiple columns, you can specify the one you want by using a "result_field" entry (not shown here).</para>\r
\r
<simplesect>\r
<title>Putting Function Calls on Both Sides</title>\r
- <para>If you want to compare one function call to another, you can use the same syntax shown in the previous subsection -- except that the "value" entry carries an \r
+ <para>If you want to compare one function call to another, you can use the same syntax shown in the previous subsection -- except that the <quote>value</quote> entry carries an \r
array instead of a literal value. For example:</para>\r
- <programlisting> \r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where": {\r
- "id": {\r
- ">": {\r
- "transform":"factorial",\r
- "value":[ "sqrt", 1000 ]\r
- }\r
- }\r
- }\r
- } \r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- factorial("aou".id ) > sqrt( '1000' ) ;\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where": {\r
+ "id": {\r
+ ">": {\r
+ "transform":"factorial",\r
+ "value":[ "sqrt", 1000 ]\r
+ }\r
+ }\r
+ }\r
+} \r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+ factorial("aou".id ) > sqrt( '1000' ) ;\r
+</programlisting>\r
<para>The format for the right side function is similar to what we saw earlier, in the subsection Comparing to a Function. Note that there are two different formats \r
for defining function calls:</para>\r
<itemizedlist>\r
</simplesect>\r
<simplesect>\r
<title>Comparing a Function to a Condition</title>\r
- <para>So far we have seen two kinds of data for the "value" tag. A string or number translates to a literal value, and an array translates to a function call. \r
+ <para>So far we have seen two kinds of data for the <quote>value</quote> tag. A string or number translates to a literal value, and an array translates to a function call. \r
The third possibility is a JSON object, which translates to a condition. For example:</para>\r
- <programlisting> \r
- {\r
- "from":"aou",\r
- "select": { "aou":[ "id", "name" ] },\r
- "where": {\r
- "id": {\r
- "=": {\r
- "value":{ "parent_ou":{ ">":3 } },\r
- "transform":"is_prime"\r
- }\r
- }\r
- }\r
+<programlisting> \r
+{\r
+ "from":"aou",\r
+ "select": { "aou":[ "id", "name" ] },\r
+ "where": {\r
+ "id": {\r
+ "=": {\r
+ "value":{ "parent_ou":{ ">":3 } },\r
+ "transform":"is_prime"\r
+ }\r
}\r
- </programlisting>\r
+ }\r
+}\r
+</programlisting>\r
<para>The function tagged as <quote>transform</quote> must return boolean, or else json_query will generate invalid SQL. The function used here, <quote>is_prime</quote>, \r
is fictitious.</para>\r
- <programlisting> \r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- WHERE\r
- (\r
- is_prime("aou".id ) = ( "aou".parent_ou > 3 )\r
- );\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+WHERE\r
+(\r
+ is_prime("aou".id ) = ( "aou".parent_ou > 3 )\r
+);\r
+</programlisting>\r
<para>If we left out the <quote>transform</quote> entry, json_query would compare the column on the left (which would to be boolean) to the condition on the right. The results are similar \r
to those for a simpler format described earlier (see the subsection Testing Boolean Columns).</para>\r
<para>In the example above we compared the boolean to a simple condition. However the expression on the right may include multiple conditions, IN lists, subqueries, \r
the class name of the relevant table.</para>\r
<para>When the FROM clause joins multiple tables, the corresponding JSON naturally gets more complicated.</para>\r
<para>SQL provides two ways to define a join. One way is to list both tables in the FROM clause, and put the join conditions in the WHERE clause:</para>\r
- <programlisting> \r
- SELECT\r
- aou.id,\r
- aout.name\r
- FROM\r
- actor.org_unit aou,\r
- actor.org_unit_type aout\r
- WHERE\r
- aout.id = aou.ou_type;\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ aou.id,\r
+ aout.name\r
+FROM\r
+ actor.org_unit aou,\r
+ actor.org_unit_type aout\r
+WHERE\r
+ aout.id = aou.ou_type;\r
+</programlisting>\r
<para>The other way is to use an explicit JOIN clause:</para>\r
- <programlisting> \r
- SELECT\r
- aou.id,\r
- aout.name\r
- FROM\r
- actor.org_unit aou\r
- JOIN actor.org_unit_type aout\r
- ON ( aout.id = aou.ou_type );\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ aou.id,\r
+ aout.name\r
+FROM\r
+ actor.org_unit aou\r
+ JOIN actor.org_unit_type aout\r
+ ON ( aout.id = aou.ou_type );\r
+</programlisting>\r
<para>JSON queries use only the second of these methods. The following example expresses the same query in JSON:</para>\r
- <programlisting> \r
- {\r
- "select": { "aou":[ "id" ], "aout":[ "name" ] },\r
- "from": {\r
- "aou":"aout"\r
- }\r
- }\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "select": { "aou":[ "id" ], "aout":[ "name" ] },\r
+ "from": {\r
+ "aou":"aout"\r
+ }\r
+}\r
+</programlisting>\r
<para>First, let's review the SELECT clause. Since it selects rows from two different tables, the data for <quote>select</quote> includes two entries, one for each table.</para>\r
<para>As for the FROM clause, it's no longer just a string. It's a JSON object, with exactly one entry. The key of this entry is the class name of the core table, i.e. \r
the table named immediately after the FROM keyword. The data associated with this key contains the rest of the information about the join. In this simple example, \r
that information consists entirely of a string containing the class name of the other table.</para>\r
<para>So where is the join condition?</para>\r
<para>It's in the IDL. Upon reading the IDL, json_query knows that actor.org_unit has a foreign key pointing to actor.org_unit_type, and builds a join condition accordingly:</para>\r
- <programlisting> \r
- SELECT\r
- "aou".id AS "id",\r
- "aout".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- INNER JOIN actor.org_unit_type AS "aout"\r
- ON ( "aout".id = "aou".ou_type ) ;\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ "aou".id AS "id",\r
+ "aout".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+ INNER JOIN actor.org_unit_type AS "aout"\r
+ ON ( "aout".id = "aou".ou_type ) ;\r
+</programlisting>\r
<para>In this case the core table is the child table, and the joined table is the parent table. We could just as well have written it the other way around:</para>\r
- <programlisting> \r
- {\r
- "select": { "aou":[ "id" ], "aout":[ "name" ] },\r
- "from": {\r
- "aout":"aou"\r
- }\r
- }\r
- \r
- SELECT\r
- "aou".id AS "id",\r
- "aout".name AS "name"\r
- FROM\r
- actor.org_unit_type AS "aout"\r
- INNER JOIN actor.org_unit AS "aou"\r
- ON ( "aou".ou_type = "aout".id ) ;\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "select": { "aou":[ "id" ], "aout":[ "name" ] },\r
+ "from": {\r
+ "aout":"aou"\r
+ }\r
+}\r
+ \r
+SELECT\r
+ "aou".id AS "id",\r
+ "aout".name AS "name"\r
+FROM\r
+ actor.org_unit_type AS "aout"\r
+ INNER JOIN actor.org_unit AS "aou"\r
+ ON ( "aou".ou_type = "aout".id ) ;\r
+</programlisting>\r
</simplesect>\r
<simplesect>\r
<title>Specifying The Join Columns Explicitly</title>\r
Json_query can't guess which one you want if you don't tell it.</para>\r
<para>(Actually it will try to guess. It will pick the first matching link that it finds in the IDL, which may or may not be the one you want.)</para>\r
<para>Here's how to define exactly which columns you want for the join:</para>\r
- <programlisting> \r
- {\r
- "select": { "aou":[ "id" ], "aoa":[ "street1" ] },\r
- "from": {\r
- "aou": {\r
- "aoa": {\r
- "fkey":"holds_address",\r
- "field":"id"\r
- }\r
- }\r
- }\r
+<programlisting> \r
+{\r
+ "select": { "aou":[ "id" ], "aoa":[ "street1" ] },\r
+ "from": {\r
+ "aou": {\r
+ "aoa": {\r
+ "fkey":"holds_address",\r
+ "field":"id"\r
+ }\r
}\r
- </programlisting>\r
+ }\r
+}\r
+</programlisting>\r
<para>Before, the table we were joining was represented merely by its class name. Now it's represented by an entry in a JSON object. The key of that entry is the \r
class name, and the associated data is another layer of JSON object containing the attributes of the join.</para>\r
<para>Later we'll encounter other kinds of join attributes. For now, the only attributes that we're looking at are the ones that identify the join columns: \r
<para>When there are only two tables involved, the core table is on the left, and the non-core table is on the right. In more complex queries neither table may be the \r
core table.</para>\r
<para>Here is the result of the preceding JSON:</para>\r
- <programlisting> \r
- SELECT\r
- "aou".id AS "id",\r
- "aoa".street1 AS "street1"\r
- FROM\r
- actor.org_unit AS "aou"\r
- INNER JOIN actor.org_address AS "aoa"\r
- ON ( "aoa".id = "aou".holds_address ) ;\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ "aou".id AS "id",\r
+ "aoa".street1 AS "street1"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+ INNER JOIN actor.org_address AS "aoa"\r
+ ON ( "aoa".id = "aou".holds_address ) ;\r
+</programlisting>\r
<para>In this example the child table is on the left and the parent table is on the right. We can swap the tables if we swap the join columns as well:</para>\r
- <programlisting> \r
- {\r
- "select": { "aou":[ "id" ], "aoa":[ "street1" ] },\r
- "from": {\r
- "aoa": {\r
- "aou": {\r
- "fkey":"id",\r
- "field":"holds_address"\r
- }\r
- }\r
- }\r
+<programlisting> \r
+{\r
+ "select": { "aou":[ "id" ], "aoa":[ "street1" ] },\r
+ "from": {\r
+ "aoa": {\r
+ "aou": {\r
+ "fkey":"id",\r
+ "field":"holds_address"\r
+ }\r
}\r
- \r
- SELECT\r
- "aou".id AS "id",\r
- "aoa".street1 AS "street1"\r
- FROM\r
- actor.org_address AS "aoa"\r
- INNER JOIN actor.org_unit AS "aou"\r
- ON ( "aou".holds_address = "aoa".id ) ;\r
- </programlisting>\r
+ }\r
+}\r
+ \r
+SELECT\r
+ "aou".id AS "id",\r
+ "aoa".street1 AS "street1"\r
+FROM\r
+ actor.org_address AS "aoa"\r
+ INNER JOIN actor.org_unit AS "aou"\r
+ ON ( "aou".holds_address = "aoa".id ) ;\r
+</programlisting>\r
<para>When you specify both of the join columns, json_query assumes that you know what you're doing. It doesn't check the IDL to confirm that the join makes sense. \r
The burden is on you to avoid absurdities.</para>\r
</simplesect>\r
<title>Specifying Only One Join Column</title>\r
<para>We just saw how to specify both ends of a join. It turns out that there's a shortcut -- most of the time you only need to specify one end. Consider \r
the following variation on the previous example:</para>\r
- <programlisting> \r
- {\r
- "select": { "aou":[ "id" ], "aoa":[ "street1" ] },\r
- "from": {\r
- "aoa": {\r
- "aou": {\r
- "field":"holds_address"\r
- }\r
- }\r
- }\r
+<programlisting> \r
+{\r
+ "select": { "aou":[ "id" ], "aoa":[ "street1" ] },\r
+ "from": {\r
+ "aoa": {\r
+ "aou": {\r
+ "field":"holds_address"\r
+ }\r
}\r
- </programlisting>\r
+ }\r
+}\r
+</programlisting>\r
<para>..which results in exactly the same SQL as before.</para>\r
<para>Here we specified the join column from the child table, the column that is a foreign key pointing to another table. As long as that linkage is defined in the IDL, \r
json_query can look it up and figure out what the corresponding column is in the parent table.</para>\r
<title>Joining to Multiple Tables</title>\r
<para>So far we have joined only two tables at a time. What if we need to join one table to two different tables?</para>\r
<para>Here's an example:</para>\r
- <programlisting> \r
- {\r
- "select": { "aou":[ "id" ], "aout":[ "depth" ], "aoa":[ "street1" ] },\r
- "from": {\r
- "aou": {\r
- "aout":{},\r
- "aoa": {\r
- "fkey":"holds_address"\r
- }\r
- }\r
- }\r
- }\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "select": { "aou":[ "id" ], "aout":[ "depth" ], "aoa":[ "street1" ] },\r
+ "from": {\r
+ "aou": {\r
+ "aout":{},\r
+ "aoa": {\r
+ "fkey":"holds_address"\r
+ }\r
+ }\r
+ }\r
+}\r
+</programlisting>\r
<para>The first join, to actor.org_unit_type, is simple. We could have specified join columns, but we don't have to, because json_query will construct that join on the basis of \r
what it finds in the IDL. Having no join attributes to specify, we leave that object empty.</para>\r
<para>For the second join, to actor.org_address, we have to specify at least the join column in the child table, as discussed earlier. We could also have specified the join \r
column from the parent table, but we don't have to, so we didn't.</para>\r
<para>Here is the resulting SQL:</para>\r
- <programlisting> \r
- SELECT\r
- "aou".id AS "id",\r
- "aout".depth AS "depth",\r
- "aoa".street1 AS "street1"\r
- FROM\r
- actor.org_unit AS "aou"\r
- INNER JOIN actor.org_unit_type AS "aout"\r
- ON ( "aout".id = "aou".ou_type )\r
- INNER JOIN actor.org_address AS "aoa"\r
- ON ( "aoa".id = "aou".holds_address ) ;\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ "aou".id AS "id",\r
+ "aout".depth AS "depth",\r
+ "aoa".street1 AS "street1"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+ INNER JOIN actor.org_unit_type AS "aout"\r
+ ON ( "aout".id = "aou".ou_type )\r
+ INNER JOIN actor.org_address AS "aoa"\r
+ ON ( "aoa".id = "aou".holds_address ) ;\r
+</programlisting>\r
<para>Since there can be only one core table, the outermost object in the FROM clause can have only one entry, whose key is the class name of the core table. The next \r
level has one entry for every table that's joined to the core table.</para>\r
</simplesect>\r
<title>Nested Joins</title>\r
<para>Let's look at that last query again. It joins three tables, and the core table is the one in the middle. Can we make one of the end tables the core table instead?</para>\r
<para>Yes, we can:</para>\r
- <programlisting> \r
- {\r
- "select": { "aou":[ "id" ], "aout":[ "depth" ], "aoa":[ "street1" ] },\r
- "from": {\r
- "aoa": {\r
- "aou": {\r
- "field":"holds_address",\r
- "join": {\r
- "aout":{ "fkey":"ou_type" }\r
- }\r
- }\r
- }\r
- }\r
+<programlisting> \r
+{\r
+ "select": { "aou":[ "id" ], "aout":[ "depth" ], "aoa":[ "street1" ] },\r
+ "from": {\r
+ "aoa": {\r
+ "aou": {\r
+ "field":"holds_address",\r
+ "join": {\r
+ "aout":{ "fkey":"ou_type" }\r
+ }\r
+ }\r
}\r
- </programlisting>\r
- <para>The "join" attribute introduces another level of join. In this case "aou" is the left table for the nested join, and the right table for the original join. \r
+ }\r
+}\r
+</programlisting>\r
+ <para>The <quote>join</quote> attribute introduces another level of join. In this case "aou" is the left table for the nested join, and the right table for the original join. \r
Here are the results:</para>\r
- <programlisting> \r
- SELECT\r
- "aou".id AS "id",\r
- "aout".depth AS "depth",\r
- "aoa".street1 AS "street1"\r
- FROM\r
- actor.org_address AS "aoa"\r
- INNER JOIN actor.org_unit AS "aou"\r
- ON ( "aou".holds_address = "aoa".id )\r
- INNER JOIN actor.org_unit_type AS "aout"\r
- ON ( "aout".id = "aou".ou_type ) ;\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ "aou".id AS "id",\r
+ "aout".depth AS "depth",\r
+ "aoa".street1 AS "street1"\r
+FROM\r
+ actor.org_address AS "aoa"\r
+ INNER JOIN actor.org_unit AS "aou"\r
+ ON ( "aou".holds_address = "aoa".id )\r
+ INNER JOIN actor.org_unit_type AS "aout"\r
+ ON ( "aout".id = "aou".ou_type ) ;\r
+</programlisting>\r
</simplesect>\r
<simplesect>\r
<title>Outer Joins</title>\r
<para>By default, json_query constructs an inner join. If you need an outer join, you can add the join type as an attribute of the join:</para>\r
<para>Yes, we can:</para>\r
- <programlisting> \r
- {\r
- "select": { "aou":[ "id" ], "aoa":[ "street1" ] },\r
- "from": {\r
- "aoa": {\r
- "aou": {\r
- "field":"mailing_address",\r
- "type":"left"\r
- }\r
- }\r
- }\r
+<programlisting> \r
+{\r
+ "select": { "aou":[ "id" ], "aoa":[ "street1" ] },\r
+ "from": {\r
+ "aoa": {\r
+ "aou": {\r
+ "field":"mailing_address",\r
+ "type":"left"\r
+ }\r
}\r
- </programlisting>\r
+ }\r
+}\r
+</programlisting>\r
<para>Here is the resulting SQL for this example:</para>\r
- <programlisting> \r
- SELECT\r
- "aou".id AS "id",\r
- "aoa".street1 AS "street1"\r
- FROM\r
- actor.org_address AS "aoa"\r
- LEFT JOIN actor.org_unit AS "aou"\r
- ON ( "aou".mailing_address = "aoa".id ) ;\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ "aou".id AS "id",\r
+ "aoa".street1 AS "street1"\r
+FROM\r
+ actor.org_address AS "aoa"\r
+ LEFT JOIN actor.org_unit AS "aou"\r
+ ON ( "aou".mailing_address = "aoa".id ) ;\r
+</programlisting>\r
</simplesect>\r
<simplesect>\r
<title>Referring to Joined Tables in the WHERE Clause</title>\r
<para>In the WHERE clause of the generated SQL, every column name is qualified by a table alias, which is always the corresponding class name.</para>\r
<para>If a column belongs to the core table, this qualification happens by default. If it belongs to a joined table, the JSON must specify what class name \r
to use for an alias. For example:</para>\r
- <programlisting> \r
- {\r
- "select": { "aou":[ "id" ], "aout":[ "name" ] },\r
- "from": {\r
- "aout":"aou"\r
- },\r
- "where": {\r
- "+aou":{ "parent_ou":2 }\r
- }\r
- }\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "select": { "aou":[ "id" ], "aout":[ "name" ] },\r
+ "from": {\r
+ "aout":"aou"\r
+ },\r
+ "where": {\r
+ "+aou":{ "parent_ou":2 }\r
+ }\r
+}\r
+</programlisting>\r
<para>Note the peculiar operator <quote>+aou</quote> -- a plus sign followed by the relevant class name. This operator tells json_query to apply the specified class to the condition that \r
follows. The result:</para>\r
- <programlisting> \r
- SELECT\r
- "aou".id AS "id",\r
- "aout".name AS "name"\r
- FROM\r
- actor.org_unit_type AS "aout"\r
- INNER JOIN actor.org_unit AS "aou"\r
- ON ( "aou".ou_type = "aout".id )\r
- WHERE\r
- ( "aou".parent_ou = 2 );\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ "aou".id AS "id",\r
+ "aout".name AS "name"\r
+FROM\r
+ actor.org_unit_type AS "aout"\r
+ INNER JOIN actor.org_unit AS "aou"\r
+ ON ( "aou".ou_type = "aout".id )\r
+WHERE\r
+ ( "aou".parent_ou = 2 );\r
+</programlisting>\r
<para>The plus-class operator may apply to multiple conditions:</para>\r
- <programlisting> \r
- {\r
- "select": { "aou":[ "id" ], "aout":[ "name" ] },\r
- "from": {\r
- "aout":"aou"\r
- },\r
- "where": {\r
- "+aou":{\r
- "parent_ou":2,\r
- "id":{ "<":42 }\r
- }\r
- }\r
- }\r
- \r
- SELECT\r
- "aou".id AS "id",\r
- "aout".name AS "name"\r
- FROM\r
- actor.org_unit_type AS "aout"\r
- INNER JOIN actor.org_unit AS "aou"\r
- ON ( "aou".ou_type = "aout".id )\r
- WHERE\r
- (\r
- "aou".parent_ou = 2\r
- AND "aou".id < 42\r
- );\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "select": { "aou":[ "id" ], "aout":[ "name" ] },\r
+ "from": {\r
+ "aout":"aou"\r
+ },\r
+ "where": {\r
+ "+aou":{\r
+ "parent_ou":2,\r
+ "id":{ "<":42 }\r
+ }\r
+ }\r
+}\r
+ \r
+SELECT\r
+ "aou".id AS "id",\r
+ "aout".name AS "name"\r
+FROM\r
+ actor.org_unit_type AS "aout"\r
+ INNER JOIN actor.org_unit AS "aou"\r
+ ON ( "aou".ou_type = "aout".id )\r
+WHERE\r
+ (\r
+ "aou".parent_ou = 2\r
+ AND "aou".id < 42\r
+ );\r
+</programlisting>\r
<para>For these artificial examples, it would have been simpler to swap the tables, so that actor.org_unit is the core table. Then you wouldn't need to go through any \r
special gyrations to apply the right table alias. In a more realistic case, however, you might need to apply conditions to both tables. Just swapping the tables \r
wouldn't solve the problem.</para>\r
<para>You can also use a plus-class operator to compare columns from two different tables:</para>\r
- <programlisting> \r
- {\r
- "select": { "aou":[ "id" ], "aout":[ "name" ] },\r
- "from": {\r
- "aout":"aou"\r
- },\r
- "where": {\r
- "depth": { ">": { "+aou":"parent_ou" } }\r
- }\r
- }\r
- \r
+<programlisting> \r
+{\r
+ "select": { "aou":[ "id" ], "aout":[ "name" ] },\r
+ "from": {\r
+ "aout":"aou"\r
+ },\r
+ "where": {\r
+ "depth": { ">": { "+aou":"parent_ou" } }\r
+ }\r
+}\r
+ \r
\r
- SELECT\r
- "aou".id AS "id",\r
- "aout".name AS "name"\r
- FROM\r
- actor.org_unit_type AS "aout"\r
- INNER JOIN actor.org_unit AS "aou"\r
- ON ( "aou".ou_type = "aout".id )\r
- WHERE\r
- (\r
- "aout".depth > ( "aou".parent_ou )\r
- );\r
- </programlisting>\r
+SELECT\r
+ "aou".id AS "id",\r
+ "aout".name AS "name"\r
+FROM\r
+ actor.org_unit_type AS "aout"\r
+ INNER JOIN actor.org_unit AS "aou"\r
+ ON ( "aou".ou_type = "aout".id )\r
+WHERE\r
+ (\r
+ "aout".depth > ( "aou".parent_ou )\r
+ );\r
+</programlisting>\r
<para>Please don't expect that query to make any sense. It doesn't. But it illustrates the syntax.</para>\r
</simplesect>\r
<simplesect>\r
<title>Join Filters</title>\r
<para>While the above approach certainly works, the special syntax needed is goofy and awkward. A somewhat cleaner solution is to include a condition in the JOIN clause:</para>\r
- \r
- <programlisting> \r
- {\r
- "select": { "aou":[ "id" ], "aout":[ "name" ] },\r
- "from": {\r
- "aout": {\r
- "aou": {\r
- "filter": {\r
- "parent_ou":2\r
- }\r
- }\r
- }\r
- }\r
- } \r
+<programlisting> \r
+{\r
+ "select": { "aou":[ "id" ], "aout":[ "name" ] },\r
+ "from": {\r
+ "aout": {\r
+ "aou": {\r
+ "filter": {\r
+ "parent_ou":2\r
+ }\r
+ }\r
+ }\r
+ }\r
+} \r
\r
- SELECT\r
- "aou".id AS "id", "aout".name AS "name"\r
- FROM\r
- actor.org_unit_type AS "aout"\r
- INNER JOIN actor.org_unit AS "aou"\r
- ON ( "aou".ou_type = "aout".id\r
- AND "aou".parent_ou = 2 ) ;\r
- </programlisting>\r
+SELECT\r
+ "aou".id AS "id", "aout".name AS "name"\r
+FROM\r
+ actor.org_unit_type AS "aout"\r
+ INNER JOIN actor.org_unit AS "aou"\r
+ ON ( "aou".ou_type = "aout".id\r
+ AND "aou".parent_ou = 2 ) ;\r
+</programlisting>\r
<para>By default, json_query uses AND to combine the <quote>filter</quote> condition with the original join condition. If you need OR, you can use the <quote>filter_op</quote> attribute to \r
say so:</para>\r
- <programlisting> \r
- {\r
- "select": { "aou":[ "id" ], "aout":[ "name" ] },\r
- "from": {\r
- "aout": {\r
- "aou": {\r
- "filter": {\r
- "parent_ou":2\r
- },\r
- "filter_op":"or"\r
- }\r
- }\r
- }\r
- } \r
+<programlisting> \r
+{\r
+ "select": { "aou":[ "id" ], "aout":[ "name" ] },\r
+ "from": {\r
+ "aout": {\r
+ "aou": {\r
+ "filter": {\r
+ "parent_ou":2\r
+ },\r
+ "filter_op":"or"\r
+ }\r
+ }\r
+ }\r
+} \r
\r
- SELECT\r
- "aou".id AS "id",\r
- "aout".name AS "name"\r
- FROM\r
- actor.org_unit_type AS "aout"\r
- INNER JOIN actor.org_unit AS "aou"\r
- ON ( "aou".ou_type = "aout".id\r
- OR "aou".parent_ou = 2 ) ;\r
- </programlisting>\r
+SELECT\r
+ "aou".id AS "id",\r
+ "aout".name AS "name"\r
+FROM\r
+ actor.org_unit_type AS "aout"\r
+ INNER JOIN actor.org_unit AS "aou"\r
+ ON ( "aou".ou_type = "aout".id\r
+ OR "aou".parent_ou = 2 ) ;\r
+</programlisting>\r
<para>If the data tagged by <quote>filter_op</quote> is anything but <quote>or</quote> (in upper, lower, or mixed case), json_query uses AND instead of OR.</para>\r
<para>The condition tagged by <quote>filter</quote> may be much more complicated. In fact it accepts all the same syntax as the WHERE clause.</para>\r
<para>Remember, though, that it all gets combined with the the original join condition with an AND, or with an OR if you so specify. If \r
<title>Joining to a Subquery</title>\r
<para>In SQL you can put a subquery in a FROM clause, and select from it as if it were a table. A JSON query has no way to do that directly. The IDL, however, \r
can define a class as a subquery instead of as a table. When you SELECT from it, json_query inserts the corresponding subquery into the FROM clause. For example:</para>\r
- <programlisting> \r
- {\r
- "select":{ "iatc":[ "id", "dest", "copy_status" ] },\r
- "from": "iatc"\r
- }\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "select":{ "iatc":[ "id", "dest", "copy_status" ] },\r
+ "from": "iatc"\r
+}\r
+</programlisting>\r
<para>There's nothing special-looking about this JSON, but json_query expands it as follows:</para>\r
- <programlisting> \r
- SELECT\r
- "iatc".id AS "id",\r
- "iatc".dest AS "dest",\r
- "iatc".copy_status AS "copy_status"\r
- FROM\r
- (\r
- SELECT t.*\r
- FROM\r
- action.transit_copy t\r
- JOIN actor.org_unit AS s\r
- ON (t.source = s.id)\r
- JOIN actor.org_unit AS d\r
- ON (t.dest = d.id)\r
- WHERE\r
- s.parent_ou <> d.parent_ou\r
- ) AS "iatc" ;\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ "iatc".id AS "id",\r
+ "iatc".dest AS "dest",\r
+ "iatc".copy_status AS "copy_status"\r
+FROM\r
+ (\r
+ SELECT t.*\r
+ FROM\r
+ action.transit_copy t\r
+ JOIN actor.org_unit AS s\r
+ ON (t.source = s.id)\r
+ JOIN actor.org_unit AS d\r
+ ON (t.dest = d.id)\r
+ WHERE\r
+ s.parent_ou <> d.parent_ou\r
+ ) AS "iatc" ;\r
+</programlisting>\r
<para>The <quote>iatc</quote> class is like a view, except that it's defined in the IDL instead of the database. In this case it provides a way to do a join that would otherwise be \r
impossible through a JSON query, because it joins the same table in two different ways (see the next subsection).</para>\r
</simplesect>\r
condition, or to omit any join condition in order to obtain a Cartesian product. If necessary, you can devise such unconventional joins by combining the normal join \r
conditions with join filters.</para>\r
<para>For example, here's how to get a Cartesian product:</para>\r
- <programlisting> \r
- {\r
- "select": { "aou":[ "id" ], "aout":[ "name" ] },\r
- "from": {\r
- "aout": {\r
- "aou": {\r
- "filter": {\r
- "ou_type":{ "<>": { "+aout":"id" } }\r
- },\r
- "filter_op":"or"\r
- }\r
- }\r
- }\r
- }\r
- \r
+<programlisting> \r
+{\r
+ "select": { "aou":[ "id" ], "aout":[ "name" ] },\r
+ "from": {\r
+ "aout": {\r
+ "aou": {\r
+ "filter": {\r
+ "ou_type":{ "<>": { "+aout":"id" } }\r
+ },\r
+ "filter_op":"or"\r
+ }\r
+ }\r
+ }\r
+}\r
+ \r
\r
- SELECT\r
- "aou".id AS "id",\r
- "aout".name AS "name"\r
- FROM\r
- actor.org_unit_type AS "aout"\r
- INNER JOIN actor.org_unit AS "aou"\r
- ON\r
- (\r
- "aou".ou_type = "aout".id\r
- OR ("aou".ou_type <> ( "aout".id ))\r
- ) ;\r
- </programlisting>\r
+SELECT\r
+ "aou".id AS "id",\r
+ "aout".name AS "name"\r
+FROM\r
+ actor.org_unit_type AS "aout"\r
+ INNER JOIN actor.org_unit AS "aou"\r
+ ON\r
+ (\r
+ "aou".ou_type = "aout".id\r
+ OR ("aou".ou_type <> ( "aout".id ))\r
+ ) ;\r
+</programlisting>\r
<para>Yes, it's ugly, but at least you're not likely to do it by accident.</para>\r
</simplesect>\r
<simplesect>\r
<title>Selecting from Functions</title>\r
<para>In SQL, you can put a function call in the FROM clause. The function may return multiple columns and multiple rows. Within the query, the function behaves like a table.</para>\r
<para>A JSON query can also select from a function:</para>\r
- <programlisting> \r
- {\r
- "from": [ "actor.org_unit_ancestors", 5 ]\r
- }\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "from": [ "actor.org_unit_ancestors", 5 ]\r
+}\r
+</programlisting>\r
<para>The data associated with <quote>from</quote> is an array instead of a string or an object. The first element in the array specifies the name of the function. Subsequent elements, \r
if any, supply the parameters of the function; they must be literal values or nulls.</para>\r
<para>Here is the resulting query:</para>\r
- <programlisting> \r
- SELECT *\r
- FROM\r
- actor.org_unit_ancestors( '5' ) AS "actor.org_unit_ancestors" ;\r
- </programlisting>\r
+<programlisting> \r
+SELECT *\r
+FROM\r
+ actor.org_unit_ancestors( '5' ) AS "actor.org_unit_ancestors" ;\r
+</programlisting>\r
<para>In a JSON query this format is very limited, largely because the IDL knows nothing about the available functions. You can't join the function to a table or to \r
another function. If you try to supply a SELECT list or a WHERE clause, json_query will ignore it. The generated query will always select every column, via a wild card asterisk, \r
from every row.</para>\r
<simplesect>\r
<title>The ORDER BY Clause</title>\r
<para>In most cases you can encode an ORDER BY clause as either an array or an object. Let's take a simple example and try it both ways. First the array:</para>\r
- <programlisting> \r
- {\r
- "select":{ "aou":[ "name" ] },\r
- "from": "aou",\r
- "order_by": [\r
- { "class":"aou", "field":"name" }\r
- ]\r
- }\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "select":{ "aou":[ "name" ] },\r
+ "from": "aou",\r
+ "order_by": [\r
+ { "class":"aou", "field":"name" }\r
+ ]\r
+}\r
+</programlisting>\r
<para>Now the object:</para>\r
- <programlisting> \r
- {\r
- "select":{ "aou":[ "name" ] },\r
- "from": "aou",\r
- "order_by": {\r
- "aou":{ "name":{} }\r
- }\r
- }\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "select":{ "aou":[ "name" ] },\r
+ "from": "aou",\r
+ "order_by": {\r
+ "aou":{ "name":{} }\r
+ }\r
+}\r
+</programlisting>\r
<para>The results are identical from either version:</para>\r
- <programlisting> \r
- SELECT\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- ORDER BY\r
- "aou".name;\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+ORDER BY\r
+ "aou".name;\r
+</programlisting>\r
<para>The array format is more verbose, but as we shall see, it is also more flexible. It can do anything the object format can do, plus some things that the object \r
format can't do.</para>\r
</simplesect>\r
</itemizedlist> \r
<para>If you want to sort by multiple fields, just include a separate object for each field.</para>\r
<para>If you want to sort a field in descending order, add a <quote>direction</quote> tag:</para>\r
- <programlisting> \r
- {\r
- "select":{ "aou":[ "name" ] },\r
- "from": "aou",\r
- "order_by": [\r
- {\r
- "class":"aou",\r
- "field":"name",\r
- "transform":"upper"\r
- }\r
- ]\r
- }\r
- \r
+<programlisting> \r
+{\r
+ "select":{ "aou":[ "name" ] },\r
+ "from": "aou",\r
+ "order_by": [\r
+ {\r
+ "class":"aou",\r
+ "field":"name",\r
+ "transform":"upper"\r
+ }\r
+ ]\r
+}\r
+ \r
\r
- SELECT\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- ORDER BY\r
- upper("aou".name );\r
- </programlisting>\r
+SELECT\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+ORDER BY\r
+ upper("aou".name );\r
+</programlisting>\r
<para>If you need additional parameters for the function, you can use the <quote>params</quote> tag to pass them:</para>\r
- <programlisting> \r
- {\r
- "select":{ "aou":[ "name" ] },\r
- "from": "aou",\r
- "order_by": [\r
- {\r
- "class":"aou",\r
- "field":"name",\r
- "transform":"substr",\r
- "params":[ 1, 8 ]\r
- }\r
- ]\r
- }\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "select":{ "aou":[ "name" ] },\r
+ "from": "aou",\r
+ "order_by": [\r
+ {\r
+ "class":"aou",\r
+ "field":"name",\r
+ "transform":"substr",\r
+ "params":[ 1, 8 ]\r
+ }\r
+ ]\r
+}\r
+</programlisting>\r
<para>The additional parameters appear as elements in an array. They may be numbers, strings, or nulls.</para>\r
- <programlisting> \r
- SELECT\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- ORDER BY\r
- substr("aou".name,'1','8' );\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+ORDER BY\r
+ substr("aou".name,'1','8' );\r
+</programlisting>\r
<para>As we have seen elsewhere, all literal values are passed as quoted strings, even if they are numbers.</para>\r
<para>If the function returns multiple columns, you can use the <quote>result_field</quote> tag to indicate which one you want (not shown).</para>\r
</simplesect>\r
<title>ORDER BY as an Object</title>\r
<para>When you encode the ORDER BY clause as an object, the keys of the object are class names. Each class must be either the core class or a joined class. The data for \r
each class can be either an array or another layer of object. Here's an example with one of each:</para>\r
- <programlisting> \r
- {\r
- "select":{ "aout":"id", "aou":[ "name" ] },\r
- "from": { "aou":"aout" },\r
- "order_by": {\r
- "aout":[ "id" ],\r
- "aou":{ "name":{ "direction":"desc" } }\r
- }\r
- }\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "select":{ "aout":"id", "aou":[ "name" ] },\r
+ "from": { "aou":"aout" },\r
+ "order_by": {\r
+ "aout":[ "id" ],\r
+ "aou":{ "name":{ "direction":"desc" } }\r
+ }\r
+}\r
+</programlisting>\r
<para>For the <quote>aout</quote> class, the associated array is simply a list of field names (in this case, just one). Naturally, each field must reside in the class with which \r
it is associated.</para>\r
<para>However, a list of field names provides no way to specify the direction of sorting, or a transforming function. You can add those details only if the class \r
<para>In this example, we use the <quote>direction"</quote> tag to specify that the name field be sorted in descending order. This tag works the same way here as described earlier. \r
If the associated string starts with "D" or "d", the sort will be descending; otherwise it will be ascending.</para>\r
<para>Here is the resulting SQL:</para>\r
- <programlisting> \r
- SELECT\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- INNER JOIN actor.org_unit_type AS "aout"\r
- ON ( "aout".id = "aou".ou_type )\r
- ORDER BY\r
- "aout".id,\r
- "aou".name DESC;\r
- </programlisting>\r
- <programlisting>\r
- {\r
- "select":{ "aou":[ "name", "id" ] },\r
- "from": "aou",\r
- "order_by": {\r
- "aou":{\r
- "name":{ "transform":"substr", "params":[ 1, 8 ] }\r
- }\r
- }\r
- } \r
+<programlisting> \r
+SELECT\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+ INNER JOIN actor.org_unit_type AS "aout"\r
+ ON ( "aout".id = "aou".ou_type )\r
+ORDER BY\r
+ "aout".id,\r
+ "aou".name DESC;\r
+</programlisting>\r
+<programlisting>\r
+{\r
+ "select":{ "aou":[ "name", "id" ] },\r
+ "from": "aou",\r
+ "order_by": {\r
+ "aou":{\r
+ "name":{ "transform":"substr", "params":[ 1, 8 ] }\r
+ }\r
+ }\r
+} \r
\r
- SELECT\r
- "aou".name AS "name",\r
- "aou".id AS "id"\r
- FROM\r
- actor.org_unit AS "aou"\r
- ORDER BY\r
- substr("aou".name,'1','8' );\r
- </programlisting>\r
+SELECT\r
+ "aou".name AS "name",\r
+ "aou".id AS "id"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+ORDER BY\r
+ substr("aou".name,'1','8' );\r
+</programlisting>\r
</simplesect>\r
<simplesect>\r
<title>Things You Can't Do</title>\r
there are situations where it can be useful, provided that the column is passed to a transforming function.</para>\r
<para>For example, you might want a case-insensitive sort, except that for any given letter you want lower case to sort first. For example, you want <quote>diBona</quote> to sort \r
before <quote>Dibona</quote>. Here's a way to do that, coding the ORDER BY clause as an array:</para>\r
- <programlisting> \r
- {\r
- "select":{ "au":[ "family_name", "id" ] },\r
- "from": "au",\r
- "order_by": [\r
- { "class":"au", "field":"family_name", "transform":"upper" },\r
- { "class":"au", "field":"family_name" }\r
- ]\r
- }\r
- SELECT\r
- "au".family_name AS "family_name",\r
- "au".id AS "id"\r
- FROM\r
- actor.usr AS "au"\r
- ORDER BY\r
- upper("au".family_name ),\r
- "au".family_name;\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "select":{ "au":[ "family_name", "id" ] },\r
+ "from": "au",\r
+ "order_by": [\r
+ { "class":"au", "field":"family_name", "transform":"upper" },\r
+ { "class":"au", "field":"family_name" }\r
+ ]\r
+}\r
+SELECT\r
+ "au".family_name AS "family_name",\r
+ "au".id AS "id"\r
+FROM\r
+ actor.usr AS "au"\r
+ORDER BY\r
+ upper("au".family_name ),\r
+ "au".family_name;\r
+</programlisting>\r
<para>Such a sort is not possible where the ORDER BY clause is coded as an object.</para>\r
</simplesect>\r
<simplesect>\r
<para>A JSON query has no separate construct to define a GROUP BY clause. Instead, the necessary information is distributed across the SELECT clause. However, \r
the way it works is a bit backwards from what you might expect, so pay attention.</para>\r
<para>Here's an example:</para>\r
- <programlisting> \r
- {\r
- "select": {\r
- "aou": [\r
- { "column":"parent_ou" },\r
- { "column":"name", "transform":"max", "aggregate":true }\r
- ]\r
- },\r
- "from": "aou"\r
- }\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "select": {\r
+ "aou": [\r
+ { "column":"parent_ou" },\r
+ { "column":"name", "transform":"max", "aggregate":true }\r
+ ]\r
+ },\r
+ "from": "aou"\r
+}\r
+</programlisting>\r
<para>The <quote>transform</quote> tag is there just to give us an excuse to do a GROUP BY. What's important to notice is the <quote>aggregate</quote> tag.</para>\r
<para>Here's the resulting SQL:</para>\r
- <programlisting> \r
- SELECT\r
- "aou".parent_ou AS "parent_ou",\r
- max("aou".name ) AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- GROUP BY\r
- 1;\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ "aou".parent_ou AS "parent_ou",\r
+ max("aou".name ) AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+GROUP BY\r
+ 1;\r
+</programlisting>\r
<para>The GROUP BY clause references fields from the SELECT clause by numerical reference, instead of by repeating them. Notice that the field it references, \r
parent_ou, is the one that doesn't carry the <quote>aggregate</quote> tag in the JSON.</para>\r
<para>Let's state that more generally. The GROUP BY clause includes only the fields that do not carry the <quote>aggregate</quote> tag (or that carry it with a value of false).</para>\r
<para>JSON queries don't generate DISTINCT clauses. However, they can generate GROUP BY clauses that include every item from the SELECT clause. The effect is the same as \r
applying DISTINCT to the entire SELECT clause.</para>\r
<para>For example:</para>\r
- <programlisting> \r
- {\r
- "select": {\r
- "aou": [\r
- "parent_ou",\r
- "ou_type"\r
- ]\r
- },\r
- "from":"aou",\r
- "distinct":"true"\r
- }\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "select": {\r
+ "aou": [\r
+ "parent_ou",\r
+ "ou_type"\r
+ ]\r
+ },\r
+ "from":"aou",\r
+ "distinct":"true"\r
+}\r
+</programlisting>\r
<para>Note the <quote>distinct</quote> entry at the top level of the query object, with a value of <quote>true</quote>.</para>\r
- <programlisting> \r
- SELECT\r
- "aou".parent_ou AS "parent_ou",\r
- "aou".ou_type AS "ou_type"\r
- FROM\r
- actor.org_unit AS "aou"\r
- GROUP BY\r
- 1, 2;\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ "aou".parent_ou AS "parent_ou",\r
+ "aou".ou_type AS "ou_type"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+GROUP BY\r
+ 1, 2;\r
+</programlisting>\r
<para>The generated GROUP BY clause references every column in the SELECT clause by number.</para>\r
</simplesect>\r
<simplesect>\r
<para>For a HAVING clause, add a <quote>having</quote> entry at the top level of the query object. For the associated data, you can use all the same syntax \r
that you can use for a WHERE clause.</para>\r
<para>Here's a simple example:</para>\r
- <programlisting> \r
- {\r
- "select": {\r
- "aou": [\r
- "parent_ou", {\r
- "column":"id",\r
- "transform":"count",\r
- "alias":"id_count",\r
- "aggregate":"true"\r
- }\r
- ]\r
- },\r
- "from":"aou",\r
- "having": {\r
- "id": {\r
- ">" : {\r
- "transform":"count",\r
- "value":6\r
- }\r
- }\r
- }\r
- }\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "select": {\r
+ "aou": [\r
+ "parent_ou", {\r
+ "column":"id",\r
+ "transform":"count",\r
+ "alias":"id_count",\r
+ "aggregate":"true"\r
+ }\r
+ ]\r
+ },\r
+ "from":"aou",\r
+ "having": {\r
+ "id": {\r
+ ">" : {\r
+ "transform":"count",\r
+ "value":6\r
+ }\r
+ }\r
+ }\r
+}\r
+</programlisting>\r
<para>We use the <quote>aggregate</quote> tag in the SELECT clause to give us a GROUP BY to go with the HAVING. Results:</para>\r
- <programlisting> \r
- SELECT\r
- "aou".parent_ou AS "parent_ou",\r
- count("aou".id ) AS "id_count"\r
- FROM\r
- actor.org_unit AS "aou"\r
- GROUP BY\r
- 1\r
- HAVING\r
- count("aou".id ) > 6 ;\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ "aou".parent_ou AS "parent_ou",\r
+ count("aou".id ) AS "id_count"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+GROUP BY\r
+ 1\r
+HAVING\r
+ count("aou".id ) > 6 ;\r
+</programlisting>\r
<para>In raw SQL we could have referred to <quote>count( 1 )</quote>. But since JSON queries cannot encode arbitrary expressions, we applied the count function to a column that \r
cannot be null.</para>\r
</simplesect>\r
<simplesect>\r
<title>The LIMIT and OFFSET Clauses</title>\r
<para>To add an LIMIT or OFFSET clause, add an entry to the top level of a query object. For example:</para>\r
- <programlisting> \r
- {\r
- "select": {\r
- "aou": [ "id", "name" ]\r
- },\r
- "from":"aou",\r
- "order_by": { "aou":[ "id" ] },\r
- "offset": 7,\r
- "limit": 42\r
- }\r
- </programlisting>\r
+<programlisting> \r
+{\r
+ "select": {\r
+ "aou": [ "id", "name" ]\r
+ },\r
+ "from":"aou",\r
+ "order_by": { "aou":[ "id" ] },\r
+ "offset": 7,\r
+ "limit": 42\r
+}\r
+</programlisting>\r
<para>The data associated with <quote>offset</quote> and <quote>limit</quote> may be either a number or a string, but if it's a string, it should have a number inside.</para>\r
<para>Result:</para>\r
- <programlisting> \r
- SELECT\r
- "aou".id AS "id",\r
- "aou".name AS "name"\r
- FROM\r
- actor.org_unit AS "aou"\r
- ORDER BY\r
- "aou".id\r
- LIMIT 42 \r
- OFFSET 7;\r
- </programlisting>\r
+<programlisting> \r
+SELECT\r
+ "aou".id AS "id",\r
+ "aou".name AS "name"\r
+FROM\r
+ actor.org_unit AS "aou"\r
+ORDER BY\r
+ "aou".id\r
+LIMIT 42 \r
+OFFSET 7;\r
+</programlisting>\r
</simplesect>\r
</chapter>\r
<simplesect>\r
<title>Return a list of ISBNs for related records</title>\r
<para>Similar to the OCLC xISBN service, Evergreen can return a list of related records based on its oISBN algorithm:</para>\r
- <screen>http://<hostname>/opac/extras/osibn/<ISBN></screen>\r
- <para>For example, http://dev.gapines.org/opac/extras/oisbn/0439136350 returns:</para>\r
- <programlisting>\r
- <idlist metarecord="302670">\r
- <isbn record="250060">0790783525</isbn>\r
- <isbn record="20717">0736691316</isbn>\r
- <isbn record="250045">0790783517</isbn>\r
- <isbn record="199060">9500421151</isbn>\r
- <isbn record="250061">0790783495</isbn>\r
- <isbn record="154477">0807286028</isbn>\r
- <isbn record="227297">1594130027</isbn>\r
- <isbn record="26682">0786222743</isbn>\r
- <isbn record="17179">0807282316</isbn>\r
- <isbn record="34885">0807282316</isbn>\r
- <isbn record="118019">8478885196</isbn>\r
- <isbn record="1231">0738301477</isbn>\r
- </idlist>\r
- </programlisting>\r
+ <para><uri>http://<hostname>/opac/extras/osibn/<ISBN></uri></para>\r
+ <para>For example, <uri>http://dev.gapines.org/opac/extras/oisbn/0439136350</uri> returns:</para>\r
+<screen>\r
+<idlist metarecord="302670">\r
+<isbn record="250060">0790783525</isbn>\r
+<isbn record="20717">0736691316</isbn>\r
+<isbn record="250045">0790783517</isbn>\r
+<isbn record="199060">9500421151</isbn>\r
+<isbn record="250061">0790783495</isbn>\r
+<isbn record="154477">0807286028</isbn>\r
+<isbn record="227297">1594130027</isbn>\r
+<isbn record="26682">0786222743</isbn>\r
+<isbn record="17179">0807282316</isbn>\r
+<isbn record="34885">0807282316</isbn>\r
+<isbn record="118019">8478885196</isbn>\r
+<isbn record="1231">0738301477</isbn>\r
+</idlist>\r
+</screen>\r
</simplesect>\r
<simplesect>\r
<title>Return records</title>\r
<para>SuperCat can return records and metarecords in many different formats (see <xref linkend='supportedsupercatformats' /></para>\r
- <screen>http://<hostname>/opac/extras/supercat/retrieve/<format>/<record-type>/<bib-ID></screen>\r
- <para>For example, http://dev.gapines.org/opac/extras/supercat/retrieve/mods/record/555 returns:</para>\r
- <programlisting>\r
- <mods:modsCollection version="3.0">\r
- <mods:mods xsi:schemaLocation="http://www.loc.gov/mods/ http://www.loc.gov/standards/mods/mods.xsd">\r
- <titleInfo>\r
- <title>More Brer Rabbit stories /</title>\r
- </titleInfo>\r
- <typeOfResource>text</typeOfResource>\r
- <originInfo>\r
- <place>\r
- <code authority="marc">xx</c0de>\r
- </place>\r
- <publisher>Award Publications</publisher>\r
- <dateIssued>c1982, 1983</dateIssued>\r
- <dateIssued encoding="marc" point="start">1983</dateIssued>\r
- <dateIssued encoding="marc" point="end">1982</dateIssued>\r
- <issuance>monographic</issuance>\r
- </originInfo>\r
- <language authority="iso639-2b">eng</language>\r
- <physicalDescription>\r
- <form authority="marcform">print</form>\r
- <extent>unp. : col. ill.</extent>\r
- </physicalDescription>\r
- <note type="statement of responsibility">ill. by Rene Cloke.</note>\r
- <subject authority="lcsh">\r
- <topic>Animals</topic>\r
- <topic>Fiction</topic>\r
- </subject>\r
- <subject authority="lcsh">\r
- <topic>Fables</topic>\r
- </subject>\r
- <recordInfo>\r
- <recordContentSource>(BRO)</recordContentSource>\r
- <recordCreationDate encoding="marc">930903</recordCreationDate>\r
- <recordChangeDate encoding="iso8601">19990703024637.0</recordChangeDate>\r
- <recordIdentifier>PIN60000007 </recordIdentifier>\r
- </recordInfo>\r
- </mods:mods>\r
- </mods:modsCollection>\r
- </programlisting>\r
+ <para><uri>http://<hostname>/opac/extras/supercat/retrieve/<format>/<record-type>/<bib-ID></uri></para>\r
+ <para>For example, <uri>http://dev.gapines.org/opac/extras/supercat/retrieve/mods/record/555</uri> returns:</para>\r
+<screen>\r
+<mods:modsCollection version="3.0">\r
+ <mods:mods xsi:schemaLocation="http://www.loc.gov/mods/ http://www.loc.gov/standards/mods/mods.xsd">\r
+ <titleInfo>\r
+ <title>More Brer Rabbit stories /</title>\r
+ </titleInfo>\r
+ <typeOfResource>text</typeOfResource>\r
+ <originInfo>\r
+ <place>\r
+ <code authority="marc">xx</c0de>\r
+ </place>\r
+ <publisher>Award Publications</publisher>\r
+ <dateIssued>c1982, 1983</dateIssued>\r
+ <dateIssued encoding="marc" point="start">1983</dateIssued>\r
+ <dateIssued encoding="marc" point="end">1982</dateIssued>\r
+ <issuance>monographic</issuance>\r
+ </originInfo>\r
+ <language authority="iso639-2b">eng</language>\r
+ <physicalDescription>\r
+ <form authority="marcform">print</form>\r
+ <extent>unp. : col. ill.</extent>\r
+ </physicalDescription>\r
+ <note type="statement of responsibility">ill. by Rene Cloke.</note>\r
+ <subject authority="lcsh">\r
+ <topic>Animals</topic>\r
+ <topic>Fiction</topic>\r
+ </subject>\r
+ <subject authority="lcsh">\r
+ <topic>Fables</topic>\r
+ </subject>\r
+ <recordInfo>\r
+ <recordContentSource>(BRO)</recordContentSource>\r
+ <recordCreationDate encoding="marc">930903</recordCreationDate>\r
+ <recordChangeDate encoding="iso8601">19990703024637.0</recordChangeDate>\r
+ <recordIdentifier>PIN60000007 </recordIdentifier>\r
+ </recordInfo>\r
+ </mods:mods>\r
+</mods:modsCollection>\r
+</screen>\r
</simplesect>\r
<simplesect>\r
<title>Return a feed of recently edited or created records</title>\r
<para>SuperCat can return feeds of recently edited or created authority and bibliographic records:</para>\r
- <screen>http://<hostname>/opac/extras/feed/freshmeat/<feed-type>/[authority|biblio]/[import|edit]/<limit>/<date></screen>\r
+ <para><uri>http://<hostname>/opac/extras/feed/freshmeat/<feed-type>/[authority|biblio]/[import|edit]/<limit>/<date></uri></para>\r
<para>The limit records imported or edited following the supplied date will be returned. If you do not supply a date, then the most recent limit records will be returned.</para>\r
<para>If you do not supply a limit, then up to 10 records will be returned.</para> \r
<para>Feed-type can be one of atom, html, htmlholdings, marcxml, mods, mods3, or rss2.</para> \r
- <para>For example, http://dev.gapines.org/opac/extras/feed/freshmeat/atom/biblio/import/10/2008-01-01</para>\r
+ <para><uri>For example, http://dev.gapines.org/opac/extras/feed/freshmeat/atom/biblio/import/10/2008-01-01</uri></para>\r
</simplesect>\r
<simplesect>\r
<title>Browse records</title>\r
<para>SuperCat can browse records in HTML and XML formats:</para>\r
- <screen>http://<hostname>/opac/extras/supercat/browse/<format>/call_number/<org_unit>/<call_number></screen>\r
- <para>For example, http://dev.gapines.org/opac/extras/browse/xml/call_number/-/GV returns:</para>\r
- <programlisting>\r
- <hold:volumes xmlns:hold='http://open-ils.org/spec/holdings/v1'>\r
- <hold:volume id="tag:open-ils.org,2008:asset-call_number/130607" lib="FRRLS-FA" label="GUTCHEON BETH">\r
- <act:owning_lib id="tag:open-ils.org,2008:actor-org_unit/111" name="Fayette County Public Library"/>\r
- <record xsi:schemaLocation="http://www.loc.gov/MARC21/slim http://www.loc.gov/ \r
- standards/marcxml/schema/MARC21slim.xsd"\r
- id="tag:open-ils.org,2008:biblio-record_entry/21669/FRRLS-FA">\r
- <leader>09319pam a2200961 a 4500</leader>\r
- <controlfield tag="001"/>\r
- <controlfield tag="005">20000302124754.0</controlfield>\r
- <controlfield tag="008">990817s2000 nyu 000 1 eng </controlfield>\r
- <datafield tag="010" ind1=" " ind2=" ">\r
- <subfield code="a"> 99045936</subfield>\r
- </datafield>\r
- ..\r
- </record>\r
- <record>\r
- ..\r
- </record>\r
- </hold:volume>\r
- </hold:volumes> \r
- </programlisting>\r
+ <para><uri>http://<hostname>/opac/extras/supercat/browse/<format>/call_number/<org_unit>/<call_number></uri></para>\r
+ <para>For example, <uri>http://dev.gapines.org/opac/extras/browse/xml/call_number/-/GV</uri> returns:</para>\r
+<screen>\r
+<hold:volumes xmlns:hold='http://open-ils.org/spec/holdings/v1'>\r
+ <hold:volume id="tag:open-ils.org,2008:asset-call_number/130607" lib="FRRLS-FA" label="GUTCHEON BETH">\r
+ <act:owning_lib id="tag:open-ils.org,2008:actor-org_unit/111" name="Fayette County Public Library"/>\r
+ <record xsi:schemaLocation="http://www.loc.gov/MARC21/slim http://www.loc.gov/ \r
+ standards/marcxml/schema/MARC21slim.xsd"\r
+ id="tag:open-ils.org,2008:biblio-record_entry/21669/FRRLS-FA">\r
+ <leader>09319pam a2200961 a 4500</leader>\r
+ <controlfield tag="001"/>\r
+ <controlfield tag="005">20000302124754.0</controlfield>\r
+ <controlfield tag="008">990817s2000 nyu 000 1 eng </controlfield>\r
+ <datafield tag="010" ind1=" " ind2=" ">\r
+ <subfield code="a"> 99045936</subfield>\r
+ </datafield>\r
+ ..\r
+ </record>\r
+ <record>\r
+ ..\r
+ </record>\r
+ </hold:volume>\r
+</hold:volumes> \r
+</screen>\r
</simplesect>\r
<simplesect xml:id="supportedsupercatformats">\r
<title>Supported formats</title>\r
<para>SuperCat maintains a list of supported formats for records and metarecords:</para>\r
- <screen>http://<hostname>/opac/extras/supercat/formats/<record-type></screen>\r
- <para>For example, http://dev.gapines.org/opac/extras/supercat/formats/record returns:</para>\r
- <programlisting>\r
- <formats>\r
+ <para><uri>http://<hostname>/opac/extras/supercat/formats/<record-type></uri></para>\r
+ <para>For example, <uri>http://dev.gapines.org/opac/extras/supercat/formats/record</uri> returns:</para>\r
+<screen>\r
+<formats>\r
\r
- <format>\r
- <name>opac</name>\r
- <type>text/html</type>\r
- </format>\r
+ <format>\r
+ <name>opac</name>\r
+ <type>text/html</type>\r
+ </format>\r
\r
- <format>\r
- <name>htmlholdings</name>\r
- <type>text/html</type>\r
- </format>\r
- \r
- <format>\r
- <name>html</name>\r
- <type>text/html</type>\r
- </format>\r
- \r
- <format>\r
- <name>htmlholdings-full</name>\r
- <type>text/html</type>\r
- </format>\r
- \r
- <format>\r
- <name>html-full</name>\r
- <type>text/html</type>\r
- </format>\r
- \r
- <format>\r
- <name>marcxml</name>\r
- <type>application/xml</type>\r
- <namespace_uri>http://www.loc.gov/MARC21/slim</namespace_uri>\r
- <docs>http://www.loc.gov/marcxml/</docs>\r
- \r
- <schema_location>\r
- http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd\r
- </schema_location>\r
- </format>\r
- \r
- <format>\r
- <name>marcxml-full</name>\r
- <type>application/xml</type>\r
- <namespace_uri>http://www.loc.gov/MARC21/slim</namespace_uri>\r
- <docs>http://www.loc.gov/marcxml/</docs>\r
- \r
- <schema_location>\r
- http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd\r
- </schema_location>\r
- </format>\r
- \r
- <format>\r
- <name>rss2</name>\r
- <type>application/xml</type>\r
- </format>\r
- \r
- <format>\r
- <name>rss2-full</name>\r
- <type>application/xml</type>\r
- </format>\r
- \r
- <format>\r
- <name>rdf_dc</name>\r
- <type>application/xml</type>\r
- <namespace_uri>http://purl.org/dc/elements/1.1/</namespace_uri>\r
- <schema_location>http://purl.org/dc/elements/1.1/</schema_location>\r
- </format>\r
- \r
- <format>\r
- <name>oai_dc</name>\r
- <type>application/xml</type>\r
- <namespace_uri>http://www.openarchives.org/OAI/2.0/oai_dc/</namespace_uri>\r
- <schema_location>http://www.openarchives.org/OAI/2.0/oai_dc.xsd</schema_location>\r
- </format>\r
- \r
- <format>\r
- <name>srw_dc</name>\r
- <type>application/xml</type>\r
- <namespace_uri>info:srw/schema/1/dc-schema</namespace_uri>\r
- \r
- <schema_location>\r
- http://www.loc.gov/z3950/agency/zing/srw/dc-schema.xsd\r
- </schema_location>\r
- </format>\r
- \r
- <format>\r
- <name>mods32</name>\r
- <type>application/xml</type>\r
- <namespace_uri>http://www.loc.gov/mods/v3</namespace_uri>\r
- <docs>http://www.loc.gov/mods/</docs>\r
- <schema_location>http://www.loc.gov/standards/mods/v3/mods-3-2.xsd</schema_location>\r
- </format>\r
- \r
- <format>\r
- <name>mods3</name>\r
- <type>application/xml</type>\r
- <namespace_uri>http://www.loc.gov/mods/v3</namespace_uri>\r
- <docs>http://www.loc.gov/mods/</docs>\r
- <schema_location>http://www.loc.gov/standards/mods/v3/mods-3-1.xsd</schema_location>\r
- </format>\r
- \r
- <format>\r
- <name>mods3-full</name>\r
- <type>application/xml</type>\r
- <namespace_uri>http://www.loc.gov/mods/v3</namespace_uri>\r
- <docs>http://www.loc.gov/mods/</docs>\r
- <schema_location>http://www.loc.gov/standards/mods/v3/mods-3-1.xsd</schema_location>\r
- </format>\r
- \r
- <format>\r
- <name>mods</name>\r
- <type>application/xml</type>\r
- <namespace_uri>http://www.loc.gov/mods/</namespace_uri>\r
- <docs>http://www.loc.gov/mods/</docs>\r
- <schema_location>http://www.loc.gov/standards/mods/mods.xsd</schema_location>\r
- </format>\r
- \r
- <format>\r
- <name>mods-full</name>\r
- <type>application/xml</type>\r
- <namespace_uri>http://www.loc.gov/mods/</namespace_uri>\r
- <docs>http://www.loc.gov/mods/</docs>\r
- <schema_location>http://www.loc.gov/standards/mods/mods.xsd</schema_location>\r
- </format>\r
- \r
- <format>\r
- <name>atom</name>\r
- <type>application/xml</type>\r
- <namespace_uri>http://www.w3.org/2005/Atom</namespace_uri>\r
- <docs>http://www.ietf.org/rfc/rfc4287.txt</docs>\r
- </format>\r
- \r
- <format>\r
- <name>atom-full</name>\r
- <type>application/xml</type>\r
- <namespace_uri>http://www.w3.org/2005/Atom</namespace_uri>\r
- <docs>http://www.ietf.org/rfc/rfc4287.txt</docs>\r
- </format>\r
- </formats>\r
- </programlisting>\r
+ <format>\r
+ <name>htmlholdings</name>\r
+ <type>text/html</type>\r
+ </format>\r
+ \r
+ <format>\r
+ <name>html</name>\r
+ <type>text/html</type>\r
+ </format>\r
+ \r
+ <format>\r
+ <name>htmlholdings-full</name>\r
+ <type>text/html</type>\r
+ </format>\r
+ \r
+ <format>\r
+ <name>html-full</name>\r
+ <type>text/html</type>\r
+ </format>\r
+ \r
+ <format>\r
+ <name>marcxml</name>\r
+ <type>application/xml</type>\r
+ <namespace_uri>http://www.loc.gov/MARC21/slim</namespace_uri>\r
+ <docs>http://www.loc.gov/marcxml/</docs>\r
+ \r
+ <schema_location>\r
+ http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd\r
+ </schema_location>\r
+ </format>\r
+ \r
+ <format>\r
+ <name>marcxml-full</name>\r
+ <type>application/xml</type>\r
+ <namespace_uri>http://www.loc.gov/MARC21/slim</namespace_uri>\r
+ <docs>http://www.loc.gov/marcxml/</docs>\r
+ \r
+ <schema_location>\r
+ http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd\r
+ </schema_location>\r
+ </format>\r
+ \r
+ <format>\r
+ <name>rss2</name>\r
+ <type>application/xml</type>\r
+ </format>\r
+ \r
+ <format>\r
+ <name>rss2-full</name>\r
+ <type>application/xml</type>\r
+ </format>\r
+ \r
+ <format>\r
+ <name>rdf_dc</name>\r
+ <type>application/xml</type>\r
+ <namespace_uri>http://purl.org/dc/elements/1.1/</namespace_uri>\r
+ <schema_location>http://purl.org/dc/elements/1.1/</schema_location>\r
+ </format>\r
+ \r
+ <format>\r
+ <name>oai_dc</name>\r
+ <type>application/xml</type>\r
+ <namespace_uri>http://www.openarchives.org/OAI/2.0/oai_dc/</namespace_uri>\r
+ <schema_location>http://www.openarchives.org/OAI/2.0/oai_dc.xsd</schema_location>\r
+ </format>\r
+ \r
+ <format>\r
+ <name>srw_dc</name>\r
+ <type>application/xml</type>\r
+ <namespace_uri>info:srw/schema/1/dc-schema</namespace_uri>\r
+ \r
+ <schema_location>\r
+ http://www.loc.gov/z3950/agency/zing/srw/dc-schema.xsd\r
+ </schema_location>\r
+ </format>\r
+ \r
+ <format>\r
+ <name>mods32</name>\r
+ <type>application/xml</type>\r
+ <namespace_uri>http://www.loc.gov/mods/v3</namespace_uri>\r
+ <docs>http://www.loc.gov/mods/</docs>\r
+ <schema_location>http://www.loc.gov/standards/mods/v3/mods-3-2.xsd</schema_location>\r
+ </format>\r
+ \r
+ <format>\r
+ <name>mods3</name>\r
+ <type>application/xml</type>\r
+ <namespace_uri>http://www.loc.gov/mods/v3</namespace_uri>\r
+ <docs>http://www.loc.gov/mods/</docs>\r
+ <schema_location>http://www.loc.gov/standards/mods/v3/mods-3-1.xsd</schema_location>\r
+ </format>\r
+ \r
+ <format>\r
+ <name>mods3-full</name>\r
+ <type>application/xml</type>\r
+ <namespace_uri>http://www.loc.gov/mods/v3</namespace_uri>\r
+ <docs>http://www.loc.gov/mods/</docs>\r
+ <schema_location>http://www.loc.gov/standards/mods/v3/mods-3-1.xsd</schema_location>\r
+ </format>\r
+ \r
+ <format>\r
+ <name>mods</name>\r
+ <type>application/xml</type>\r
+ <namespace_uri>http://www.loc.gov/mods/</namespace_uri>\r
+ <docs>http://www.loc.gov/mods/</docs>\r
+ <schema_location>http://www.loc.gov/standards/mods/mods.xsd</schema_location>\r
+ </format>\r
+ \r
+ <format>\r
+ <name>mods-full</name>\r
+ <type>application/xml</type>\r
+ <namespace_uri>http://www.loc.gov/mods/</namespace_uri>\r
+ <docs>http://www.loc.gov/mods/</docs>\r
+ <schema_location>http://www.loc.gov/standards/mods/mods.xsd</schema_location>\r
+ </format>\r
+ \r
+ <format>\r
+ <name>atom</name>\r
+ <type>application/xml</type>\r
+ <namespace_uri>http://www.w3.org/2005/Atom</namespace_uri>\r
+ <docs>http://www.ietf.org/rfc/rfc4287.txt</docs>\r
+ </format>\r
+ \r
+ <format>\r
+ <name>atom-full</name>\r
+ <type>application/xml</type>\r
+ <namespace_uri>http://www.w3.org/2005/Atom</namespace_uri>\r
+ <docs>http://www.ietf.org/rfc/rfc4287.txt</docs>\r
+ </format>\r
+</formats>\r
+</screen>\r
</simplesect>\r
<simplesect>\r
<title>Adding new SuperCat formats</title> \r
- <para>SuperCat web services are based on the OpenSRF application, <emphasis>open-ils.supercat</emphasis>.</para> \r
+ <para>SuperCat web services are based on the OpenSRF service, <systemitem class="service">>open-ils.supercat</systemitem>.</para> \r
<para>Developers are able to add new formats by adding the <emphasis>xsl</emphasis> \r
stylesheet for the format in the directory<filename class="directory">/openils/var/web/opac/extras/xsl/</filename>, and by adding the feed references to the perl modules \r
<filename>openils/lib/perl5/OpenILS/WWW/SuperCat/feed.pm</filename> and <filename>openils/lib/perl5/OpenILS/WWW/SuperCat.pm</filename>. An Evergreen restart is \r
#/bin/bash
#generate draft html
- xsltproc --xinclude --stringparam base.dir /openils/var/web/evergreen_documentation/draft/html/ ~/Evergreen-DocBook/stylesheets/evergreen_docbook_files/evergreen_xhtml.xsl ~/Evergreen-DocBook/1.6/root.xml
+ xsltproc --xinclude --stringparam base.dir /openils/var/web/evergreen_documentation/1.6/draft/html/ ~/Evergreen-DocBook/stylesheets/evergreen_docbook_files/evergreen_xhtml.xsl ~/Evergreen-DocBook/1.6/root.xml
#Generate PDF via FO
# must run fop from same directory as root.xml
cd ~/Evergreen-DocBook/1.6/
- ~/doctools/fop/fop -fo pdf/temp.fo -pdf /openils/var/web/evergreen_documentation/draft/pdf/Evergreen_Documentation.pdf
+ ~/doctools/fop/fop -fo pdf/temp.fo -pdf /openils/var/web/evergreen_documentation/1.6/draft/pdf/Evergreen_Documentation.pdf
# remove temporary .fo file
rm pdf/temp.fo