diff --git a/collects/db/TODO b/collects/db/TODO index 3a69ba3572..1c31fcc149 100644 --- a/collects/db/TODO +++ b/collects/db/TODO @@ -71,3 +71,10 @@ Misc - connection-pool-lease-evt - when is it useful in practice? - would make it easier to handle timeouts... + +- on insert, return last inserted id + - postgresql: parse CommandComplete msg tag + - mysql: in ok-packet (what conditions, though?) + - sqlite3: sqlite3_last_insert_rowid(), use sqlite3_changes() to see if insert succeeded, + but still need to tell if stmt was even insert (parse sql?) + - odbc: ??? diff --git a/collects/db/base.rkt b/collects/db/base.rkt index 7fc39c6d71..fe626b3aa6 100644 --- a/collects/db/base.rkt +++ b/collects/db/base.rkt @@ -104,7 +104,9 @@ [query-exec (->* (connection? statement?) () #:rest list? any)] [query-rows - (->* (connection? statement?) () #:rest list? (listof vector?))] + (->* (connection? statement?) + (#:group (or/c (vectorof string?) (listof (vectorof string?)))) + #:rest list? (listof vector?))] [query-list (->* (connection? statement?) () #:rest list? list?)] [query-row diff --git a/collects/db/private/generic/functions.rkt b/collects/db/private/generic/functions.rkt index 554fb73fb9..54527bbe0e 100644 --- a/collects/db/private/generic/functions.rkt +++ b/collects/db/private/generic/functions.rkt @@ -142,7 +142,7 @@ (let* ([sql (compose-statement 'query-rows c sql args 'rows)] [result (query/rows c 'query-rows sql #f)] [result - (cond [(pair? group-fields-list) + (cond [(not (null? group-fields-list)) (group-rows-result* 'query-rows result group-fields-list (not (memq 'preserve-null-rows group-mode)) (memq 'list group-mode))] diff --git a/collects/db/private/sqlite3/connection.rkt b/collects/db/private/sqlite3/connection.rkt index 523d4336a5..3d9a4b5657 100644 --- a/collects/db/private/sqlite3/connection.rkt +++ b/collects/db/private/sqlite3/connection.rkt @@ -37,14 +37,13 @@ (define/override (connected?) (and -db #t)) (define/public (query fsym stmt) - (let-values ([(stmt* info rows) + (let-values ([(stmt* result) (call-with-lock fsym (lambda () (check-valid-tx-status fsym) (query1 fsym stmt)))]) (statement:after-exec stmt) - (cond [(pair? info) (rows-result info rows)] - [else (simple-result '())]))) + result)) (define/private (query1 fsym stmt) (let* ([stmt (cond [(string? stmt) @@ -64,12 +63,23 @@ (load-param fsym db stmt i param)) (let* ([info (for/list ([i (in-range (sqlite3_column_count stmt))]) - `((name ,(sqlite3_column_name stmt i)) - (decltype ,(sqlite3_column_decltype stmt i))))] + `((name . ,(sqlite3_column_name stmt i)) + (decltype . ,(sqlite3_column_decltype stmt i))))] [rows (step* fsym db stmt)]) (HANDLE fsym (sqlite3_reset stmt)) (HANDLE fsym (sqlite3_clear_bindings stmt)) - (values stmt info rows))))) + (values stmt + (cond [(pair? info) + (rows-result info rows)] + [else + (let ([changes (sqlite3_changes db)]) + (cond [(and (positive? changes) + #f ;; Note: currently disabled + #| FIXME: statement was INSERT stmt |#) + (simple-result + (list (cons 'last-insert-rowid + (sqlite3_last_insert_rowid db))))] + [else (simple-result '())]))])))))) (define/private (load-param fsym db stmt i param) (HANDLE fsym @@ -203,7 +213,7 @@ (let ([db (get-db fsym)]) (when (get-tx-status db) (error/already-in-tx fsym)) - (let-values ([(stmt* _info _rows) + (let-values ([(stmt* _result) (query1 fsym "BEGIN TRANSACTION")]) stmt*))))]) (statement:after-exec stmt) @@ -217,7 +227,7 @@ (unless (eq? mode 'rollback) (check-valid-tx-status fsym)) (when (get-tx-status db) - (let-values ([(stmt* _info _rows) + (let-values ([(stmt* _result) (case mode ((commit) (query1 fsym "COMMIT TRANSACTION")) @@ -235,14 +245,11 @@ ;; schema ignored, because sqlite doesn't support (string-append "SELECT tbl_name from sqlite_master " "WHERE type = 'table' or type = 'view'")]) - (let-values ([(stmt rows) + (let-values ([(stmt result) (call-with-lock fsym - (lambda () - (let-values ([(stmt _info rows) - (query1 fsym stmt)]) - (values stmt rows))))]) + (lambda () (query1 fsym stmt)))]) (statement:after-exec stmt) - (for/list ([row (in-list rows)]) + (for/list ([row (in-list (rows-result-rows result))]) (vector-ref row 0))))) ;; ---- diff --git a/collects/db/private/sqlite3/ffi.rkt b/collects/db/private/sqlite3/ffi.rkt index 94db428b6d..8fd7e8ce78 100644 --- a/collects/db/private/sqlite3/ffi.rkt +++ b/collects/db/private/sqlite3/ffi.rkt @@ -140,6 +140,14 @@ (_fun _sqlite3_statement -> _string)) +(define-sqlite sqlite3_changes + (_fun _sqlite3_database + -> _int)) + +(define-sqlite sqlite3_last_insert_rowid + (_fun _sqlite3_database + -> _int)) + ;; ---------------------------------------- #| diff --git a/collects/db/scribblings/db.scrbl b/collects/db/scribblings/db.scrbl index 99369d1199..8b8e76d8fa 100644 --- a/collects/db/scribblings/db.scrbl +++ b/collects/db/scribblings/db.scrbl @@ -26,17 +26,15 @@ native client library is required.} @item{@bold{@as-index{@hyperlink["http://www.sqlite.org"]{SQLite}} version 3.} The SQLite native client library is required; see -@secref["sqlite3-native-libs"].} +@secref["sqlite3-requirements"].} @item{@bold{@as-index{ODBC}.} An ODBC Driver Manager and appropriate -ODBC drivers are required; see @secref["odbc-native-libs"]. The -following additional database systems are known to work with this -library's ODBC support (see @secref["odbc-status"] for details): -@itemlist[ -@item{@bold{@as-index{@hyperlink["http://www.oracle.com"]{Oracle}}}} -@item{@bold{@as-index{@hyperlink["http://www.ibm.com/software/data/db2/"]{DB2}}}} -@item{@bold{@as-index{@hyperlink["http://www.microsoft.com/sqlserver/"]{SQL Server}}}} -]} +ODBC drivers are required; see @secref["odbc-requirements"]. The +following database systems are known to work with this library via +ODBC (see @secref["odbc-status"] for details): +@bold{@as-index{@hyperlink["http://www.ibm.com/software/data/db2/"]{DB2}}}, +@bold{@as-index{@hyperlink["http://www.oracle.com"]{Oracle}}}, and +@bold{@as-index{@hyperlink["http://www.microsoft.com/sqlserver/"]{SQL Server}}}.} ] The query operations are functional in spirit: queries return results diff --git a/collects/db/scribblings/notes.scrbl b/collects/db/scribblings/notes.scrbl index 55d51512bd..2a5860c060 100644 --- a/collects/db/scribblings/notes.scrbl +++ b/collects/db/scribblings/notes.scrbl @@ -4,25 +4,27 @@ scribble/struct racket/sandbox "config.rkt" - (for-label db)) + (for-label db + setup/dirs)) @title[#:tag "notes"]{Notes} -This section describes miscellaneous issues. +This section discusses issues related to specific database systems. + @section[#:tag "connecting-to-server"]{Local Sockets for PostgreSQL and MySQL Servers} PostgreSQL and MySQL servers are sometimes configured by default to listen only on local sockets (also called ``unix domain sockets''). This library provides support for communication over local -sockets, but only on Linux (x86 and x86-64) and Mac OS X. If local -socket communication is not available, the server must be reconfigured -to listen on a TCP port. +sockets on Linux (x86 and x86-64) and Mac OS X. If local socket +communication is not available, the server must be reconfigured to +listen on a TCP port. The socket file for a PostgreSQL server is located in the directory specified by the @tt{unix_socket_directory} variable in the @tt{postgresql.conf} server configuration file. For example, on -Ubuntu 10.10 running PostgreSQL 8.4, the socket directory is +Ubuntu 11.04 running PostgreSQL 8.4, the socket directory is @tt{/var/run/postgresql} and the socket file is @tt{/var/run/postgresql/.s.PGSQL.5432}. Common socket paths may be searched automatically using the @racket[postgresql-guess-socket-path] @@ -30,20 +32,19 @@ function. The socket file for a MySQL server is located at the path specified by the @tt{socket} variable in the @tt{my.cnf} configuration file. For -example, on Ubuntu 10.10 running MySQL 5.1, the socket is located at +example, on Ubuntu 11.04 running MySQL 5.1, the socket is located at @tt{/var/run/mysqld/mysqld.sock}. Common socket paths for MySQL can be searched using the @racket[mysql-guess-socket-path] function. -@section{Database Character Encodings} +@section{PostgreSQL Database Character Encoding} -In most cases, a PostgreSQL or MySQL database's character encoding is -irrelevant, since the connect function always requests translation to -Unicode (UTF-8) when creating a connection. If a PostgreSQL database's -character encoding is @tt{SQL_ASCII}, however, PostgreSQL will not -honor the connection encoding; it will instead send untranslated -octets, which will cause corrupt data or internal errors in the client -connection. +In most cases, a database's character encoding is irrelevant, since +the connect function always requests translation to Unicode (UTF-8) +when creating a connection. If a PostgreSQL database's character +encoding is @tt{SQL_ASCII}, however, PostgreSQL will not honor the +connection encoding; it will instead send untranslated octets, which +will cause corrupt data or internal errors in the client connection. To convert a PostgreSQL database from @tt{SQL_ASCII} to something sensible, @tt{pg_dump} the database, recode the dump file (using a @@ -51,31 +52,17 @@ utility such as @tt{iconv}), create a new database with the desired encoding, and @tt{pg_restore} from the recoded dump file. -@section{Prepared Query Parameter Types} - -Different database systems vary in their handling of query parameter -types. For example, consider the following parameterized SQL -statement: - -@tt{SELECT 1 + ?;} - -PostgreSQL reports an expected type of @tt{integer} for the parameter and -will not accept other types. MySQL and SQLite, in contrast, report no -useful parameter type information, and ODBC connections vary in -behavior based on the driver, the data source configuration, and the -connection parameters (see @secref["odbc-status"] for specific notes). - - @section{PostgreSQL Authentication} -PostgreSQL supports a large variety of authentication mechanisms, -controlled by the @tt{pg_hba.conf} server configuration file. This -library currently supports only cleartext and md5-hashed passwords, -and it does not send cleartext passwords unless explicitly ordered to -(see @racket[postgresql-connect]). These correspond to the @tt{md5} -and @tt{password} authentication methods in the parlance of +PostgreSQL supports a large variety of +@hyperlink["http://www.postgresql.org/docs/8.4/static/auth-pg-hba-conf.html"]{authentication +mechanisms}, controlled by the @tt{pg_hba.conf} server configuration +file. This library currently supports only cleartext and md5-hashed +passwords, and it does not send cleartext passwords unless explicitly +ordered to (see @racket[postgresql-connect]). These correspond to the +@tt{md5} and @tt{password} authentication methods in the parlance of @tt{pg_hba.conf}, respectively. On Linux, @tt{ident} authentication is -automatically supported for unix domain sockets (but not TCP). The +automatically supported for local sockets, but not TCP sockets. The @tt{gss}, @tt{sspi}, @tt{krb5}, @tt{pam}, and @tt{ldap} methods are not supported. @@ -89,36 +76,69 @@ plugins}. The only plugin currently supported by this library is password authentication mechanism used since version 4.1. -@section[#:tag "sqlite3-native-libs"]{SQLite Native Library} +@section[#:tag "sqlite3-requirements"]{SQLite Requirements} -SQLite support requires the appropriate native library, specifically -@tt{libsqlite3.so.0} on Unix or @tt{sqlite3.dll} on Windows. +SQLite support requires the appropriate native library. + +@itemlist[ + +@item{On Windows, the library is @tt{sqlite3.dll}. It can be obtained +from @hyperlink["http://www.sqlite.org/download.html"]{the SQLite +download page}; the DLL file should be extracted and placed into one +of the directories produced by +@racketblock[(begin (require setup/dirs) (get-lib-search-dirs))]} + +@item{On Mac OS X, the library is @tt{libsqlite3.0.dylib}, which is +included (in @tt{/usr/lib}) in Mac OS X version 10.4 onwards.} + +@item{On Linux, the library is @tt{libsqlite3.so.0}. It is included in +the @tt{libsqlite3-0} package in Debian/Ubuntu and in the @tt{sqlite} +package in Red Hat.} +] -@section[#:tag "odbc-native-libs"]{ODBC Native Libraries} +@section[#:tag "odbc-requirements"]{ODBC Requirements} -ODBC support requires the appropriate native library, specifically -@tt{libodbc.so.1} (from unixODBC; iODBC is not supported) on Unix or -@tt{odbc32.dll} on Windows. In addition, the appropriate ODBC Drivers -must be installed and any Data Sources configured. +ODBC requires the appropriate driver manager native library as well as +driver native libraries for each database system you want use ODBC to +connect to. + +@itemlist[ + +@item{On Windows, the driver manager is @tt{odbc32.dll}, which is +included automatically with Windows.} + +@item{On Mac OS X, the driver manager is @tt{libiodbc.2.dylib} +(@hyperlink["http://www.iodbc.org"]{iODBC}), which is included (in +@tt{/usr/lib}) in Mac OS X version 10.2 onwards.} + +@item{On Linux, the driver manager is @tt{libodbc.so.1} +(@hyperlink["http://www.unixodbc.org"]{unixODBC}---iODBC is not +supported). It is available from the @tt{unixodbc} package in +Debian/Ubuntu and in the @tt{unixODBC} package in Red Hat.} +] + +In addition, you must install the appropriate ODBC Drivers and +configure Data Sources. Refer to the ODBC documentation for the +specific database system for more information. -@section[#:tag "odbc-status"]{ODBC Support Status} +@section[#:tag "odbc-status"]{ODBC Status} -ODBC support is experimental. This library is compatible only with -ODBC 3.x Driver Managers. The behavior of ODBC connections can vary -widely depending on the driver in use and even the configuration of a -particular data source. +ODBC support is experimental. The behavior of ODBC connections can +vary widely depending on the driver in use and even the configuration +of a particular data source. The following sections describe the configurations that this library -has been tested with. The platform @bold{win32} means Windows Vista on -a 32-bit processor and @bold{linux} means Ubuntu 11.04 and unixODBC on -both x86 (32-bit) and x86-64 processors, unless otherwise -specified. The iODBC Driver Manager is not supported. +has been tested with. Reports of success or failure on other platforms or with other drivers would be appreciated. +@;{ +** There's no reason to actually use the following drivers. They're just +** useful for testing ODBC support. + @subsection{PostgreSQL ODBC Driver} The PostgreSQL ODBC driver version 09.00.0300 has been tested on @@ -149,15 +169,18 @@ Furthermore, this driver interprets the declared types of columns strictly, replacing nonconforming values in query results with @tt{NULL}. All computed columns, even those with explicit @tt{CAST}s, seem to be returned as @tt{text}. +} @subsection{DB2 ODBC Driver} -The driver from IBM DB2 Express-C v9.7 has been tested on @bold{linux} +The driver from IBM DB2 Express-C v9.7 has been tested on Ubuntu 11.04 (32-bit only). For a typical installation where the instance resides at @tt{/home/db2inst1}, set the following option in the Driver -configuration: @tt{Driver = /home/db2inst1/sqllib/lib32/libdb2.so}. +configuration: @tt{Driver = +/home/db2inst1/sqllib/lib32/libdb2.so}. (The path would presumably be +different for a 64-bit installation.) The DB2 driver does not seem to accept a separate argument for the database to connect to; it must be the same as the Data Source name. @@ -165,7 +188,7 @@ database to connect to; it must be the same as the Data Source name. @subsection{Oracle ODBC Driver} The driver from Oracle Database 10g Release 2 Express Edition has been -tested on @bold{linux} (32-bit only). +tested on Ubuntu 11.04 (32-bit only). It seems the @tt{ORACLE_HOME} and @tt{LD_LIBRARY_PATH} environment variables must be set according to the @tt{oracle_env.{csh,sh}} script @@ -185,5 +208,5 @@ Maybe Oracle bug? See: @subsection{SQL Server ODBC Driver} -Basic SQL Server support has been verified on @bold{win32}, but the -automated test suite has not yet been adapted and run. +Basic SQL Server support has been verified on Windows (32-bit only), +but the automated test suite has not yet been adapted and run.