This is an automated email from the ASF dual-hosted git repository.

lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git


The following commit(s) were added to refs/heads/main by this push:
     new 1a07f244 fix(c/driver/sqlite): fix escaping of sqlite TABLE CREATE 
columns (#906)
1a07f244 is described below

commit 1a07f244b6ef4a65067550f36e51142b937be37b
Author: William Ayd <[email protected]>
AuthorDate: Mon Jul 17 10:35:03 2023 -0700

    fix(c/driver/sqlite): fix escaping of sqlite TABLE CREATE columns (#906)
    
    Should maybe use sqlite3's string handling throughout the module; for
    now just focused on CREATE TABLE
    
    fixes
    https://github.com/apache/arrow-adbc/issues/905#issuecomment-1636418672
---
 c/driver/flightsql/dremio_flightsql_test.cc |  2 ++
 c/driver/flightsql/sqlite_flightsql_test.cc |  2 ++
 c/driver/sqlite/sqlite.c                    | 52 +++++++++++++++++------------
 c/integration/duckdb/duckdb_test.cc         |  2 ++
 c/validation/adbc_validation.cc             | 25 ++++++++++++++
 c/validation/adbc_validation.h              |  2 ++
 6 files changed, 64 insertions(+), 21 deletions(-)

diff --git a/c/driver/flightsql/dremio_flightsql_test.cc 
b/c/driver/flightsql/dremio_flightsql_test.cc
index f4e0642c..c128bd49 100644
--- a/c/driver/flightsql/dremio_flightsql_test.cc
+++ b/c/driver/flightsql/dremio_flightsql_test.cc
@@ -87,6 +87,8 @@ class DremioFlightSqlStatementTest : public ::testing::Test,
   void SetUp() override { ASSERT_NO_FATAL_FAILURE(SetUpTest()); }
   void TearDown() override { ASSERT_NO_FATAL_FAILURE(TearDownTest()); }
 
+  void TestSqlIngestTableEscaping() { GTEST_SKIP() << "Table escaping not 
implemented"; }
+
  protected:
   DremioFlightSqlQuirks quirks_;
 };
diff --git a/c/driver/flightsql/sqlite_flightsql_test.cc 
b/c/driver/flightsql/sqlite_flightsql_test.cc
index 96d448a0..fdb2a795 100644
--- a/c/driver/flightsql/sqlite_flightsql_test.cc
+++ b/c/driver/flightsql/sqlite_flightsql_test.cc
@@ -228,6 +228,8 @@ class SqliteFlightSqlStatementTest : public ::testing::Test,
   void SetUp() override { ASSERT_NO_FATAL_FAILURE(SetUpTest()); }
   void TearDown() override { ASSERT_NO_FATAL_FAILURE(TearDownTest()); }
 
+  void TestSqlIngestTableEscaping() { GTEST_SKIP() << "Table escaping not 
implemented"; }
+
  protected:
   SqliteFlightSqlQuirks quirks_;
 };
diff --git a/c/driver/sqlite/sqlite.c b/c/driver/sqlite/sqlite.c
index 4124098f..6d0e38ea 100644
--- a/c/driver/sqlite/sqlite.c
+++ b/c/driver/sqlite/sqlite.c
@@ -981,23 +981,22 @@ AdbcStatusCode SqliteStatementInitIngest(struct 
SqliteStatement* stmt,
   AdbcStatusCode code = ADBC_STATUS_OK;
 
   // Create statements for CREATE TABLE / INSERT
-  struct StringBuilder create_query = {0};
-  struct StringBuilder insert_query = {0};
-
-  if (StringBuilderInit(&create_query, /*initial_size=*/256) != 0) {
-    SetError(error, "[SQLite] Could not initiate StringBuilder");
+  sqlite3_str* create_query = sqlite3_str_new(NULL);
+  if (sqlite3_str_errcode(create_query)) {
+    SetError(error, "[SQLite] %s", sqlite3_errmsg(stmt->conn));
     return ADBC_STATUS_INTERNAL;
   }
+  struct StringBuilder insert_query = {0};
 
   if (StringBuilderInit(&insert_query, /*initial_size=*/256) != 0) {
     SetError(error, "[SQLite] Could not initiate StringBuilder");
-    StringBuilderReset(&create_query);
+    sqlite3_free(sqlite3_str_finish(create_query));
     return ADBC_STATUS_INTERNAL;
   }
 
-  if (StringBuilderAppend(&create_query, "%s%s%s", "CREATE TABLE ", 
stmt->target_table,
-                          " (") != 0) {
-    SetError(error, "[SQLite] Call to StringBuilderAppend failed");
+  sqlite3_str_appendf(create_query, "%s%Q%s", "CREATE TABLE ", 
stmt->target_table, " (");
+  if (sqlite3_str_errcode(create_query)) {
+    SetError(error, "[SQLite] %s", sqlite3_errmsg(stmt->conn));
     code = ADBC_STATUS_INTERNAL;
     goto cleanup;
   }
@@ -1010,11 +1009,18 @@ AdbcStatusCode SqliteStatementInitIngest(struct 
SqliteStatement* stmt,
   }
 
   for (int i = 0; i < stmt->binder.schema.n_children; i++) {
-    if (i > 0) StringBuilderAppend(&create_query, "%s", ", ");
-    // XXX: should escape the column name too
-    if (StringBuilderAppend(&create_query, "%s", 
stmt->binder.schema.children[i]->name) !=
-        0) {
-      SetError(error, "[SQLite] Call to StringBuilderAppend failed");
+    if (i > 0) {
+      sqlite3_str_appendf(create_query, "%s", ", ");
+      if (sqlite3_str_errcode(create_query)) {
+        SetError(error, "[SQLite] %s", sqlite3_errmsg(stmt->conn));
+        code = ADBC_STATUS_INTERNAL;
+        goto cleanup;
+      }
+    }
+
+    sqlite3_str_appendf(create_query, "%Q", 
stmt->binder.schema.children[i]->name);
+    if (sqlite3_str_errcode(create_query)) {
+      SetError(error, "[SQLite] %s", sqlite3_errmsg(stmt->conn));
       code = ADBC_STATUS_INTERNAL;
       goto cleanup;
     }
@@ -1033,8 +1039,10 @@ AdbcStatusCode SqliteStatementInitIngest(struct 
SqliteStatement* stmt,
       goto cleanup;
     }
   }
-  if (StringBuilderAppend(&create_query, "%s", ")") != 0) {
-    SetError(error, "[SQLite] Call to StringBuilderAppend failed");
+
+  sqlite3_str_appendchar(create_query, 1, ')');
+  if (sqlite3_str_errcode(create_query)) {
+    SetError(error, "[SQLite] %s", sqlite3_errmsg(stmt->conn));
     code = ADBC_STATUS_INTERNAL;
     goto cleanup;
   }
@@ -1048,15 +1056,17 @@ AdbcStatusCode SqliteStatementInitIngest(struct 
SqliteStatement* stmt,
   sqlite3_stmt* create = NULL;
   if (!stmt->append) {
     // Create table
-    int rc = sqlite3_prepare_v2(stmt->conn, create_query.buffer, 
(int)create_query.size,
-                                &create, /*pzTail=*/NULL);
+    int rc =
+        sqlite3_prepare_v2(stmt->conn, sqlite3_str_value(create_query),
+                           sqlite3_str_length(create_query), &create, 
/*pzTail=*/NULL);
     if (rc == SQLITE_OK) {
       rc = sqlite3_step(create);
     }
 
     if (rc != SQLITE_OK && rc != SQLITE_DONE) {
-      SetError(error, "[SQLite] Failed to create table: %s (executed '%s')",
-               sqlite3_errmsg(stmt->conn), create_query.buffer);
+      SetError(error, "[SQLite] Failed to create table: %s (executed '%.*s')",
+               sqlite3_errmsg(stmt->conn), sqlite3_str_length(create_query),
+               sqlite3_str_value(create_query));
       code = ADBC_STATUS_INTERNAL;
     }
   }
@@ -1074,7 +1084,7 @@ AdbcStatusCode SqliteStatementInitIngest(struct 
SqliteStatement* stmt,
   sqlite3_finalize(create);
 
 cleanup:
-  StringBuilderReset(&create_query);
+  sqlite3_free(sqlite3_str_finish(create_query));
   StringBuilderReset(&insert_query);
   return code;
 }
diff --git a/c/integration/duckdb/duckdb_test.cc 
b/c/integration/duckdb/duckdb_test.cc
index b2a5c524..fd6e1984 100644
--- a/c/integration/duckdb/duckdb_test.cc
+++ b/c/integration/duckdb/duckdb_test.cc
@@ -94,6 +94,8 @@ class DuckDbStatementTest : public ::testing::Test,
   // Accepts Prepare() without any query
   void TestSqlPrepareErrorNoQuery() { GTEST_SKIP(); }
 
+  void TestSqlIngestTableEscaping() { GTEST_SKIP() << "Table escaping not 
implemented"; }
+
  protected:
   DuckDbQuirks quirks_;
 };
diff --git a/c/validation/adbc_validation.cc b/c/validation/adbc_validation.cc
index bba51588..208b3f3b 100644
--- a/c/validation/adbc_validation.cc
+++ b/c/validation/adbc_validation.cc
@@ -1193,6 +1193,31 @@ void StatementTest::TestSqlIngestTimestampTz() {
       
TestSqlIngestTemporalType<NANOARROW_TIME_UNIT_NANO>("America/Los_Angeles"));
 }
 
+void StatementTest::TestSqlIngestTableEscaping() {
+  std::string name = "create_table_escaping";
+
+  ASSERT_THAT(quirks()->DropTable(&connection, name, &error),
+              adbc_validation::IsOkStatus(&error));
+  Handle<struct ArrowSchema> schema;
+  Handle<struct ArrowArray> array;
+  struct ArrowError na_error;
+  ASSERT_THAT(MakeSchema(&schema.value, {{"index", NANOARROW_TYPE_INT64}}), 
IsOkErrno());
+  ASSERT_THAT((MakeBatch<int64_t>(&schema.value, &array.value, &na_error,
+                                  {42, -42, std::nullopt})),
+              IsOkErrno());
+
+  Handle<struct AdbcStatement> statement;
+  ASSERT_THAT(AdbcStatementNew(&connection, &statement.value, &error), 
IsOkErrno());
+  ASSERT_THAT(AdbcStatementSetOption(&statement.value, 
ADBC_INGEST_OPTION_TARGET_TABLE,
+                                     name.c_str(), &error),
+              IsOkErrno());
+  ASSERT_THAT(AdbcStatementBind(&statement.value, &array.value, &schema.value, 
&error),
+              IsOkErrno());
+  ASSERT_THAT(AdbcStatementExecuteQuery(&statement.value, nullptr, nullptr, 
&error),
+              IsOkErrno());
+  ASSERT_THAT(AdbcStatementRelease(&statement.value, &error), IsOkErrno());
+}
+
 void StatementTest::TestSqlIngestAppend() {
   if (!quirks()->supports_bulk_ingest()) {
     GTEST_SKIP();
diff --git a/c/validation/adbc_validation.h b/c/validation/adbc_validation.h
index aeafa9c3..61208d13 100644
--- a/c/validation/adbc_validation.h
+++ b/c/validation/adbc_validation.h
@@ -235,6 +235,7 @@ class StatementTest {
 
   // ---- End Type-specific tests ----------------
 
+  void TestSqlIngestTableEscaping();
   void TestSqlIngestAppend();
   void TestSqlIngestErrors();
   void TestSqlIngestMultipleConnections();
@@ -301,6 +302,7 @@ class StatementTest {
   TEST_F(FIXTURE, SqlIngestBinary) { TestSqlIngestBinary(); }                  
         \
   TEST_F(FIXTURE, SqlIngestTimestamp) { TestSqlIngestTimestamp(); }            
         \
   TEST_F(FIXTURE, SqlIngestTimestampTz) { TestSqlIngestTimestampTz(); }        
         \
+  TEST_F(FIXTURE, SqlIngestTableEscaping) { TestSqlIngestTableEscaping(); }    
         \
   TEST_F(FIXTURE, SqlIngestAppend) { TestSqlIngestAppend(); }                  
         \
   TEST_F(FIXTURE, SqlIngestErrors) { TestSqlIngestErrors(); }                  
         \
   TEST_F(FIXTURE, SqlIngestMultipleConnections) { 
TestSqlIngestMultipleConnections(); } \

Reply via email to