-
Notifications
You must be signed in to change notification settings - Fork 111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
sqlite3_bind_text
calls with empty Span<byte>
result in null
values
#557
Comments
The root cause appears similar to that described and handled for SQLitePCL.raw/src/providers/provider.tt Lines 1586 to 1595 in 9c66b4f
This was confirmed by testing the SQLite C API behavior with the program below. C API test#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include "sqlite3.h"
void verify_text_inserted(sqlite3 *db, const char* context);
int main(int argc, char *argv[])
{
printf("SQLite version: %s\n", sqlite3_libversion());
sqlite3 *db;
int rc = sqlite3_open_v2(":memory:", &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_MEMORY, NULL);
assert(rc == SQLITE_OK);
char *errmsg;
rc = sqlite3_exec(db, "create table tab(fld text);", NULL, NULL, &errmsg);
assert(rc == SQLITE_OK && errmsg == NULL);
// insert literal empty string
rc = sqlite3_exec(db, "insert into tab(fld) values('');", NULL, NULL, &errmsg);
assert(rc == SQLITE_OK && errmsg == NULL);
verify_text_inserted(db, "Literal empty string");
// clean up
rc = sqlite3_exec(db, "delete from tab;", NULL, NULL, &errmsg);
assert(rc == SQLITE_OK && errmsg == NULL);
// insert bound empty string param
char *insert_params = "insert into tab(fld) values (@v);";
sqlite3_stmt *query;
rc = sqlite3_prepare_v2(db, insert_params, strlen(insert_params), &query, NULL);
assert(rc == SQLITE_OK);
rc = sqlite3_bind_text(query, 1, "", 0, NULL); // <-- NOTE: zero-length explicitly passed
assert(rc == SQLITE_OK);
rc = sqlite3_step(query);
assert(rc == SQLITE_DONE);
sqlite3_finalize(query);
verify_text_inserted(db, "Bound empty string");
sqlite3_close(db);
}
void verify_text_inserted(sqlite3 *db, const char *context)
{
char *sql = "select fld from tab limit 1;";
sqlite3_stmt *query;
int rc = sqlite3_prepare_v2(db, sql, strlen(sql), &query, NULL);
assert(rc == SQLITE_OK);
rc = sqlite3_step(query);
assert(rc == SQLITE_ROW);
int sql_type = sqlite3_column_type(query, 0);
if (sql_type != SQLITE_TEXT)
printf("Expected to find SQLITE_TEXT (%d) but got %d", SQLITE_TEXT, sql_type);
assert(sql_type == SQLITE_TEXT);
int byte_count = sqlite3_column_bytes(query, 0);
assert(byte_count == 0);
const void *ptr = sqlite3_column_blob(query, 0);
printf("%s: got sql_type=%d, byte_count=%d, ptr=%p\n", context, sql_type, byte_count, ptr);
sqlite3_finalize(query);
} Running this produces expected results:
However, if line 40 is changed to pass in a - rc = sqlite3_bind_text(query, 1, "", 0, NULL);
+ rc = sqlite3_bind_text(query, 1, NULL, 0, NULL); Then the behavior changes and SQLITE_NULL (
This suggests that the issue reported here is caused by:
Pull request #558 suggests a potential fix for this corner case in the |
Hmmm. Thanks for the report. The behavior you've described does seem incorrect. The code has been this way for years, so I wonder why the problem has not been noticed earlier. I need to look a bit more closely before I make the code change. |
The issue popped up on our radar when inserting UTF-8 byte spans (empty strings) into SQLite columns declared I can only guess that other (more common?) use cases might bind C# (UTF-16) strings or I understand that a detailed review is needed; happy to help in any way I can to see the fix proposed in the PR (or something similar to it) go through. |
… but leaving the explicit checks for Length == 0
This was included in 2.1.7, which has been pushed up to nuget. |
The method
sqlite3_bind_text(sqlite3_stmt stmt, int index, ReadOnlySpan<byte> val)
, called withReadOnlySpan.Empty
when inserting into atext
column, results innull
values, instead of empty strings.With reference to the SQLite docs, namely:
The current behaviour seems surprising and unexpected. Passing an empty span with non-negative length, it seems reasonable to expect the same result as having a literal empty string (
''
) in the SQL text, i.e. an empty string to be bound, and notnull
.A repro program is provided below. It inserts an empty string, once using a SQL literal string, and again calling
sqlite3_bind_text
with an empty span.The literal value is stored and can be retrieved as expected. However, the bound empty span results in
null
being stored.SQLitePCLRaw.bundle_green
2.1.6Reproduces on MacOS Sonoma (14.0) and Windows 11 (22H2, 10.0.22621.2283), both ARM64.
.NET (Core) 8.0.100-rc.1.23455.8
Reproduces from the command line, as well as IDE (Rider), thus I assume IDE version may not be relevant.
PackageReference
None.
In a .NET console app project, add a package reference to
SQLitePCLRaw.bundle_green
2.1.6, e.g.:Replace
Program.cs
with the code below.An assertion is triggered when SQLITE_NULL (
5
) is unexpectedly found after insert:The text was updated successfully, but these errors were encountered: