Support "insert into ... returning ..." in sqlite-execute

* doc/lispref/text.texi (Database): Mention it.
* src/sqlite.c (Fsqlite_execute): Support syntax like "insert into
... returning ..." (bug#58390).
This commit is contained in:
Lars Ingebrigtsen 2022-10-10 10:58:33 +02:00
parent 7ab6ec364d
commit 7e7dc74ffb
2 changed files with 81 additions and 70 deletions

View file

@ -5321,9 +5321,12 @@ This has exactly the same effect as the previous example, but is more
efficient and safer (because it doesn't involve any string parsing or efficient and safer (because it doesn't involve any string parsing or
interpolation). interpolation).
@code{sqlite-execute} returns the number of affected rows. For @code{sqlite-execute} usually returns the number of affected rows.
instance, an @samp{insert} statement will return @samp{1}, whereas an For instance, an @samp{insert} statement will typically return
@samp{update} statement may return zero or a higher number. @samp{1}, whereas an @samp{update} statement may return zero or a
higher number. However, when using @acronym{SQL} statements like
@samp{insert into ... returning ...} and the like, the values
specified by @samp{returning ...} will be returned instead.
Strings in SQLite are, by default, stored as @code{utf-8}, and Strings in SQLite are, by default, stored as @code{utf-8}, and
selecting a text column will decode the string using that charset. selecting a text column will decode the string using that charset.

View file

@ -377,73 +377,6 @@ bind_values (sqlite3 *db, sqlite3_stmt *stmt, Lisp_Object values)
return NULL; return NULL;
} }
DEFUN ("sqlite-execute", Fsqlite_execute, Ssqlite_execute, 2, 3, 0,
doc: /* Execute a non-select SQL statement.
If VALUES is non-nil, it should be a vector or a list of values
to bind when executing a statement like
insert into foo values (?, ?, ...)
Value is the number of affected rows. */)
(Lisp_Object db, Lisp_Object query, Lisp_Object values)
{
check_sqlite (db, false);
CHECK_STRING (query);
if (!(NILP (values) || CONSP (values) || VECTORP (values)))
xsignal1 (Qerror, build_string ("VALUES must be a list or a vector"));
sqlite3 *sdb = XSQLITE (db)->db;
Lisp_Object retval = Qnil;
const char *errmsg = NULL;
Lisp_Object encoded = encode_string (query);
sqlite3_stmt *stmt = NULL;
/* We only execute the first statement -- if there's several
(separated by a semicolon), the subsequent statements won't be
done. */
int ret = sqlite3_prepare_v2 (sdb, SSDATA (encoded), -1, &stmt, NULL);
if (ret != SQLITE_OK)
{
if (stmt != NULL)
{
sqlite3_finalize (stmt);
sqlite3_reset (stmt);
}
errmsg = sqlite3_errmsg (sdb);
goto exit;
}
/* Bind ? values. */
if (!NILP (values))
{
const char *err = bind_values (sdb, stmt, values);
if (err != NULL)
{
errmsg = err;
goto exit;
}
}
ret = sqlite3_step (stmt);
sqlite3_finalize (stmt);
if (ret != SQLITE_OK && ret != SQLITE_DONE)
{
errmsg = sqlite3_errmsg (sdb);
goto exit;
}
retval = make_fixnum (sqlite3_changes (sdb));
exit:
if (errmsg != NULL)
xsignal1 (ret == SQLITE_LOCKED || ret == SQLITE_BUSY?
Qsqlite_locked_error: Qerror,
build_string (errmsg));
return retval;
}
static Lisp_Object static Lisp_Object
row_to_value (sqlite3_stmt *stmt) row_to_value (sqlite3_stmt *stmt)
{ {
@ -488,6 +421,81 @@ row_to_value (sqlite3_stmt *stmt)
return Fnreverse (values); return Fnreverse (values);
} }
DEFUN ("sqlite-execute", Fsqlite_execute, Ssqlite_execute, 2, 3, 0,
doc: /* Execute a non-select SQL statement.
If VALUES is non-nil, it should be a vector or a list of values
to bind when executing a statement like
insert into foo values (?, ?, ...)
Value is the number of affected rows. */)
(Lisp_Object db, Lisp_Object query, Lisp_Object values)
{
check_sqlite (db, false);
CHECK_STRING (query);
if (!(NILP (values) || CONSP (values) || VECTORP (values)))
xsignal1 (Qerror, build_string ("VALUES must be a list or a vector"));
sqlite3 *sdb = XSQLITE (db)->db;
const char *errmsg = NULL;
Lisp_Object encoded = encode_string (query);
sqlite3_stmt *stmt = NULL;
/* We only execute the first statement -- if there's several
(separated by a semicolon), the subsequent statements won't be
done. */
int ret = sqlite3_prepare_v2 (sdb, SSDATA (encoded), -1, &stmt, NULL);
if (ret != SQLITE_OK)
{
if (stmt != NULL)
{
sqlite3_finalize (stmt);
sqlite3_reset (stmt);
}
errmsg = sqlite3_errmsg (sdb);
goto exit;
}
/* Bind ? values. */
if (!NILP (values))
{
const char *err = bind_values (sdb, stmt, values);
if (err != NULL)
{
errmsg = err;
goto exit;
}
}
ret = sqlite3_step (stmt);
if (ret == SQLITE_ROW)
{
Lisp_Object data = Qnil;
do
data = Fcons (row_to_value (stmt), data);
while (sqlite3_step (stmt) == SQLITE_ROW);
sqlite3_finalize (stmt);
return Fnreverse (data);
}
else if (ret == SQLITE_OK || ret == SQLITE_DONE)
{
Lisp_Object rows = make_fixnum (sqlite3_changes (sdb));
sqlite3_finalize (stmt);
return rows;
}
else
errmsg = sqlite3_errmsg (sdb);
exit:
sqlite3_finalize (stmt);
xsignal1 (ret == SQLITE_LOCKED || ret == SQLITE_BUSY?
Qsqlite_locked_error: Qerror,
build_string (errmsg));
}
static Lisp_Object static Lisp_Object
column_names (sqlite3_stmt *stmt) column_names (sqlite3_stmt *stmt)
{ {