diff --git a/collects/db/private/mysql/connection.rkt b/collects/db/private/mysql/connection.rkt index 172ef2c887..fd832ee91d 100644 --- a/collects/db/private/mysql/connection.rkt +++ b/collects/db/private/mysql/connection.rkt @@ -362,7 +362,7 @@ [(struct ok-packet (affected-rows insert-id status warnings message)) (when wbox (set-box! wbox warnings)) (vector 'command `((affected-rows . ,affected-rows) - (insert-id . ,insert-id) + (insert-id . ,(if (zero? insert-id) #f insert-id)) (status . ,status) (message . ,message)))] [(struct result-set-header-packet (fields extra)) diff --git a/collects/db/private/postgresql/connection.rkt b/collects/db/private/postgresql/connection.rkt index 93bd98e7af..030c9c9008 100644 --- a/collects/db/private/postgresql/connection.rkt +++ b/collects/db/private/postgresql/connection.rkt @@ -379,7 +379,7 @@ (define/private (query1:expect-completion fsym) (match (recv-message fsym) - [(struct CommandComplete (command)) `((command . ,command))] + [(struct CommandComplete (command)) command] [(struct EmptyQueryResponse ()) '()] [other-r (query1:error fsym other-r)])) diff --git a/collects/db/private/postgresql/message.rkt b/collects/db/private/postgresql/message.rkt index 8cdaddb850..3fbc4e165b 100644 --- a/collects/db/private/postgresql/message.rkt +++ b/collects/db/private/postgresql/message.rkt @@ -267,7 +267,7 @@ (define (parse:CommandComplete p) (with-length-in p #\C (let* ([command (io:read-null-terminated-string p)]) - (make-CommandComplete command)))) + (make-CommandComplete (string->command-alist command))))) (define-struct CopyInResponse (format column-formats) #:transparent) (define (parse:CopyInResponse p) @@ -527,45 +527,25 @@ [(transaction) #\T] [(failed) #\E])) -(define (string->command s) - (cond [(regexp-match #rx"^SELECT *$" s) - => (lambda (m) (list 'select))] - [(regexp-match #rx"^INSERT ([0-9]*) ([0-9]*) *$" s) +(define (string->command-alist s) + (cond [(regexp-match #rx"^INSERT ([0-9]*) ([0-9]*) *$" s) => (lambda (m) - (list 'insert - (string->number (cadr m)) - (string->number (caddr m))))] - [(regexp-match #rx"^DELETE ([0-9]* *$)" s) + `((insert-id . ,(let ([oid (string->number (cadr m))]) + (if (zero? oid) #f oid))) + (affected-rows . ,(string->number (caddr m)))))] + [(regexp-match #rx"^DELETE ([0-9]*) *$" s) => (lambda (m) - (list 'delete (string->number (cadr m))))] + `((affected-rows . ,(string->number (cadr m)))))] [(regexp-match #rx"^UPDATE ([0-9]*) *$" s) => (lambda (m) - (list 'update (string->number (cadr m))))] - [(regexp-match #rx"^MOVE ([0-9]*) *$" s) - => (lambda (m) - (list 'move (string->number (cadr m))))] - [(regexp-match #rx"^FETCH ([0-9]*) *$" s) - => (lambda (m) - (list 'fetch (string->number (cadr m))))] - [(regexp-match #rx"^(CREATE|ALTER|DROP) ([A-Z]*) *$" s) - => (lambda (m) - (list (string->symbol (string-downcase (cadr m))) - (string->symbol (string-downcase (caddr m)))))] - [else s])) - -(define (command->string s) - (if (list? s) - (apply string-append - (case (car s) - [(insert) "INSERT"] - [(delete) "DELETE"] - [(update) "UPDATE"] - [(move) "MOVE"] - [(fetch) "FETCH"] - [else s]) - (map (lambda (n) (format " ~a" n)) - (cdr s))) - s)) + `((affected-rows . ,(string->number (cadr m)))))] + #| + [(regexp-match #rx"^SELECT *$" s) ...] + [(regexp-match #rx"^MOVE ([0-9]*) *$" s) ...] + [(regexp-match #rx"^FETCH ([0-9]*) *$" s) ...] + [(regexp-match #rx"^(CREATE|ALTER|DROP) ([A-Z]*) *$" s) ...] + |# + [else '()])) ;; dvec layout is #(name table-oid col-oid typeid typelen typemod text/binary) diff --git a/collects/db/private/sqlite3/connection.rkt b/collects/db/private/sqlite3/connection.rkt index 09e12a6ce2..69face42e9 100644 --- a/collects/db/private/sqlite3/connection.rkt +++ b/collects/db/private/sqlite3/connection.rkt @@ -67,13 +67,17 @@ (for ([i (in-naturals 1)] [param (in-list params)]) (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))))] - [result - (or cursor? - (step* fsym db stmt #f +inf.0))]) + (let* ([info + (for/list ([i (in-range (sqlite3_column_count stmt))]) + `((name . ,(sqlite3_column_name stmt i)) + (decltype . ,(sqlite3_column_decltype stmt i))))] + [saved-last-insert-rowid + (and (null? info) (sqlite3_last_insert_rowid db))] + [saved-total-changes + (and (null? info) (sqlite3_total_changes db))] + [result + (or cursor? + (step* fsym db stmt #f +inf.0))]) (unless (eq? (get-tx-status) 'invalid) (set-tx-status! fsym (read-tx-status))) (unless cursor? @@ -83,7 +87,18 @@ [(and (pair? info) cursor?) (cursor-result info pst (box #f))] [else - (simple-result '())]))))) + (simple-result + (let ([last-insert-rowid (sqlite3_last_insert_rowid db)] + [total-changes (sqlite3_total_changes db)]) + ;; Not all statements clear last_insert_rowid, changes; so + ;; extra guards to make sure results are relevant. + `((insert-id + . ,(and (not (= last-insert-rowid saved-last-insert-rowid)) + last-insert-rowid)) + (affected-rows + . ,(if (> total-changes saved-total-changes) + (sqlite3_changes db) + 0)))))]))))) (define/public (fetch/cursor fsym cursor fetch-size) (let ([pst (cursor-result-pst cursor)] diff --git a/collects/db/private/sqlite3/ffi.rkt b/collects/db/private/sqlite3/ffi.rkt index 8f83244970..a185d3ac27 100644 --- a/collects/db/private/sqlite3/ffi.rkt +++ b/collects/db/private/sqlite3/ffi.rkt @@ -182,10 +182,14 @@ (_fun _sqlite3_database -> _int)) -(define-sqlite sqlite3_last_insert_rowid +(define-sqlite sqlite3_total_changes (_fun _sqlite3_database -> _int)) +(define-sqlite sqlite3_last_insert_rowid + (_fun _sqlite3_database + -> _int64)) + ;; ---------------------------------------- #| diff --git a/collects/db/scribblings/query.scrbl b/collects/db/scribblings/query.scrbl index c5de4f97c5..60e4098e62 100644 --- a/collects/db/scribblings/query.scrbl +++ b/collects/db/scribblings/query.scrbl @@ -297,14 +297,39 @@ A general query result is either a @racket[simple-result] or a @racket[rows-result]. @defstruct*[simple-result - ([info any/c])]{ + ([info (listof (cons/c symbol? any/c))])]{ Represents the result of a SQL statement that does not return a relation, such as an @tt{INSERT} or @tt{DELETE} statement. -The @racket[info] field is usually an association list, but do not -rely on its contents; it varies based on database system and may -change in future versions of this library (even new minor versions). +The @racket[info] field is an association list, but its contents vary +based on database system and may change in future versions of this +library (even new minor versions). The following keys are supported for +multiple database systems: + +@itemlist[ + +@item{@racket['insert-id]: If the value is a positive integer, the +statement was an @tt{INSERT} statement and the value is a +system-specific identifier for the inserted row. For PostgreSQL, the +value is the row's OID, if the table has OIDs (for an alternative, see +the @tt{INSERT ... RETURNING} statement). For MySQL, the value is the +same as the result of +@hyperlink["http://dev.mysql.com/doc/refman/5.0/en/information-functions.html#function_last-insert-id"]{last_insert_id} +function---that is, the value of the row's @tt{AUTO_INCREMENT} +field. If there is no such field, the value is @racket[#f]. For +SQLite, the value is the same as the result of the +@hyperlink["http://www.sqlite.org/lang_corefunc.html#last_insert_rowid"]{last_insert_rowid} +function---that is, the +@hyperlink["http://www.sqlite.org/lang_createtable.html#rowid"]{ROWID} +of the inserted row.} + +@item{@racket['affected-rows]: The number (a nonnegative integer) of +rows inserted by an @tt{INSERT} statement, modified by an @tt{UPDATE} +statement, or deleted by a @tt{DELETE} statement. Only directly +affected rows are counted; rows affected because of triggers or +integrity constraint actions are not counted.} +] } @defstruct*[rows-result