db-api.xml
Delivered as text/xml
[ hide source ] | [ make this the default ]
File Contents
<?xml version='1.0' ?> <!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [ <!ENTITY % myvars SYSTEM "../variables.ent"> %myvars; ]> <sect1 id="db-api-detailed" xreflabel="Database Access API"> <title>Database Access API</title> <authorblurb> <para>By <ulink url="mailto:jsalz@mit.edu">Jon Salz</ulink>. Revised and expanded by Roberto Mello (rmello at fslc dot usu dot edu), July 2002. </para> </authorblurb> <itemizedlist> <listitem><para>Tcl procedures: /packages/acs-kernel/10-database-procs.tcl</para></listitem> <listitem><para>Tcl initialization: /packages/acs-kernel/database-init.tcl</para></listitem> </itemizedlist> <sect2 id="db-api-detailed-bigpicture"> <title>The Big Picture</title> <para> One of OpenACS's great strengths is that code written for it is very close to the database. It is very easy to interact with the database from anywhere within OpenACS. Our goal is to develop a coherent API for database access which makes this even easier. </para> <para>There were four significant problems with the way OpenACS previously used the database (i.e., directly through the <computeroutput>ns_db</computeroutput> interface):</para> <orderedlist> <listitem><para><emphasis role="strong">Handle management</emphasis>. We required code to pass database handles around, and for routines which needed to perform database access but didn't receive a database handle as input, it was difficult to know from which of the three "magic pools" (main, subquery, and log) to allocate a new handle. </para></listitem> <listitem><para><emphasis role="strong">Nested transactions</emphasis>. In our Oracle driver, <computeroutput>begin transaction</computeroutput> really means "turn auto-commit mode off" and <computeroutput>end transaction</computeroutput> means "commit the current transaction and turn auto-commit mode on." Thus if transactional code needed to call a routine which needed to operate transactionally, the semantics were non-obvious. Consider: </para> <programlisting> proc foo { db args } { db_transaction { ... } } db_transaction { db_dml unused {insert into greeble(bork) values(33)} foo $db db_dml unused {insert into greeble(bork) values(50)} } </programlisting> <para> This would insert greeble #33 and do all the stuff in <computeroutput>foo</computeroutput> transactionally, but the <computeroutput>end transaction</computeroutput> in <computeroutput>foo</computeroutput> would actually cause a commit, and greeble #50 would later be inserted in auto-commit mode. This could cause subtle bugs: e.g., in the case that the insert for greeble #50 failed, part of the "transaction" would have already have been committed!. This is not a good thing. </para></listitem> <listitem><para><emphasis role="strong">Unorthodox use of variables</emphasis>. The standard mechanism for mapping column values into variables involved the use of the <computeroutput>set_variables_after_query</computeroutput> routine, which relies on an uplevel variable named <computeroutput>selection</computeroutput> (likewise for <computeroutput>set_variables_after_subquery</computeroutput> and <computeroutput>subselection</computeroutput>). </para></listitem> <listitem><para><emphasis role="strong">Hard-coded reliance on Oracle</emphasis>. It's difficult to write code supporting various different databases (dynamically using the appropriate dialect based on the type of database being used, e.g., using <computeroutput>DECODE</computeroutput> on Oracle and <computeroutput>CASE ... WHEN</computeroutput> on Postgres).</para></listitem> </orderedlist> <para> The Database Access API addresses the first three problems by: </para> <orderedlist> <listitem><para>making use of database handles transparent</para></listitem> <listitem><para>wrapping common database operations (including transaction management) in Tcl control structures (this is, after all, what Tcl is good at!)</para></listitem> </orderedlist> <para> It lays the groundwork for addressing the fourth problem by assigning each SQL statement a logical name. In a future version of the OpenACS Core, this API will translate logical statement names into actual SQL, based on the type of database in use. (To smooth the learning curve, we provide a facility for writing SQL inline for a "default SQL dialect", which we assume to be Oracle for now.) </para> <para>To be clear, SQL abstraction is <emphasis>not</emphasis> fully implemented in OpenACS 3.3.1. The statement names supplied to each call are not used by the API at all. The API's design for SQL abstraction is in fact incomplete; unresolved issues include:</para> <itemizedlist> <listitem><para>how to add <computeroutput>WHERE</computeroutput> clause criteria dynamically</para></listitem> <listitem><para>how to build a dynamic <computeroutput>ORDER BY</computeroutput> clause (Ben Adida has a proposed solution for this)</para></listitem> <listitem><para>how to define a statement's formal interface (i.e., what bind variables it expects, what columns its <computeroutput>SELECT</computeroutput> clause must contain if it's a query) without actually implementing the statement in a specific SQL dialect</para></listitem> </itemizedlist> <para> So why is the incremental change of adding statement naming to the API worth the effort? It is worth the effort because we know that giving each SQL statement a logical name will be required by the complete SQL abstraction design. Therefore, we know that the effort will not be wasted, and taking advantage of the new support for bind variables will already require code that uses 3.3.0 version of the API to be updated. </para> </sect2> <sect2 id="db-api-detailed-set-var-aft-query"> <title>The Bell Tolls for <computeroutput>set_variables_after_query</computeroutput></title> <para> <computeroutput>set_variables_after_query</computeroutput> is gone! (Well, it's still there, but you'll never need to use it.) The new API routines set local variables automatically. For instance: </para> <programlisting> db_1row select_names "select first_names, last_name from users where user_id = [ad_conn user_id]" doc_body_append "Hello, $first_names $last_name!" </programlisting> <para> Like <computeroutput>ns_db 1row</computeroutput>, this will bomb if the query doesn't return any rows (no such user exists). If this isn't what you want, you can write: </para> <programlisting> if { [db_0or1row select_names "select first_names, last_name from users where user_id = [ad_conn user_id]"] } { doc_body_append "Hello, $first_names $last_name!" } else { # Executed if the query returns no rows. doc_body_append "There's no such user!" } </programlisting> <para> Selecting a bunch of rows is a lot prettier now: </para> <programlisting> db_foreach select_names "select first_names, last_name from users" { doc_body_append "Say hi to $first_names $last_name for me!<br>" } </programlisting> <para> That's right, <computeroutput>db_foreach</computeroutput> is now like <computeroutput>ns_db select</computeroutput> plus a <computeroutput>while</computeroutput> loop plus <computeroutput>set_variables_after_query</computeroutput> plus an <computeroutput>if</computeroutput> statement (containing code to be executed if no rows are returned). </para> <programlisting> db_foreach select_names "select first_names, last_name from users where last_name like 'S%'" { doc_body_append "Say hi to $first_names $last_name for me!<br>" } if_no_rows { doc_body_append "There aren't any users with last names beginnings with S!" } </programlisting> </sect2> <sect2 id="db-api-detailed-handles"> <title>Handle Management</title> <para> The new API keeps track of which handles are in use, and automatically allocates new handles when they are necessary (e.g., to perform subqueries while a select is active). For example: </para> <programlisting> doc_body_append "<ul>" db_foreach select_names "select first_names, last_name, user_id from users" { # Automatically allocated a database handle from the main pool. doc_body_append "<li>User $first_names $last_name\n<ul>" db_foreach select_groups "select group_id from user_group_map where user_id = $user_id" { # There's a selection in progress, so we allocated a database handle # from the subquery pool for this selection. doc_body_append "<li>Member of group #$group_id.\n" } if_no_rows { # Not a member of any groups. doc_body_append "<li>Not a member of any group.\n" } } doc_body_append "</ul>" db_release_unused_handles </programlisting> <para> A new handle isn't actually allocated and released for every selection, of course - as a performance optimization, the API keeps old handles around until <computeroutput>db_release_unused_handles</computeroutput> is invoked (or the script terminates). </para> <para>Note that there is no analogue to <computeroutput>ns_db gethandle</computeroutput> - the handle is always automatically allocated the first time it's needed.</para> </sect2> <sect2 id="db-api-detailed-bindvars"> <title>Bind Variables</title> <para><emphasis role="strong">Introduction</emphasis></para> <para> Most SQL statements require that the code invoking the statement pass along data associated with that statement, usually obtained from the user. For instance, in order to delete a WimpyPoint presentation, a Tcl script might use the SQL statement </para> <programlisting> delete from wp_presentations where presentation_id = <emphasis>some_presentation_id</emphasis> </programlisting> <para> where <emphasis><computeroutput>some_presentation_id</computeroutput></emphasis> is a number which is a valid presentation ID of the presentation I want to delete. It's easy to write code handling situations like this since SQL statements can include <emphasis role="strong">bind variables</emphasis>, which represent placeholders for actual data. A bind variable is specified as a colon followed by an identifier, so the statement above can be coded as: </para> <programlisting> db_dml presentation_delete { delete from wp_presentations where presentation_id = :some_presentation_id } </programlisting> <para> When this SQL statement is invoked, the value for the bind variable <computeroutput>:some_presentation_id</computeroutput> is pulled from the Tcl variable <computeroutput>$some_presentation_id</computeroutput> (in the caller's environment). Note that bind variables are not limited to one per statement; you can use an arbitrary number, and each will pull from the correspondingly named Tcl variable. (Alternatively, you can also specify a list or <computeroutput>ns_set</computeroutput> providing bind variables' values; see <emphasis>Usage</emphasis>.) </para> <para>The value of a bind variable is taken literally by the database driver, so there is never any need to put single-quotes around the value for a bind variable, or to use <computeroutput>db_quote</computeroutput> to escape single-quotes contained in the value. The following works fine, despite the apostrophe:</para> <programlisting> set exclamation "That's all, folks!" db_dml exclamation_insert { insert into exclamations(exclamation) values(:exclamation) } </programlisting> <para>Note that you can use a bind variable in a SQL statement only where you could use a literal (a number or single-quoted string). Bind variables cannot be placeholders for things like SQL keywords, table names, or column names, so the following will not work, even if <computeroutput>$table_name</computeroutput> is set properly:</para> <programlisting> select * from :table_name </programlisting> <para><emphasis role="strong">Why Bind Variables Are Useful</emphasis></para> <para> Why bother with bind variables at all - why not just write the Tcl statement above like this: </para> <programlisting> db_dml presentation_delete " delete from wp_presentations where presentation_id = $some_presentation_id " </programlisting> <para> (Note the use of double-quotes to allow the variable reference to <computeroutput>$some_presentation_id</computeroutput> to be interpolated in.) This will work, but consider the case where some devious user causes <computeroutput>some_presentation_id</computeroutput> to be set to something like <computeroutput>'3 or 1 = 1'</computeroutput>, which would result in the following statement being executed: </para> <programlisting> delete from wp_presentations where presentation_id = 3 or 1 = 1 </programlisting> <para> This deletes every presentation in the database! Using bind variables eliminates this gaping security hole: since bind variable values are taken literally. Oracle will attempt to delete presentations whose presentation ID is literally <computeroutput>'3 or 1 = 1'</computeroutput> (i.e., no presentations, since <computeroutput>'3 or 1 = 1'</computeroutput> can't possibly be a valid integer primary key for <computeroutput>wp_presentations</computeroutput>. In general, since Oracle always considers the values of bind variables to be literals, it becomes more difficult for users to perform URL surgery to trick scripts into running dangerous queries and DML. </para> <para><emphasis role="strong">Usage</emphasis></para> <para>Every <computeroutput>db_*</computeroutput> command accepting a SQL command as an argument supports bind variables. You can either</para> <itemizedlist> <listitem><para>specify the <computeroutput>-bind</computeroutput> switch to provide a set with bind variable values, or</para></listitem> <listitem><para>specify the <computeroutput>-bind</computeroutput> switch to explicitly provide a list of bind variable names and values, or</para></listitem> <listitem><para>not specify a bind variable list at all, in which case Tcl variables are used as bind variables.</para></listitem> </itemizedlist> <para> The default behavior (i.e., if the <computeroutput>-bind</computeroutput> switch is omitted) is that these procedures expect to find local variables that correspond in name to the referenced bind variables, e.g.: </para> <programlisting> set user_id 123456 set role "administrator" db_foreach user_group_memberships_by_role { select g.group_id, g.group_name from user_groups g, user_group_map map where g.group_id = map.user_id and map.user_id = :user_id and map.role = :role } { # do something for each group of which user 123456 is in the role # of "administrator" } </programlisting> <para> The value of the local Tcl variable <computeroutput>user_id</computeroutput> (123456) is bound to the <computeroutput>user_id</computeroutput> bind variable. </para> <para>The <computeroutput>-bind</computeroutput> switch can takes the name of an <computeroutput>ns_set</computeroutput> containing keys for each bind variable named in the query, e.g.:</para> <programlisting> set bind_vars [ns_set create] ns_set put $bind_vars user_id 123456 ns_set put $bind_vars role "administrator" db_foreach user_group_memberships_by_role { select g.group_id, g.group_name from user_groups g, user_group_map map where g.group_id = map.user_id and map.user_id = :user_id and map.role = :role } -bind $bind_vars { # do something for each group in which user 123456 has the role # of "administrator" } </programlisting> <para> Alternatively, as an argument to <computeroutput>-bind</computeroutput> you can specify a list of alternating name/value pairs for bind variables: </para> <programlisting> db_foreach user_group_memberships_by_role { select g.group_id, g.group_name from user_groups g, user_group_map map where g.group_id = map.user_id and map.user_id = :user_id and map.role = :role } -bind [list user_id 123456 role "administrator"] { # do something for each group in which user 123456 has the role # of "administrator" } </programlisting> <para><emphasis role="strong"><anchor id="kernel.dbapi_nulls_and_bind_vars"/>Nulls and Bind Variables</emphasis></para> <para> When processing a DML statement, Oracle coerces empty strings into <computeroutput>null</computeroutput>. (This coercion does <emphasis>not</emphasis> occur in the <computeroutput>WHERE</computeroutput> clause of a query, i.e. <computeroutput>col = ''</computeroutput> and <computeroutput>col is null</computeroutput> are not equivalent.) </para> <para>As a result, when using bind variables, the only way to make Oracle set a column value to <computeroutput>null</computeroutput> is to set the corresponding bind variable to the empty string, since a bind variable whose value is the string "null" will be interpreted as the literal string "null".</para> <para>These Oracle quirks complicate the process of writing clear and abstract DML difficult. Here is an example that illustrates why:</para> <programlisting> # # Given the table: # # create table foo ( # bar integer, # baz varchar(10) # ); # set bar "" set baz "" db_dml foo_create {insert into foo(bar, baz) values(:bar, :baz)} # # the values of the "bar" and "baz" columns in the new row are both # null, because Oracle has coerced the empty string (even for the # numeric column "bar") into null in both cases </programlisting> </sect2> <sect2 id="db-api-detailed-sql-abstraction"> <title>SQL Abstraction</title> <para> We now require that each SQL statement be assigned a logical name for the statement that is unique to the procedure or page in which it is defined. This is so that (eventually) we can implement logically named statements with alternative SQL for non-Oracle databases (e.g., Postgres). More on this later. </para> </sect2> <sect2 id="db-api-detailed-placing-values"> <title>Placing Column Values in Arrays and Sets</title> <para> Normally, <computeroutput>db_foreach</computeroutput>, <computeroutput>db_0or1row</computeroutput>, and <computeroutput>db_1row</computeroutput> places the results of queries in Tcl variables, so you can say: </para> <programlisting> db_foreach users_select "select first_names, last_name from users" { doc_body_append "<li>$first_names $last_name\n" } </programlisting> <para> However, sometimes this is not sufficient: you may need to examine the rows returned, to dynamically determine the set of columns returned by the query, or to avoid collisions with existing variables. You can use the <computeroutput>-column_array</computeroutput> and <computeroutput>-column_set</computeroutput> switches to <computeroutput>db_foreach</computeroutput>, <computeroutput>db_0or1row</computeroutput>, and <computeroutput>db_1row</computeroutput> to instruct the database routines to place the results in a Tcl array or <computeroutput>ns_set</computeroutput>, respectively, where the keys are the column names and the values are the column values. For example: </para> <programlisting> db_foreach users_select "select first_names, last_name from users" -column_set columns { # Now $columns is an ns_set. doc_body_append "<li>" for { set i 0 } { $i < [ns_set size $columns] } { incr i } { doc_body_append "[ns_set key $columns $i] is [ns_set value $columns $i]. \n" } } </programlisting> <para> will write something like: </para> <itemizedlist> <listitem><para>first_names is Jon. last_name is Salz.</para></listitem> <listitem><para>first_names is Lars. last_name is Pind.</para></listitem> <listitem><para>first_names is Michael. last_name is Yoon.</para></listitem> </itemizedlist> </sect2> <sect2 id="dp-api-detailed-api"> <title>API</title> <para> Note that you never have to use <computeroutput>ns_db</computeroutput> anymore (including <computeroutput>ns_db gethandle</computeroutput>)! Just start doing stuff, and (if you want) call <computeroutput>db_release_unused_handles</computeroutput> when you're done as a hint to release the database handle. </para> <variablelist> <varlistentry> <term><emphasis role="strong"><computeroutput><anchor id="kernel.dbapi_db_null"/>db_null</computeroutput></emphasis> </term> <listitem> <programlisting> <emphasis role="strong"><computeroutput>db_null</computeroutput></emphasis> </programlisting> <para>Returns a value which can be used in a bind variable to represent the SQL value <computeroutput>null</computeroutput>. See <link linkend="dbapi_nulls_and_bind_vars">Nulls and Bind Variables</link> above.</para></listitem> </varlistentry> <varlistentry> <term> <emphasis role="strong"><computeroutput><anchor id="kernel.dbapi_db_foreach"/>db_foreach</computeroutput></emphasis> </term> <listitem> <programlisting> <emphasis role="strong">db_foreach</emphasis> <emphasis> statement-name sql</emphasis> [ -bind <emphasis>bind_set_id</emphasis> | -bind <emphasis>bind_value_list</emphasis> ] \ [ -column_array <emphasis>array_name</emphasis> | -column_set <emphasis>set_name</emphasis> ] \ <emphasis>code_block</emphasis> [ if_no_rows <emphasis>if_no_rows_block ]</emphasis> </programlisting> <para>Performs the SQL query <emphasis><computeroutput>sql</computeroutput></emphasis>, executing <emphasis><computeroutput>code_block</computeroutput></emphasis> once for each row with variables set to column values (or a set or array populated if <computeroutput>-column_array</computeroutput> or <computeroutput>column_set</computeroutput> is specified). If the query returns no rows, executes <emphasis><computeroutput>if_no_rows_block</computeroutput></emphasis> (if provided). </para> <para>Example:</para> <programlisting> db_foreach select_foo "select foo, bar from greeble" { doc_body_append "<li>foo=$foo; bar=$bar\n" } if_no_rows { doc_body_append "<li>There are no greebles in the database.\n" } </programlisting> <para> The code block may contain <computeroutput>break</computeroutput> statements (which terminate the loop and flush the database handle) and <computeroutput>continue</computeroutput> statements (which continue to the next row of the loop). </para> </listitem> </varlistentry> <varlistentry> <term><emphasis role="strong"><computeroutput><anchor id="kernel.dbapi_db_1row"/>db_1row</computeroutput></emphasis></term> <listitem> <programlisting> <emphasis role="strong">db_1row</emphasis> <emphasis> statement-name sql</emphasis> [ -bind <emphasis>bind_set_id</emphasis> | -bind <emphasis>bind_value_list</emphasis> ] \ [ -column_array <emphasis>array_name</emphasis> | -column_set <emphasis>set_name</emphasis> ] </programlisting> <para>Performs the SQL query <emphasis><computeroutput>sql</computeroutput></emphasis>, setting variables to column values. Raises an error if the query does not return exactly 1 row. </para> <para>Example:</para> <programlisting> db_1row select_foo "select foo, bar from greeble where greeble_id = $greeble_id" # Bombs if there's no such greeble! # Now $foo and $bar are set. </programlisting> </listitem> </varlistentry> <varlistentry> <term><emphasis role="strong"><computeroutput><anchor id="kernel.dbapi_db_0or1row"/>db_0or1row</computeroutput></emphasis> </term> <listitem> <programlisting> <emphasis role="strong">db_0or1row</emphasis> <emphasis> statement-name sql</emphasis> [ -bind <emphasis>bind_set_id</emphasis> | -bind <emphasis>bind_value_list</emphasis> ] \ [ -column_array <emphasis>array_name</emphasis> | -column_set <emphasis>set_name</emphasis> ] </programlisting> <para>Performs the SQL query <emphasis><computeroutput>sql</computeroutput></emphasis>. If a row is returned, sets variables to column values and returns 1. If no rows are returned, returns 0. If more than one row is returned, throws an error. </para> </listitem> </varlistentry> <varlistentry> <term><emphasis role="strong"><computeroutput><anchor id="kernel.dbapi_db_string"/>db_string</computeroutput></emphasis> </term> <listitem> <programlisting> <emphasis role="strong">db_string</emphasis> <emphasis> statement-name sql</emphasis> [ -default <emphasis>default</emphasis> ] [ -bind <emphasis>bind_set_id</emphasis> | -bind <emphasis>bind_value_list</emphasis> ] </programlisting> <para>Returns the first column of the result of SQL query <emphasis><computeroutput>sql</computeroutput></emphasis>. If <emphasis><computeroutput>sql</computeroutput></emphasis> doesn't return a row, returns <emphasis><computeroutput>default</computeroutput></emphasis> (or throws an error if <emphasis><computeroutput>default</computeroutput></emphasis> is unspecified). Analogous to <computeroutput>database_to_tcl_string</computeroutput> and <computeroutput>database_to_tcl_string_or_null</computeroutput>. </para></listitem> </varlistentry> <varlistentry> <term><emphasis role="strong"><computeroutput><anchor id="kernel.dbapi_db_nextval"/>db_nextval</computeroutput></emphasis> </term> <listitem> <programlisting> <emphasis role="strong">db_nextval</emphasis> <emphasis> sequence-name</emphasis> </programlisting> <para>Returns the next value for the sequence <emphasis>sequence-name</emphasis> (using a SQL statement like <computeroutput>SELECT</computeroutput> <emphasis><computeroutput>sequence-name</computeroutput></emphasis><computeroutput>.nextval FROM DUAL</computeroutput>). If sequence pooling is enabled for the sequence, transparently uses a value from the pool if available to save a round-trip to the database. </para></listitem> </varlistentry> <varlistentry> <term><emphasis role="strong"><computeroutput><anchor id="kernel.dbapi_db_list"/>db_list</computeroutput></emphasis></term> <listitem> <programlisting> <emphasis role="strong">db_list</emphasis> <emphasis> statement-name sql</emphasis> [ -bind <emphasis>bind_set_id</emphasis> | -bind <emphasis>bind_value_list</emphasis> ] </programlisting> <para>Returns a Tcl list of the values in the first column of the result of SQL query <emphasis><computeroutput>sql</computeroutput></emphasis>. If <emphasis><computeroutput>sql</computeroutput></emphasis> doesn't return any rows, returns an empty list. Analogous to <computeroutput>database_to_tcl_list</computeroutput>. </para></listitem> </varlistentry> <varlistentry> <term><emphasis role="strong"><computeroutput><anchor id="kernel.dbapi_db_list_of_lists"/>db_list_of_lists</computeroutput></emphasis></term> <listitem> <programlisting> <emphasis role="strong">db_list_of_lists</emphasis> <emphasis> statement-name sql</emphasis> [ -bind <emphasis>bind_set_id</emphasis> | -bind <emphasis>bind_value_list</emphasis> ] </programlisting> <para>Returns a Tcl list, each element of which is a list of all column values in a row of the result of SQL query <emphasis><computeroutput>sql</computeroutput></emphasis>. If <emphasis><computeroutput>sql</computeroutput></emphasis> doesn't return any rows, returns an empty list. (Analogous to <computeroutput>database_to_tcl_list_list</computeroutput>.) </para></listitem> </varlistentry> <varlistentry> <term><emphasis role="strong"><computeroutput><anchor id="kernel.dbapi_db_list_of_ns_sets"/>db_list_of_ns_sets</computeroutput></emphasis></term> <listitem> <programlisting> <emphasis role="strong">db_list_of_ns_sets</emphasis> <emphasis> statement-name sql</emphasis> [ -bind <emphasis>bind_set_id</emphasis> | -bind <emphasis>bind_value_list</emphasis> ] </programlisting> <para> Returns a list of ns_sets with the values of each column of each row returned by the <computeroutput>sql</computeroutput> query specified. </para> </listitem> </varlistentry> <varlistentry> <term><emphasis role="strong"><computeroutput><anchor id="kernel.dbapi_db_dml"/>db_dml</computeroutput></emphasis></term> <listitem> <programlisting> <emphasis role="strong">db_dml</emphasis> <emphasis> statement-name sql</emphasis> \ [ -bind <emphasis>bind_set_id</emphasis> | -bind <emphasis>bind_value_list</emphasis> ] \ [ -blobs <emphasis>blob_list</emphasis> | -clobs <emphasis>clob_list</emphasis> | -blob_files <emphasis>blob_file_list</emphasis> | -clob_files <emphasis>clob_file_list</emphasis> ] </programlisting> <para>Performs the DML or DDL statement <emphasis><computeroutput>sql</computeroutput></emphasis>. </para> <para>If a length-<emphasis>n</emphasis> list of blobs or clobs is provided, then the SQL should return <emphasis>n</emphasis> blobs or clobs into the bind variables <computeroutput>:1</computeroutput>, <computeroutput>:2</computeroutput>, ... :<emphasis><computeroutput>n</computeroutput></emphasis>. <emphasis><computeroutput>blobs</computeroutput></emphasis> or <emphasis><computeroutput>clobs</computeroutput></emphasis>, if specified, should be a list of individual BLOBs or CLOBs to insert; <emphasis><computeroutput>blob_files</computeroutput></emphasis> or <emphasis><computeroutput>clob_files</computeroutput></emphasis>, if specified, should be a list of <emphasis>paths to files</emphasis> containing the data to insert. Only one of <computeroutput>-blobs</computeroutput>, <computeroutput>-clobs</computeroutput>, <computeroutput>-blob_files</computeroutput>, and <computeroutput>-clob_files</computeroutput> may be provided.</para> <para>Example:</para> <programlisting> db_dml insert_photos { insert photos(photo_id, image, thumbnail_image) values(photo_id_seq.nextval, empty_blob(), empty_blob()) returning image, thumbnail_image into :1, :2 } -blob_files [list "/var/tmp/the_photo" "/var/tmp/the_thumbnail"] </programlisting> <para> This inserts a new row into the <computeroutput>photos</computeroutput> table, with the contents of the files <computeroutput>/var/tmp/the_photo</computeroutput> and <computeroutput>/var/tmp/the_thumbnail</computeroutput> in the <computeroutput>image</computeroutput> and <computeroutput>thumbnail</computeroutput> columns, respectively. </para></listitem> </varlistentry> <varlistentry> <term> <emphasis role="strong"><computeroutput><anchor id="kernel.dbapi_db_write_clob"/>db_write_clob</computeroutput></emphasis>, <emphasis role="strong"><computeroutput><anchor id="kernel.dbapi_db_write_blob"/>db_write_blob</computeroutput></emphasis>, <emphasis role="strong"><computeroutput><anchor id="kernel.dbapi_db_blob_get_file"/>db_blob_get_file</computeroutput></emphasis> </term> <listitem> <programlisting> <emphasis role="strong">db_write_clob</emphasis> <emphasis> statement-name sql</emphasis> [ -bind <emphasis>bind_set_id</emphasis> | -bind <emphasis>bind_value_list</emphasis> ] <emphasis role="strong">db_write_blob</emphasis> <emphasis> statement-name sql</emphasis> [ -bind <emphasis>bind_set_id</emphasis> | -bind <emphasis>bind_value_list</emphasis> ] <emphasis role="strong">db_blob_get_file</emphasis> <emphasis> statement-name sql</emphasis> [ -bind <emphasis>bind_set_id</emphasis> | -bind <emphasis>bind_value_list</emphasis> ] </programlisting> <para>Analogous to <computeroutput>ns_ora write_clob/write_blob/blob_get_file</computeroutput>. </para></listitem> </varlistentry> <varlistentry> <term><emphasis role="strong"><computeroutput><anchor id="kernel.dbapi_db_release_unused_handles"/>db_release_unused_handles</computeroutput></emphasis></term> <listitem> <programlisting> <emphasis role="strong">db_release_unused_handles</emphasis> </programlisting> <para>Releases any allocated, unused database handles. </para> </listitem> </varlistentry> <varlistentry> <term><emphasis role="strong"><computeroutput><anchor id="kernel.dbapi_db_transaction"/>db_transaction</computeroutput></emphasis></term> <listitem> <programlisting> <emphasis role="strong">db_transaction</emphasis> <emphasis> code_block</emphasis> [ on_error { <emphasis>code_block</emphasis> } ] </programlisting> <para>Executes <emphasis><computeroutput>code_block</computeroutput></emphasis> transactionally. Nested transactions are supported (<computeroutput>end transaction</computeroutput> is transparently <computeroutput>ns_db dml</computeroutput>'ed when the outermost transaction completes). The <computeroutput>db_abort_transaction</computeroutput> command can be used to abort all levels of transactions. It is possible to specify an optional <computeroutput>on_error</computeroutput> code block that will be executed if some code in <emphasis>code_block</emphasis> throws an exception. The variable <computeroutput>errmsg</computeroutput> will be bound in that scope. If there is no <computeroutput>on_error</computeroutput> code, any errors will be propagated. </para> <para>Example:</para> <programlisting> proc replace_the_foo { col } { db_transaction { db_dml delete {delete from foo} db_dml insert {insert into foo(col) values($col)} } } proc print_the_foo {} { doc_body_append "foo is [db_string "select col from foo"]<br>\n" } replace_the_foo 8 print_the_foo ; # Writes out "foo is 8" db_transaction { replace_the_foo 14 print_the_foo ; # Writes out "foo is 14" db_dml insert_foo {insert into some_other_table(col) values(999)} ... db_abort_transaction } on_error { doc_body_append "Error in transaction: $errmsg" } print_the_foo ; # Writes out "foo is 8" </programlisting> </listitem> </varlistentry> <varlistentry> <term><emphasis role="strong"><computeroutput><anchor id="kernel.dbapi_db_abort_transaction"/>db_abort_transaction</computeroutput></emphasis> </term> <listitem> <programlisting> <emphasis role="strong">db_abort_transaction</emphasis> </programlisting> <para>Aborts all levels of a transaction. That is if this is called within several nested transactions, all of them are terminated. Use this instead of <computeroutput>db_dml "abort" "abort transaction"</computeroutput>. </para></listitem> </varlistentry> <varlistentry> <term><emphasis role="strong"><computeroutput><anchor id="kernel.dbapi_db_multirow"/>db_multirow</computeroutput></emphasis></term> <listitem> <programlisting> <emphasis role="strong">db_multirow</emphasis> [ -local ] [ -append ] [ -extend <emphasis>column_list</emphasis> ] \ <emphasis>var-name</emphasis> <emphasis> statement-name sql</emphasis> \ [ -bind <emphasis>bind_set_id</emphasis> | -bind <emphasis>bind_value_list</emphasis> ] \ <emphasis>code_block</emphasis> [ if_no_rows <emphasis>if_no_rows_block ]</emphasis> </programlisting> <para> Performs the SQL query <computeroutput>sql</computeroutput>, saving results in variables of the form <computeroutput><replaceable>var_name</replaceable>:1</computeroutput>, <computeroutput><replaceable>var_name</replaceable>:2</computeroutput>, etc, setting <computeroutput><replaceable>var_name</replaceable>:rowcount</computeroutput> to the total number of rows, and setting <computeroutput><replaceable>var_name</replaceable>:columns</computeroutput> to a list of column names. </para> <para> Each row also has a column, rownum, automatically added and set to the row number, starting with 1. Note that this will override any column in the SQL statement named 'rownum', also if you're using the Oracle rownum pseudo-column. </para> <para> If the <computeroutput>-local</computeroutput> is passed, the variables defined by db_multirow will be set locally (useful if you're compiling dynamic templates in a function or similar situations). </para> <para> You may supply a code block, which will be executed for each row in the loop. This is very useful if you need to make computations that are better done in Tcl than in SQL, for example using ns_urlencode or ns_quotehtml, etc. When the Tcl code is executed, all the columns from the SQL query will be set as local variables in that code. Any changes made to these local variables will be copied back into the multirow. </para> <para> You may also add additional, computed columns to the multirow, using the <computeroutput>-extend { <replaceable>col_1</replaceable> <replaceable>col_2</replaceable> ... }</computeroutput> switch. This is useful for things like constructing a URL for the object retrieved by the query. </para> <para> If you're constructing your multirow through multiple queries with the same set of columns, but with different rows, you can use the <computeroutput>-append</computeroutput> switch. This causes the rows returned by this query to be appended to the rows already in the multirow, instead of starting a clean multirow, as is the normal behavior. The columns must match the columns in the original multirow, or an error will be thrown. </para> <para> Your code block may call <computeroutput>continue</computeroutput> in order to skip a row and not include it in the multirow. Or you can call <computeroutput>break</computeroutput> to skip this row and quit looping. </para> <para> Notice the nonstandard numbering (everything else in Tcl starts at 0); the reason is that the graphics designer, a non-programmer, may wish to work with row numbers. </para> <para> Example: </para> <programlisting> db_multirow -extend { user_url } users users_query { select user_id first_names, last_name, email from cc_users } { set user_url [acs_community_member_url -user_id $user_id] } </programlisting> </listitem> </varlistentry> <varlistentry> <term><emphasis role="strong"><computeroutput><anchor id="kernel.dbapi_db_resultrows"/>db_resultrows</computeroutput></emphasis></term> <listitem> <programlisting> <emphasis role="strong">db_resultrows</emphasis> </programlisting> <para>Returns the number of rows affected or returned by the previous statement. </para></listitem> </varlistentry> <varlistentry> <term><emphasis role="strong"><computeroutput><anchor id="kernel.dbapi_db_with_handle"/>db_with_handle</computeroutput></emphasis></term> <listitem> <programlisting> <emphasis role="strong">db_with_handle</emphasis> <emphasis> var code_block</emphasis> </programlisting> <para>Places a database handle into the variable <emphasis><computeroutput>var</computeroutput></emphasis> and executes <emphasis><computeroutput>code_block</computeroutput></emphasis>. This is useful when you don't want to have to use the new API (<computeroutput>db_foreach</computeroutput>, <computeroutput>db_1row</computeroutput>, etc.), but need to use database handles explicitly. </para> <para>Example:</para> <programlisting> proc lookup_the_foo { foo } { db_with_handle db { return [db_string unused "select ..."] } } db_with_handle db { # Now there's a database handle in $db. set selection [ns_db select $db "select foo from bar"] while { [ns_db getrow $db $selection] } { set_variables_after_query lookup_the_foo $foo } } </programlisting> </listitem> </varlistentry> <varlistentry> <term> <emphasis role="strong"> <computeroutput> <anchor id="kernel.dbapi_db_name"/>db_name </computeroutput> </emphasis> </term> <listitem> <programlisting> <emphasis role="strong"> <computeroutput>db_name</computeroutput> </emphasis> </programlisting> <para> Returns the name of the database, as returned by the driver. </para> </listitem> </varlistentry> <varlistentry> <term> <emphasis role="strong"> <computeroutput> <anchor id="kernel.dbapi_db_type"/>db_type </computeroutput> </emphasis> </term> <listitem> <programlisting> <emphasis role="strong"> <computeroutput>db_type</computeroutput> </emphasis> </programlisting> <para> Returns the RDBMS type (i.e. oracle, postgresql) this OpenACS installation is using. The nsv ad_database_type is set up during the bootstrap process. </para> </listitem> </varlistentry> <varlistentry> <term> <emphasis role="strong"> <computeroutput> <anchor id="kernel.dbapi_db_compatible_rdbms_p"/>db_compatible_rdbms_p </computeroutput> </emphasis> </term> <listitem> <programlisting> <emphasis role="strong">db_compatible_rdbms_p</emphasis> db_type </programlisting> <para> Returns 1 if the given db_type is compatible with the current RDBMS. </para> </listitem> </varlistentry> <varlistentry> <term> <emphasis role="strong"> <computeroutput> <anchor id="kernel.dbapi_db_package_supports_rdbms_p"/>db_package_supports_rdbms_p </computeroutput> </emphasis> </term> <listitem> <programlisting> <emphasis role="strong">db_package_supports_rdbms_p</emphasis> db_type_list </programlisting> <para> Returns 1 if db_type_list contains the current RDMBS type. A package intended to run with a given RDBMS must note this in it's package info file regardless of whether or not it actually uses the database. </para> </listitem> </varlistentry> <varlistentry> <term> <emphasis role="strong"> <computeroutput> <anchor id="kernel.dbapi_db_legacy_package_p"/>db_legacy_package_p </computeroutput> </emphasis> </term> <listitem> <programlisting> <emphasis role="strong">db_legacy_package_p</emphasis> db_type_list </programlisting> <para> Returns 1 if the package is a legacy package. We can only tell for certain if it explicitly supports Oracle 8.1.6 rather than the OpenACS more general oracle. </para> </listitem> </varlistentry> <varlistentry> <term> <emphasis role="strong"> <computeroutput> <anchor id="kernel.dbapi_db_version"/>db_version </computeroutput> </emphasis> </term> <listitem> <programlisting> <emphasis role="strong">db_version</emphasis> </programlisting> <para> Returns the RDBMS version (i.e. 8.1.6 is a recent Oracle version; 7.1 a recent PostgreSQL version. </para> </listitem> </varlistentry> <varlistentry> <term> <emphasis role="strong"> <computeroutput> <anchor id="kernel.dbapi_db_current_rdbms"/>db_current_rdbms </computeroutput> </emphasis> </term> <listitem> <programlisting> <emphasis role="strong">db_current_rdbms</emphasis> </programlisting> <para> Returns the current rdbms type and version. </para> </listitem> </varlistentry> <varlistentry> <term> <emphasis role="strong"> <computeroutput> <anchor id="kernel.dbapi_db_known_database_types"/>db_known_database_types </computeroutput> </emphasis> </term> <listitem> <programlisting> <emphasis role="strong">db_known_database_types</emphasis> </programlisting> <para> Returns a list of three-element lists describing the database engines known to OpenACS. Each sublist contains the internal database name (used in file paths, etc), the driver name, and a "pretty name" to be used in selection forms displayed to the user. </para> <para> The nsv containing the list is initialized by the bootstrap script and should never be referenced directly by user code. Returns the current rdbms type and version. </para> </listitem> </varlistentry> </variablelist> <para><phrase role="cvstag">($Id: db-api.xml,v 1.12.2.5 2024/02/05 15:35:07 gustafn Exp $)</phrase></para> </sect2> </sect1>