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 e80b21f  refactor(c/driver/common): Variadic arguments for 
StringBuilderAppend (#587)
e80b21f is described below

commit e80b21fab2ce8078c43a8e4d5ae73ea448e640ee
Author: William Ayd <[email protected]>
AuthorDate: Thu Apr 27 19:37:01 2023 -0700

    refactor(c/driver/common): Variadic arguments for StringBuilderAppend (#587)
    
    Cuts down on the verbosity of string appends and bridges the functional
    gap between this and the templated C++ version a bit.
    
    In follow ups would be nice to support non-string arguments, clear up
    the error handling, and make the Append function non-void with a return
    value
---
 c/driver/common/CMakeLists.txt | 15 ++++++++
 c/driver/common/utils.c        | 58 +++++++++++++++++++----------
 c/driver/common/utils.h        | 19 +++++++++-
 c/driver/common/utils_test.cc  | 74 +++++++++++++++++++++++++++++++++++++
 c/driver/sqlite/sqlite.c       | 83 +++++++++++++++++++++++++++++++++---------
 5 files changed, 210 insertions(+), 39 deletions(-)

diff --git a/c/driver/common/CMakeLists.txt b/c/driver/common/CMakeLists.txt
index 6a9cd54..5eaa307 100644
--- a/c/driver/common/CMakeLists.txt
+++ b/c/driver/common/CMakeLists.txt
@@ -19,3 +19,18 @@ add_library(adbc_driver_common STATIC utils.c)
 set_target_properties(adbc_driver_common PROPERTIES POSITION_INDEPENDENT_CODE 
ON)
 include_directories(SYSTEM ${REPOSITORY_ROOT})
 include_directories(SYSTEM ${REPOSITORY_ROOT}/c/vendor)
+
+if(ADBC_BUILD_TESTS)
+  add_test_case(driver_common_test
+                PREFIX
+                adbc
+                SOURCES
+                utils_test.cc
+                EXTRA_LINK_LIBS
+                adbc_driver_common
+                nanoarrow)
+  target_compile_features(adbc-driver-common-test PRIVATE cxx_std_17)
+  adbc_configure_target(adbc-driver-common-test)
+endif()
+
+validate_config()
diff --git a/c/driver/common/utils.c b/c/driver/common/utils.c
index 1e6d814..8ea35c5 100644
--- a/c/driver/common/utils.c
+++ b/c/driver/common/utils.c
@@ -23,12 +23,13 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <assert.h>
 #include <nanoarrow/nanoarrow.h>
 
 static size_t kErrorBufferSize = 256;
 static char kErrorPrefix[] = "[SQLite] ";
 
-void ReleaseError(struct AdbcError* error) {
+static void ReleaseError(struct AdbcError* error) {
   free(error->message);
   error->message = NULL;
   error->release = NULL;
@@ -58,11 +59,11 @@ struct SingleBatchArrayStream {
   struct ArrowSchema schema;
   struct ArrowArray batch;
 };
-const char* SingleBatchArrayStreamGetLastError(struct ArrowArrayStream* 
stream) {
+static const char* SingleBatchArrayStreamGetLastError(struct ArrowArrayStream* 
stream) {
   return NULL;
 }
-int SingleBatchArrayStreamGetNext(struct ArrowArrayStream* stream,
-                                  struct ArrowArray* batch) {
+static int SingleBatchArrayStreamGetNext(struct ArrowArrayStream* stream,
+                                         struct ArrowArray* batch) {
   if (!stream || !stream->private_data) return EINVAL;
   struct SingleBatchArrayStream* impl =
       (struct SingleBatchArrayStream*)stream->private_data;
@@ -71,15 +72,15 @@ int SingleBatchArrayStreamGetNext(struct ArrowArrayStream* 
stream,
   memset(&impl->batch, 0, sizeof(*batch));
   return 0;
 }
-int SingleBatchArrayStreamGetSchema(struct ArrowArrayStream* stream,
-                                    struct ArrowSchema* schema) {
+static int SingleBatchArrayStreamGetSchema(struct ArrowArrayStream* stream,
+                                           struct ArrowSchema* schema) {
   if (!stream || !stream->private_data) return EINVAL;
   struct SingleBatchArrayStream* impl =
       (struct SingleBatchArrayStream*)stream->private_data;
 
   return ArrowSchemaDeepCopy(&impl->schema, schema);
 }
-void SingleBatchArrayStreamRelease(struct ArrowArrayStream* stream) {
+static void SingleBatchArrayStreamRelease(struct ArrowArrayStream* stream) {
   if (!stream || !stream->private_data) return;
   struct SingleBatchArrayStream* impl =
       (struct SingleBatchArrayStream*)stream->private_data;
@@ -119,24 +120,41 @@ AdbcStatusCode BatchToArrayStream(struct ArrowArray* 
values, struct ArrowSchema*
   return ADBC_STATUS_OK;
 }
 
-void StringBuilderInit(struct StringBuilder* builder, size_t initial_size) {
+int StringBuilderInit(struct StringBuilder* builder, size_t initial_size) {
   builder->buffer = (char*)malloc(initial_size);
+  if (builder->buffer == NULL) return errno;
+
   builder->size = 0;
   builder->capacity = initial_size;
+
+  return 0;
 }
-void StringBuilderAppend(struct StringBuilder* builder, const char* value) {
-  size_t length = strlen(value);
-  size_t new_size = builder->size + length;
-  if (new_size > builder->capacity) {
-    size_t new_capacity = builder->size + length - builder->capacity;
-    if (builder->size == 0) new_capacity++;
-
-    builder->buffer = realloc(builder->buffer, new_capacity);
-    builder->capacity = new_capacity;
+int StringBuilderAppend(struct StringBuilder* builder, const char* fmt, ...) {
+  va_list argptr;
+  int bytes_available = builder->capacity - builder->size;
+
+  va_start(argptr, fmt);
+  int n = vsnprintf(builder->buffer + builder->size, bytes_available, fmt, 
argptr);
+  va_end(argptr);
+
+  if (n < 0) {
+    return errno;
+  } else if (n >= bytes_available) {  // output was truncated
+    int bytes_needed = n - bytes_available + 1;
+    builder->buffer = (char*)realloc(builder->buffer, builder->capacity + 
bytes_needed);
+    if (builder->buffer == NULL) return errno;
+
+    builder->capacity += bytes_needed;
+
+    va_start(argptr, fmt);
+    int ret = vsnprintf(builder->buffer + builder->size, n + 1, fmt, argptr);
+    assert(ret >= 0);
+
+    va_end(argptr);
   }
-  memcpy(builder->buffer + builder->size, value, length);
-  builder->buffer[new_size] = '\0';
-  builder->size = new_size;
+  builder->size += n;
+
+  return 0;
 }
 void StringBuilderReset(struct StringBuilder* builder) {
   if (builder->buffer) {
diff --git a/c/driver/common/utils.h b/c/driver/common/utils.h
index 3fc5bc5..0223dca 100644
--- a/c/driver/common/utils.h
+++ b/c/driver/common/utils.h
@@ -27,6 +27,10 @@
 #define SET_ERROR_ATTRIBUTE
 #endif
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 /// Set error details using a format string.
 void SetError(struct AdbcError* error, const char* format, ...) 
SET_ERROR_ATTRIBUTE;
 
@@ -43,8 +47,15 @@ struct StringBuilder {
   size_t size;
   size_t capacity;
 };
-void StringBuilderInit(struct StringBuilder* builder, size_t initial_size);
-void StringBuilderAppend(struct StringBuilder* builder, const char* value);
+int StringBuilderInit(struct StringBuilder* builder, size_t initial_size);
+
+#if defined(__GNUC__)
+#define ADBC_STRING_BUILDER_FORMAT_CHECK __attribute__((format(printf, 2, 3)))
+#else
+#define ADBC_STRING_BUILDER_FORMAT_CHECK
+#endif
+int ADBC_STRING_BUILDER_FORMAT_CHECK StringBuilderAppend(struct StringBuilder* 
builder,
+                                                         const char* fmt, ...);
 void StringBuilderReset(struct StringBuilder* builder);
 
 /// Check an NanoArrow status code.
@@ -92,3 +103,7 @@ void StringBuilderReset(struct StringBuilder* builder);
     AdbcStatusCode adbc_status_code = (EXPR);                        \
     if (adbc_status_code != ADBC_STATUS_OK) return adbc_status_code; \
   } while (0)
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/c/driver/common/utils_test.cc b/c/driver/common/utils_test.cc
new file mode 100644
index 0000000..6fa7e25
--- /dev/null
+++ b/c/driver/common/utils_test.cc
@@ -0,0 +1,74 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include <gtest/gtest.h>
+
+#include "utils.h"
+
+TEST(TestStringBuilder, TestBasic) {
+  struct StringBuilder str;
+  int ret;
+  ret = StringBuilderInit(&str, /*initial_size=*/64);
+  EXPECT_EQ(ret, 0);
+  EXPECT_EQ(str.capacity, 64);
+
+  ret = StringBuilderAppend(&str, "%s", "BASIC TEST");
+  EXPECT_EQ(ret, 0);
+  EXPECT_EQ(str.size, 10);
+  EXPECT_STREQ(str.buffer, "BASIC TEST");
+
+  StringBuilderReset(&str);
+}
+
+TEST(TestStringBuilder, TestBoundary) {
+  struct StringBuilder str;
+  int ret;
+  ret = StringBuilderInit(&str, /*initial_size=*/10);
+  EXPECT_EQ(ret, 0);
+  EXPECT_EQ(str.capacity, 10);
+
+  ret = StringBuilderAppend(&str, "%s", "BASIC TEST");
+  EXPECT_EQ(ret, 0);
+  // should resize to include \0
+  EXPECT_EQ(str.capacity, 11);
+  EXPECT_EQ(str.size, 10);
+  EXPECT_STREQ(str.buffer, "BASIC TEST");
+
+  StringBuilderReset(&str);
+}
+
+TEST(TestStringBuilder, TestMultipleAppends) {
+  struct StringBuilder str;
+  int ret;
+  ret = StringBuilderInit(&str, /*initial_size=*/2);
+  EXPECT_EQ(ret, 0);
+  EXPECT_EQ(str.capacity, 2);
+
+  ret = StringBuilderAppend(&str, "%s", "BASIC");
+  EXPECT_EQ(ret, 0);
+  EXPECT_EQ(str.capacity, 6);
+  EXPECT_EQ(str.size, 5);
+  EXPECT_STREQ(str.buffer, "BASIC");
+
+  ret = StringBuilderAppend(&str, "%s", " TEST");
+  EXPECT_EQ(ret, 0);
+  EXPECT_EQ(str.capacity, 11);
+  EXPECT_EQ(str.size, 10);
+  EXPECT_STREQ(str.buffer, "BASIC TEST");
+
+  StringBuilderReset(&str);
+}
diff --git a/c/driver/sqlite/sqlite.c b/c/driver/sqlite/sqlite.c
index dab5e72..2cc3276 100644
--- a/c/driver/sqlite/sqlite.c
+++ b/c/driver/sqlite/sqlite.c
@@ -1022,9 +1022,16 @@ AdbcStatusCode SqliteConnectionGetTableSchema(struct 
AdbcConnection* connection,
   }
 
   struct StringBuilder query = {0};
-  StringBuilderInit(&query, /*initial_size=*/64);
-  StringBuilderAppend(&query, "SELECT * FROM ");
-  StringBuilderAppend(&query, table_name);
+  if (StringBuilderInit(&query, /*initial_size=*/64) != 0) {
+    SetError(error, "Could not initiate StringBuilder");
+    return ADBC_STATUS_INTERNAL;
+  }
+
+  if (StringBuilderAppend(&query, "%s%s", "SELECT * FROM ", table_name) != 0) {
+    StringBuilderReset(&query);
+    SetError(error, "Call to StringBuilderAppend failed");
+    return ADBC_STATUS_INTERNAL;
+  }
 
   sqlite3_stmt* stmt = NULL;
   int rc =
@@ -1222,27 +1229,67 @@ AdbcStatusCode SqliteStatementInitIngest(struct 
SqliteStatement* stmt,
   // Create statements for CREATE TABLE / INSERT
   struct StringBuilder create_query = {0};
   struct StringBuilder insert_query = {0};
-  StringBuilderInit(&create_query, 256);
-  StringBuilderInit(&insert_query, 256);
 
-  StringBuilderAppend(&create_query, "CREATE TABLE ");
-  StringBuilderAppend(&create_query, stmt->target_table);
-  StringBuilderAppend(&create_query, " (");
+  if (StringBuilderInit(&create_query, /*initial_size=*/256) != 0) {
+    SetError(error, "Could not initiate StringBuilder");
+    return ADBC_STATUS_INTERNAL;
+  }
+
+  if (StringBuilderInit(&insert_query, /*initial_size=*/256) != 0) {
+    SetError(error, "Could not initiate StringBuilder");
+    StringBuilderReset(&create_query);
+    return ADBC_STATUS_INTERNAL;
+  }
+
+  if (StringBuilderAppend(&create_query, "%s%s%s", "CREATE TABLE ", 
stmt->target_table,
+                          " (") != 0) {
+    SetError(error, "Call to StringBuilderAppend failed");
+    code = ADBC_STATUS_INTERNAL;
+    goto cleanup;
+  }
 
-  StringBuilderAppend(&insert_query, "INSERT INTO ");
-  StringBuilderAppend(&insert_query, stmt->target_table);
-  StringBuilderAppend(&insert_query, " VALUES (");
+  if (StringBuilderAppend(&insert_query, "%s%s%s", "INSERT INTO ", 
stmt->target_table,
+                          " VALUES (") != 0) {
+    SetError(error, "Call to StringBuilderAppend failed");
+    code = ADBC_STATUS_INTERNAL;
+    goto cleanup;
+  }
 
   for (int i = 0; i < stmt->binder.schema.n_children; i++) {
-    if (i > 0) StringBuilderAppend(&create_query, ", ");
+    if (i > 0) StringBuilderAppend(&create_query, "%s", ", ");
     // XXX: should escape the column name too
-    StringBuilderAppend(&create_query, stmt->binder.schema.children[i]->name);
+    if (StringBuilderAppend(&create_query, "%s", 
stmt->binder.schema.children[i]->name) !=
+        0) {
+      SetError(error, "Call to StringBuilderAppend failed");
+      code = ADBC_STATUS_INTERNAL;
+      goto cleanup;
+    }
+
+    if (i > 0) {
+      if (StringBuilderAppend(&insert_query, "%s", ", ") != 0) {
+        SetError(error, "Call to StringBuilderAppend failed");
+        code = ADBC_STATUS_INTERNAL;
+        goto cleanup;
+      }
+    }
 
-    if (i > 0) StringBuilderAppend(&insert_query, ", ");
-    StringBuilderAppend(&insert_query, "?");
+    if (StringBuilderAppend(&insert_query, "%s", "?") != 0) {
+      SetError(error, "Call to StringBuilderAppend failed");
+      code = ADBC_STATUS_INTERNAL;
+      goto cleanup;
+    }
+  }
+  if (StringBuilderAppend(&create_query, "%s", ")") != 0) {
+    SetError(error, "Call to StringBuilderAppend failed");
+    code = ADBC_STATUS_INTERNAL;
+    goto cleanup;
+  }
+
+  if (StringBuilderAppend(&insert_query, "%s", ")") != 0) {
+    SetError(error, "Call to StringBuilderAppend failed");
+    code = ADBC_STATUS_INTERNAL;
+    goto cleanup;
   }
-  StringBuilderAppend(&create_query, ")");
-  StringBuilderAppend(&insert_query, ")");
 
   sqlite3_stmt* create = NULL;
   if (!stmt->append) {
@@ -1271,6 +1318,8 @@ AdbcStatusCode SqliteStatementInitIngest(struct 
SqliteStatement* stmt,
   }
 
   sqlite3_finalize(create);
+
+cleanup:
   StringBuilderReset(&create_query);
   StringBuilderReset(&insert_query);
   return code;

Reply via email to