changeset 75:ae6a88e6e484

[Deita] Simple DB connection lib.
author June Park <parkjune1995@gmail.com>
date Wed, 31 Dec 2025 11:20:08 -0800
parents 4b96794c8d59
children 35b1abc37969
files deita/BUILD deita/d_connection.c deita/d_query.c deita/d_sqlite.c deita/deita.h deita/deita_internal.h deita/deita_test.c raylib_examples/BUILD raylib_examples/main.c
diffstat 9 files changed, 856 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deita/BUILD	Wed Dec 31 11:20:08 2025 -0800
@@ -0,0 +1,26 @@
+load("@rules_cc//cc:cc_library.bzl", "cc_library")
+load("@rules_cc//cc:cc_test.bzl", "cc_test")
+
+cc_library(
+  name = "deita",
+  srcs = [
+    "d_connection.c",
+    "d_query.c",
+    "d_sqlite.c",
+  ],
+  hdrs = [
+    "deita.h",
+    "deita_internal.h",
+  ],
+  deps = [
+    "//dowa:dowa",
+  ],
+  linkopts = ["-lsqlite3"],
+  visibility = ["//visibility:public"],
+)
+
+cc_test(
+  name = "deita_test",
+  srcs = ["deita_test.c"],
+  deps = [":deita"],
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deita/d_connection.c	Wed Dec 31 11:20:08 2025 -0800
@@ -0,0 +1,40 @@
+#include "deita_internal.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+Deita_Connection* Deita_Connection_Create(
+  Deita_Database_Type database_type,
+  const char *connection_string)
+{
+  if (database_type == DEITA_DATABASE_TYPE_SQLITE3)
+    return deita__sqlite_connection_create(connection_string);
+
+  fprintf(stderr, "Deita_Connection_Create: Unsupported database type %d\n", database_type);
+  return NULL;
+}
+
+void Deita_Connection_Close(Deita_Connection *p_connection)
+{
+  if (!p_connection)
+    return;
+
+  if (p_connection->database_type == DEITA_DATABASE_TYPE_SQLITE3)
+  {
+    deita__sqlite_connection_close(p_connection);
+    return;
+  }
+
+  fprintf(stderr, "Deita_Connection_Close: Unsupported database type %d\n", p_connection->database_type);
+}
+
+boolean Deita_Connection_Is_Open(Deita_Connection *p_connection)
+{
+  if (!p_connection)
+    return FALSE;
+
+  if (p_connection->database_type == DEITA_DATABASE_TYPE_SQLITE3)
+    return deita__sqlite_connection_is_open(p_connection);
+
+  fprintf(stderr, "Deita_Connection_Is_Open: Unsupported database type %d\n", p_connection->database_type);
+  return FALSE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deita/d_query.c	Wed Dec 31 11:20:08 2025 -0800
@@ -0,0 +1,172 @@
+#include "deita_internal.h"
+#include <stdio.h>
+
+Deita_Result_Set* Deita_Query_Execute(
+  Deita_Connection *p_connection,
+  const char *query,
+  Dowa_Arena *p_arena)
+{
+  if (!p_connection || !query)
+    return NULL;
+
+  if (p_connection->database_type == DEITA_DATABASE_TYPE_SQLITE3)
+    return deita__sqlite_query_execute(p_connection, query, p_arena);
+
+  fprintf(stderr, "Deita_Query_Execute: Unsupported database type %d\n", p_connection->database_type);
+  return NULL;
+}
+
+Deita_Result_Set* Deita_Query_Execute_Prepared(
+  Deita_Connection *p_connection,
+  const char *query,
+  int32 parameter_count,
+  const char **parameter_values,
+  Dowa_Arena *p_arena)
+{
+  if (!p_connection || !query)
+    return NULL;
+
+  if (p_connection->database_type == DEITA_DATABASE_TYPE_SQLITE3)
+    return deita__sqlite_query_execute_prepared(p_connection, query, parameter_count, parameter_values, p_arena);
+
+  fprintf(stderr, "Deita_Query_Execute_Prepared: Unsupported database type %d\n", p_connection->database_type);
+  return NULL;
+}
+
+int32 Deita_Query_Execute_Update(
+  Deita_Connection *p_connection,
+  const char *query)
+{
+  if (!p_connection || !query)
+    return -1;
+
+  if (p_connection->database_type == DEITA_DATABASE_TYPE_SQLITE3)
+    return deita__sqlite_query_execute_update(p_connection, query);
+
+  fprintf(stderr, "Deita_Query_Execute_Update: Unsupported database type %d\n", p_connection->database_type);
+  return -1;
+}
+
+int32 Deita_Query_Execute_Update_Prepared(
+  Deita_Connection *p_connection,
+  const char *query,
+  int32 parameter_count,
+  const char **parameter_values)
+{
+  if (!p_connection || !query)
+    return -1;
+
+  if (p_connection->database_type == DEITA_DATABASE_TYPE_SQLITE3)
+    return deita__sqlite_query_execute_update_prepared(p_connection, query, parameter_count, parameter_values);
+
+  fprintf(stderr, "Deita_Query_Execute_Update_Prepared: Unsupported database type %d\n", p_connection->database_type);
+  return -1;
+}
+
+boolean Deita_Result_Set_Next(Deita_Result_Set *p_result_set)
+{
+  if (!p_result_set)
+    return FALSE;
+
+  if (p_result_set->database_type == DEITA_DATABASE_TYPE_SQLITE3)
+    return deita__sqlite_result_set_next(p_result_set);
+
+  fprintf(stderr, "Deita_Result_Set_Next: Unsupported database type %d\n", p_result_set->database_type);
+  return FALSE;
+}
+
+int32 Deita_Result_Set_Get_Column_Count(Deita_Result_Set *p_result_set)
+{
+  if (!p_result_set)
+    return 0;
+
+  if (p_result_set->database_type == DEITA_DATABASE_TYPE_SQLITE3)
+    return deita__sqlite_result_set_get_column_count(p_result_set);
+
+  fprintf(stderr, "Deita_Result_Set_Get_Column_Count: Unsupported database type %d\n", p_result_set->database_type);
+  return 0;
+}
+
+const char* Deita_Result_Set_Get_Column_Name(
+  Deita_Result_Set *p_result_set,
+  int32 column_index)
+{
+  if (!p_result_set)
+    return NULL;
+
+  if (p_result_set->database_type == DEITA_DATABASE_TYPE_SQLITE3)
+    return deita__sqlite_result_set_get_column_name(p_result_set, column_index);
+
+  fprintf(stderr, "Deita_Result_Set_Get_Column_Name: Unsupported database type %d\n", p_result_set->database_type);
+  return NULL;
+}
+
+Deita_Column_Type Deita_Result_Set_Get_Column_Type(
+  Deita_Result_Set *p_result_set,
+  int32 column_index)
+{
+  if (!p_result_set)
+    return DEITA_COLUMN_TYPE_NULL;
+
+  if (p_result_set->database_type == DEITA_DATABASE_TYPE_SQLITE3)
+    return deita__sqlite_result_set_get_column_type(p_result_set, column_index);
+
+  fprintf(stderr, "Deita_Result_Set_Get_Column_Type: Unsupported database type %d\n", p_result_set->database_type);
+  return DEITA_COLUMN_TYPE_NULL;
+}
+
+const char* Deita_Result_Set_Get_Text(
+  Deita_Result_Set *p_result_set,
+  int32 column_index)
+{
+  if (!p_result_set)
+    return NULL;
+
+  if (p_result_set->database_type == DEITA_DATABASE_TYPE_SQLITE3)
+    return deita__sqlite_result_set_get_text(p_result_set, column_index);
+
+  fprintf(stderr, "Deita_Result_Set_Get_Text: Unsupported database type %d\n", p_result_set->database_type);
+  return NULL;
+}
+
+int64 Deita_Result_Set_Get_Integer(
+  Deita_Result_Set *p_result_set,
+  int32 column_index)
+{
+  if (!p_result_set)
+    return 0;
+
+  if (p_result_set->database_type == DEITA_DATABASE_TYPE_SQLITE3)
+    return deita__sqlite_result_set_get_integer(p_result_set, column_index);
+
+  fprintf(stderr, "Deita_Result_Set_Get_Integer: Unsupported database type %d\n", p_result_set->database_type);
+  return 0;
+}
+
+double Deita_Result_Set_Get_Real(
+  Deita_Result_Set *p_result_set,
+  int32 column_index)
+{
+  if (!p_result_set)
+    return 0.0;
+
+  if (p_result_set->database_type == DEITA_DATABASE_TYPE_SQLITE3)
+    return deita__sqlite_result_set_get_real(p_result_set, column_index);
+
+  fprintf(stderr, "Deita_Result_Set_Get_Real: Unsupported database type %d\n", p_result_set->database_type);
+  return 0.0;
+}
+
+void Deita_Result_Set_Free(Deita_Result_Set *p_result_set)
+{
+  if (!p_result_set)
+    return;
+
+  if (p_result_set->database_type == DEITA_DATABASE_TYPE_SQLITE3)
+  {
+    deita__sqlite_result_set_free(p_result_set);
+    return;
+  }
+
+  fprintf(stderr, "Deita_Result_Set_Free: Unsupported database type %d\n", p_result_set->database_type);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deita/d_sqlite.c	Wed Dec 31 11:20:08 2025 -0800
@@ -0,0 +1,327 @@
+#include "deita_internal.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+Deita_Connection* deita__sqlite_connection_create(const char *connection_string)
+{
+  Deita_Connection *p_connection = (Deita_Connection*)malloc(sizeof(Deita_Connection));
+  if (!p_connection)
+  {
+    fprintf(stderr, "deita__sqlite_connection_create: Failed to allocate connection\n");
+    return NULL;
+  }
+
+  p_connection->database_type = DEITA_DATABASE_TYPE_SQLITE3;
+  p_connection->native_handle = NULL;
+  p_connection->is_open = FALSE;
+
+  sqlite3 *p_db = NULL;
+  int32 result = sqlite3_open(connection_string, &p_db);
+
+  if (result != SQLITE_OK)
+  {
+    fprintf(stderr, "deita__sqlite_connection_create: Failed to open database: %s\n", sqlite3_errmsg(p_db));
+    sqlite3_close(p_db);
+    free(p_connection);
+    return NULL;
+  }
+
+  p_connection->native_handle = p_db;
+  p_connection->is_open = TRUE;
+
+  return p_connection;
+}
+
+void deita__sqlite_connection_close(Deita_Connection *p_connection)
+{
+  if (!p_connection)
+    return;
+
+  if (p_connection->native_handle)
+  {
+    sqlite3 *p_db = (sqlite3*)p_connection->native_handle;
+    sqlite3_close(p_db);
+    p_connection->native_handle = NULL;
+  }
+
+  p_connection->is_open = FALSE;
+  free(p_connection);
+}
+
+boolean deita__sqlite_connection_is_open(Deita_Connection *p_connection)
+{
+  if (!p_connection)
+    return FALSE;
+
+  return p_connection->is_open;
+}
+
+Deita_Result_Set* deita__sqlite_query_execute(
+  Deita_Connection *p_connection,
+  const char *query,
+  Dowa_Arena *p_arena)
+{
+  if (!p_connection || !p_connection->native_handle || !query)
+    return NULL;
+
+  sqlite3 *p_db = (sqlite3*)p_connection->native_handle;
+  sqlite3_stmt *p_stmt = NULL;
+
+  int32 result = sqlite3_prepare_v2(p_db, query, -1, &p_stmt, NULL);
+  if (result != SQLITE_OK)
+  {
+    fprintf(stderr, "deita__sqlite_query_execute: Failed to prepare statement: %s\n", sqlite3_errmsg(p_db));
+    return NULL;
+  }
+
+  Deita_Result_Set *p_result_set = (Deita_Result_Set*)Dowa_Arena_Allocate(p_arena, sizeof(Deita_Result_Set));
+  if (!p_result_set)
+  {
+    fprintf(stderr, "deita__sqlite_query_execute: Failed to allocate result set\n");
+    sqlite3_finalize(p_stmt);
+    return NULL;
+  }
+
+  p_result_set->database_type = DEITA_DATABASE_TYPE_SQLITE3;
+  p_result_set->native_result = p_stmt;
+  p_result_set->column_count = sqlite3_column_count(p_stmt);
+  p_result_set->has_data = FALSE;
+  p_result_set->is_done = FALSE;
+
+  return p_result_set;
+}
+
+Deita_Result_Set* deita__sqlite_query_execute_prepared(
+  Deita_Connection *p_connection,
+  const char *query,
+  int32 parameter_count,
+  const char **parameter_values,
+  Dowa_Arena *p_arena)
+{
+  if (!p_connection || !p_connection->native_handle || !query)
+    return NULL;
+
+  sqlite3 *p_db = (sqlite3*)p_connection->native_handle;
+  sqlite3_stmt *p_stmt = NULL;
+
+  int32 result = sqlite3_prepare_v2(p_db, query, -1, &p_stmt, NULL);
+  if (result != SQLITE_OK)
+  {
+    fprintf(stderr, "deita__sqlite_query_execute_prepared: Failed to prepare statement: %s\n", sqlite3_errmsg(p_db));
+    return NULL;
+  }
+
+  for (int32 i = 0; i < parameter_count; i++)
+  {
+    result = sqlite3_bind_text(p_stmt, i + 1, parameter_values[i], -1, SQLITE_TRANSIENT);
+    if (result != SQLITE_OK)
+    {
+      fprintf(stderr, "deita__sqlite_query_execute_prepared: Failed to bind parameter %d: %s\n", i, sqlite3_errmsg(p_db));
+      sqlite3_finalize(p_stmt);
+      return NULL;
+    }
+  }
+
+  Deita_Result_Set *p_result_set = (Deita_Result_Set*)Dowa_Arena_Allocate(p_arena, sizeof(Deita_Result_Set));
+  if (!p_result_set)
+  {
+    fprintf(stderr, "deita__sqlite_query_execute_prepared: Failed to allocate result set\n");
+    sqlite3_finalize(p_stmt);
+    return NULL;
+  }
+
+  p_result_set->database_type = DEITA_DATABASE_TYPE_SQLITE3;
+  p_result_set->native_result = p_stmt;
+  p_result_set->column_count = sqlite3_column_count(p_stmt);
+  p_result_set->has_data = FALSE;
+  p_result_set->is_done = FALSE;
+
+  return p_result_set;
+}
+
+int32 deita__sqlite_query_execute_update(
+  Deita_Connection *p_connection,
+  const char *query)
+{
+  if (!p_connection || !p_connection->native_handle || !query)
+    return -1;
+
+  sqlite3 *p_db = (sqlite3*)p_connection->native_handle;
+  char *error_message = NULL;
+
+  int32 result = sqlite3_exec(p_db, query, NULL, NULL, &error_message);
+  if (result != SQLITE_OK)
+  {
+    fprintf(stderr, "deita__sqlite_query_execute_update: Failed to execute: %s\n", error_message);
+    sqlite3_free(error_message);
+    return -1;
+  }
+
+  return sqlite3_changes(p_db);
+}
+
+int32 deita__sqlite_query_execute_update_prepared(
+  Deita_Connection *p_connection,
+  const char *query,
+  int32 parameter_count,
+  const char **parameter_values)
+{
+  if (!p_connection || !p_connection->native_handle || !query)
+    return -1;
+
+  sqlite3 *p_db = (sqlite3*)p_connection->native_handle;
+  sqlite3_stmt *p_stmt = NULL;
+
+  int32 result = sqlite3_prepare_v2(p_db, query, -1, &p_stmt, NULL);
+  if (result != SQLITE_OK)
+  {
+    fprintf(stderr, "deita__sqlite_query_execute_update_prepared: Failed to prepare statement: %s\n", sqlite3_errmsg(p_db));
+    return -1;
+  }
+
+  for (int32 i = 0; i < parameter_count; i++)
+  {
+    result = sqlite3_bind_text(p_stmt, i + 1, parameter_values[i], -1, SQLITE_TRANSIENT);
+    if (result != SQLITE_OK)
+    {
+      fprintf(stderr, "deita__sqlite_query_execute_update_prepared: Failed to bind parameter %d: %s\n", i, sqlite3_errmsg(p_db));
+      sqlite3_finalize(p_stmt);
+      return -1;
+    }
+  }
+
+  result = sqlite3_step(p_stmt);
+  if (result != SQLITE_DONE)
+  {
+    fprintf(stderr, "deita__sqlite_query_execute_update_prepared: Failed to execute: %s\n", sqlite3_errmsg(p_db));
+    sqlite3_finalize(p_stmt);
+    return -1;
+  }
+
+  int32 changes = sqlite3_changes(p_db);
+  sqlite3_finalize(p_stmt);
+
+  return changes;
+}
+
+boolean deita__sqlite_result_set_next(Deita_Result_Set *p_result_set)
+{
+  if (!p_result_set || !p_result_set->native_result || p_result_set->is_done)
+    return FALSE;
+
+  sqlite3_stmt *p_stmt = (sqlite3_stmt*)p_result_set->native_result;
+  int32 result = sqlite3_step(p_stmt);
+
+  if (result == SQLITE_ROW)
+  {
+    p_result_set->has_data = TRUE;
+    return TRUE;
+  }
+  else if (result == SQLITE_DONE)
+  {
+    p_result_set->has_data = FALSE;
+    p_result_set->is_done = TRUE;
+    return FALSE;
+  }
+  else
+  {
+    fprintf(stderr, "deita__sqlite_result_set_next: Error stepping through results\n");
+    p_result_set->has_data = FALSE;
+    p_result_set->is_done = TRUE;
+    return FALSE;
+  }
+}
+
+int32 deita__sqlite_result_set_get_column_count(Deita_Result_Set *p_result_set)
+{
+  if (!p_result_set)
+    return 0;
+
+  return p_result_set->column_count;
+}
+
+const char* deita__sqlite_result_set_get_column_name(
+  Deita_Result_Set *p_result_set,
+  int32 column_index)
+{
+  if (!p_result_set || !p_result_set->native_result)
+    return NULL;
+
+  sqlite3_stmt *p_stmt = (sqlite3_stmt*)p_result_set->native_result;
+  return sqlite3_column_name(p_stmt, column_index);
+}
+
+Deita_Column_Type deita__sqlite_result_set_get_column_type(
+  Deita_Result_Set *p_result_set,
+  int32 column_index)
+{
+  if (!p_result_set || !p_result_set->native_result)
+    return DEITA_COLUMN_TYPE_NULL;
+
+  sqlite3_stmt *p_stmt = (sqlite3_stmt*)p_result_set->native_result;
+  int32 sqlite_type = sqlite3_column_type(p_stmt, column_index);
+
+  switch (sqlite_type)
+  {
+    case SQLITE_INTEGER:
+      return DEITA_COLUMN_TYPE_INTEGER;
+    case SQLITE_FLOAT:
+      return DEITA_COLUMN_TYPE_REAL;
+    case SQLITE_TEXT:
+      return DEITA_COLUMN_TYPE_TEXT;
+    case SQLITE_BLOB:
+      return DEITA_COLUMN_TYPE_BLOB;
+    case SQLITE_NULL:
+    default:
+      return DEITA_COLUMN_TYPE_NULL;
+  }
+}
+
+const char* deita__sqlite_result_set_get_text(
+  Deita_Result_Set *p_result_set,
+  int32 column_index)
+{
+  if (!p_result_set || !p_result_set->native_result)
+    return NULL;
+
+  sqlite3_stmt *p_stmt = (sqlite3_stmt*)p_result_set->native_result;
+  const unsigned char *text = sqlite3_column_text(p_stmt, column_index);
+
+  return (const char*)text;
+}
+
+int64 deita__sqlite_result_set_get_integer(
+  Deita_Result_Set *p_result_set,
+  int32 column_index)
+{
+  if (!p_result_set || !p_result_set->native_result)
+    return 0;
+
+  sqlite3_stmt *p_stmt = (sqlite3_stmt*)p_result_set->native_result;
+  return sqlite3_column_int64(p_stmt, column_index);
+}
+
+double deita__sqlite_result_set_get_real(
+  Deita_Result_Set *p_result_set,
+  int32 column_index)
+{
+  if (!p_result_set || !p_result_set->native_result)
+    return 0.0;
+
+  sqlite3_stmt *p_stmt = (sqlite3_stmt*)p_result_set->native_result;
+  return sqlite3_column_double(p_stmt, column_index);
+}
+
+void deita__sqlite_result_set_free(Deita_Result_Set *p_result_set)
+{
+  if (!p_result_set)
+    return;
+
+  if (p_result_set->native_result)
+  {
+    sqlite3_stmt *p_stmt = (sqlite3_stmt*)p_result_set->native_result;
+    sqlite3_finalize(p_stmt);
+    p_result_set->native_result = NULL;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deita/deita.h	Wed Dec 31 11:20:08 2025 -0800
@@ -0,0 +1,101 @@
+#ifndef DEITA
+#define DEITA
+
+#include "dowa/dowa.h"
+
+// Database types
+typedef enum
+{
+  DEITA_DATABASE_TYPE_SQLITE3 = 0
+  // Future: DEITA_DATABASE_TYPE_POSTGRES, DEITA_DATABASE_TYPE_MYSQL
+} Deita_Database_Type;
+
+// Connection handle (opaque)
+typedef struct Deita_Connection Deita_Connection;
+
+// Result set (opaque)
+typedef struct Deita_Result_Set Deita_Result_Set;
+
+// Column value type
+typedef enum
+{
+  DEITA_COLUMN_TYPE_NULL = 0,
+  DEITA_COLUMN_TYPE_INTEGER,
+  DEITA_COLUMN_TYPE_REAL,
+  DEITA_COLUMN_TYPE_TEXT,
+  DEITA_COLUMN_TYPE_BLOB
+} Deita_Column_Type;
+
+// --- Connection Management --- //
+
+extern Deita_Connection* Deita_Connection_Create(
+  Deita_Database_Type database_type,
+  const char *connection_string
+);
+
+extern void Deita_Connection_Close(Deita_Connection *p_connection);
+
+extern boolean Deita_Connection_Is_Open(Deita_Connection *p_connection);
+
+// --- Query Execution --- //
+
+extern Deita_Result_Set* Deita_Query_Execute(
+  Deita_Connection *p_connection,
+  const char *query,
+  Dowa_Arena *p_arena
+);
+
+extern Deita_Result_Set* Deita_Query_Execute_Prepared(
+  Deita_Connection *p_connection,
+  const char *query,
+  int32 parameter_count,
+  const char **parameter_values,
+  Dowa_Arena *p_arena
+);
+
+extern int32 Deita_Query_Execute_Update(
+  Deita_Connection *p_connection,
+  const char *query
+);
+
+extern int32 Deita_Query_Execute_Update_Prepared(
+  Deita_Connection *p_connection,
+  const char *query,
+  int32 parameter_count,
+  const char **parameter_values
+);
+
+// --- Result Set Access --- //
+
+extern boolean Deita_Result_Set_Next(Deita_Result_Set *p_result_set);
+
+extern int32 Deita_Result_Set_Get_Column_Count(Deita_Result_Set *p_result_set);
+
+extern const char* Deita_Result_Set_Get_Column_Name(
+  Deita_Result_Set *p_result_set,
+  int32 column_index
+);
+
+extern Deita_Column_Type Deita_Result_Set_Get_Column_Type(
+  Deita_Result_Set *p_result_set,
+  int32 column_index
+);
+
+extern const char* Deita_Result_Set_Get_Text(
+  Deita_Result_Set *p_result_set,
+  int32 column_index
+);
+
+extern int64 Deita_Result_Set_Get_Integer(
+  Deita_Result_Set *p_result_set,
+  int32 column_index
+);
+
+extern double Deita_Result_Set_Get_Real(
+  Deita_Result_Set *p_result_set,
+  int32 column_index
+);
+
+extern void Deita_Result_Set_Free(Deita_Result_Set *p_result_set);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deita/deita_internal.h	Wed Dec 31 11:20:08 2025 -0800
@@ -0,0 +1,63 @@
+#ifndef DEITA_INTERNAL
+#define DEITA_INTERNAL
+
+#include <sqlite3.h>
+#include "deita.h"
+
+struct Deita_Connection
+{
+  Deita_Database_Type database_type;
+  void *native_handle;
+  boolean is_open;
+};
+
+struct Deita_Result_Set
+{
+  Deita_Database_Type database_type;
+  void *native_result;
+  int32 column_count;
+  boolean has_data;
+  boolean is_done;
+};
+
+// SQLite3-specific functions (implemented in d_sqlite.c)
+extern Deita_Connection* deita__sqlite_connection_create(const char *connection_string);
+extern void deita__sqlite_connection_close(Deita_Connection *p_connection);
+extern boolean deita__sqlite_connection_is_open(Deita_Connection *p_connection);
+
+extern Deita_Result_Set* deita__sqlite_query_execute(
+  Deita_Connection *p_connection,
+  const char *query,
+  Dowa_Arena *p_arena
+);
+
+extern Deita_Result_Set* deita__sqlite_query_execute_prepared(
+  Deita_Connection *p_connection,
+  const char *query,
+  int32 parameter_count,
+  const char **parameter_values,
+  Dowa_Arena *p_arena
+);
+
+extern int32 deita__sqlite_query_execute_update(
+  Deita_Connection *p_connection,
+  const char *query
+);
+
+extern int32 deita__sqlite_query_execute_update_prepared(
+  Deita_Connection *p_connection,
+  const char *query,
+  int32 parameter_count,
+  const char **parameter_values
+);
+
+extern boolean deita__sqlite_result_set_next(Deita_Result_Set *p_result_set);
+extern int32 deita__sqlite_result_set_get_column_count(Deita_Result_Set *p_result_set);
+extern const char* deita__sqlite_result_set_get_column_name(Deita_Result_Set *p_result_set, int32 column_index);
+extern Deita_Column_Type deita__sqlite_result_set_get_column_type(Deita_Result_Set *p_result_set, int32 column_index);
+extern const char* deita__sqlite_result_set_get_text(Deita_Result_Set *p_result_set, int32 column_index);
+extern int64 deita__sqlite_result_set_get_integer(Deita_Result_Set *p_result_set, int32 column_index);
+extern double deita__sqlite_result_set_get_real(Deita_Result_Set *p_result_set, int32 column_index);
+extern void deita__sqlite_result_set_free(Deita_Result_Set *p_result_set);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deita/deita_test.c	Wed Dec 31 11:20:08 2025 -0800
@@ -0,0 +1,127 @@
+#include "deita.h"
+#include <stdio.h>
+#include <assert.h>
+#include <unistd.h>
+
+int main(void)
+{
+  printf("=== Deita Library Tests ===\n\n");
+
+  const char *test_db_path = "/tmp/deita_test.db";
+  unlink(test_db_path);
+
+  // Test 1: Connection creation
+  printf("Test 1: Creating SQLite3 connection...\n");
+  Deita_Connection *p_connection = Deita_Connection_Create(DEITA_DATABASE_TYPE_SQLITE3, test_db_path);
+  assert(p_connection != NULL);
+  assert(Deita_Connection_Is_Open(p_connection) == TRUE);
+  printf("  PASSED: Connection created successfully\n\n");
+
+  // Test 2: Create table
+  printf("Test 2: Creating test table...\n");
+  const char *create_table_query =
+    "CREATE TABLE IF NOT EXISTS test_users ("
+    "  id INTEGER PRIMARY KEY AUTOINCREMENT,"
+    "  username TEXT NOT NULL,"
+    "  age INTEGER"
+    ")";
+  int32 result = Deita_Query_Execute_Update(p_connection, create_table_query);
+  assert(result == 0);
+  printf("  PASSED: Table created\n\n");
+
+  // Test 3: Insert data
+  printf("Test 3: Inserting test data...\n");
+  const char *insert_query = "INSERT INTO test_users (username, age) VALUES ('alice', 25)";
+  result = Deita_Query_Execute_Update(p_connection, insert_query);
+  assert(result == 1);
+  printf("  PASSED: Inserted 1 row\n\n");
+
+  // Test 4: Insert with prepared statement
+  printf("Test 4: Inserting with prepared statement...\n");
+  const char *insert_prepared_query = "INSERT INTO test_users (username, age) VALUES (?, ?)";
+  const char *params[] = {"bob", "30"};
+  result = Deita_Query_Execute_Update_Prepared(p_connection, insert_prepared_query, 2, params);
+  assert(result == 1);
+  printf("  PASSED: Inserted 1 row with prepared statement\n\n");
+
+  // Test 5: Query data
+  printf("Test 5: Querying test data...\n");
+  Dowa_Arena *p_arena = Dowa_Arena_Create(ONE_MEGA_BYTE);
+  const char *select_query = "SELECT id, username, age FROM test_users ORDER BY id";
+  Deita_Result_Set *p_result_set = Deita_Query_Execute(p_connection, select_query, p_arena);
+  assert(p_result_set != NULL);
+
+  int32 row_count = 0;
+  while (Deita_Result_Set_Next(p_result_set))
+  {
+    row_count++;
+    int64 id = Deita_Result_Set_Get_Integer(p_result_set, 0);
+    const char *username = Deita_Result_Set_Get_Text(p_result_set, 1);
+    int64 age = Deita_Result_Set_Get_Integer(p_result_set, 2);
+
+    printf("  Row %d: id=%lld, username=%s, age=%lld\n", row_count, id, username, age);
+
+    if (row_count == 1)
+    {
+      assert(id == 1);
+      assert(strcmp(username, "alice") == 0);
+      assert(age == 25);
+    }
+    else if (row_count == 2)
+    {
+      assert(id == 2);
+      assert(strcmp(username, "bob") == 0);
+      assert(age == 30);
+    }
+  }
+  assert(row_count == 2);
+  Deita_Result_Set_Free(p_result_set);
+  printf("  PASSED: Retrieved 2 rows\n\n");
+
+  // Test 6: Query with prepared statement
+  printf("Test 6: Querying with prepared statement...\n");
+  const char *select_prepared_query = "SELECT username, age FROM test_users WHERE username = ?";
+  const char *query_params[] = {"alice"};
+  p_result_set = Deita_Query_Execute_Prepared(p_connection, select_prepared_query, 1, query_params, p_arena);
+  assert(p_result_set != NULL);
+
+  assert(Deita_Result_Set_Next(p_result_set) == TRUE);
+  const char *username = Deita_Result_Set_Get_Text(p_result_set, 0);
+  int64 age = Deita_Result_Set_Get_Integer(p_result_set, 1);
+  assert(strcmp(username, "alice") == 0);
+  assert(age == 25);
+  assert(Deita_Result_Set_Next(p_result_set) == FALSE);
+  Deita_Result_Set_Free(p_result_set);
+  printf("  PASSED: Retrieved correct row with prepared statement\n\n");
+
+  // Test 7: Column metadata
+  printf("Test 7: Testing column metadata...\n");
+  p_result_set = Deita_Query_Execute(p_connection, "SELECT id, username FROM test_users LIMIT 1", p_arena);
+  assert(p_result_set != NULL);
+
+  int32 column_count = Deita_Result_Set_Get_Column_Count(p_result_set);
+  assert(column_count == 2);
+
+  const char *col_name_0 = Deita_Result_Set_Get_Column_Name(p_result_set, 0);
+  const char *col_name_1 = Deita_Result_Set_Get_Column_Name(p_result_set, 1);
+  assert(strcmp(col_name_0, "id") == 0);
+  assert(strcmp(col_name_1, "username") == 0);
+
+  assert(Deita_Result_Set_Next(p_result_set) == TRUE);
+  Deita_Column_Type type_0 = Deita_Result_Set_Get_Column_Type(p_result_set, 0);
+  Deita_Column_Type type_1 = Deita_Result_Set_Get_Column_Type(p_result_set, 1);
+  assert(type_0 == DEITA_COLUMN_TYPE_INTEGER);
+  assert(type_1 == DEITA_COLUMN_TYPE_TEXT);
+
+  Deita_Result_Set_Free(p_result_set);
+  printf("  PASSED: Column metadata correct\n\n");
+
+  // Cleanup
+  printf("Cleanup: Closing connection and freeing arena...\n");
+  Dowa_Arena_Free(p_arena);
+  Deita_Connection_Close(p_connection);
+  unlink(test_db_path);
+
+  printf("\n=== All tests passed! ===\n");
+  return 0;
+}
--- a/raylib_examples/BUILD	Wed Dec 31 11:17:24 2025 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-load("//third_party/raylib:raylib.bzl", "raylib_binary")
-
-raylib_binary(
-  name = "hello_world",
-  srcs = ["main.c"],
-  deps = [
-    "//dowa:dowa",
-    "//third_party/raylib:raylib",
-  ],
-)
-
--- a/raylib_examples/main.c	Wed Dec 31 11:17:24 2025 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-#include <stdio.h>
-#include "dowa/dowa.h"
-#include "third_party/raylib/include/raylib.h" 
-
-int main()
-{
-  InitWindow(800, 450, "raylib [core] example - basic window");
-  while (!WindowShouldClose())
-  {
-      BeginDrawing();
-          ClearBackground(RAYWHITE);
-          DrawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY);
-      EndDrawing();
-  }
-  CloseWindow();
-  return 0;
-}