Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions docs/api/v4.0-api-baseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# (parameter names dropped) and struct/union/enum layouts in member order.
# `make api-check` (CI) fails when the headers no longer match this file.

== functions (766) ==
== functions (770) ==
cwist_accept_socket
cwist_alloc
cwist_alloc_array
Expand Down Expand Up @@ -561,6 +561,10 @@ cwist_orm_insert_returning_llong
cwist_orm_insert_returning_long
cwist_orm_open_socket
cwist_orm_query
cwist_orm_query_typed
cwist_orm_result_cell
cwist_orm_result_column_index
cwist_orm_result_free
cwist_orm_rollback
cwist_orm_select
cwist_orm_select_one_double
Expand Down Expand Up @@ -772,7 +776,7 @@ cwist_zod_parse
cwist_zod_print_errors
cwist_zod_validate

== function signatures (767) ==
== function signatures (771) ==
cwist_accept_socket: cwist_error_t (int, struct sockaddr *, void (*)(int client_fd, void *ctx), void *)
cwist_alloc: void * (size_t)
cwist_alloc_array: void * (size_t, size_t)
Expand Down Expand Up @@ -1330,6 +1334,10 @@ cwist_orm_insert_returning_llong: cwist_error_t (cwist_orm_t *, const char *, co
cwist_orm_insert_returning_long: cwist_error_t (cwist_orm_t *, const char *, const cJSON *, const char *, long *)
cwist_orm_open_socket: cwist_orm_t * (int)
cwist_orm_query: cwist_error_t (cwist_orm_t *, const char *, cJSON **)
cwist_orm_query_typed: cwist_error_t (cwist_orm_t *, const char *, cwist_orm_result_t **)
cwist_orm_result_cell: const cwist_orm_value_t * (const cwist_orm_result_t *, size_t, size_t)
cwist_orm_result_column_index: size_t (const cwist_orm_result_t *, const char *)
cwist_orm_result_free: void (cwist_orm_result_t *)
cwist_orm_rollback: cwist_error_t (cwist_orm_t *)
cwist_orm_select: cwist_error_t (cwist_orm_t *, const char *, const char *, const char *, cJSON **)
cwist_orm_select_one_double: cwist_error_t (cwist_orm_t *, const char *, const char *, const char *, double *)
Expand Down Expand Up @@ -1565,7 +1573,7 @@ cwist_wasm_component_dispose: void (void *)
cwist_wasm_dispatch_memory: const char * (cwist_app *, const char *, size_t, size_t *)
cwist_wasm_free: void (void *)

== types (struct/union/enum names) (150) ==
== types (struct/union/enum names) (153) ==
__natsConnection
__prim_cwist_error_t
bdr_blob_t
Expand Down Expand Up @@ -1672,6 +1680,9 @@ cwist_nats
cwist_nuke_db_t
cwist_orm
cwist_orm_dialect
cwist_orm_result
cwist_orm_value
cwist_orm_value_type
cwist_pb_field
cwist_pb_reader
cwist_pb_wire_type
Expand Down Expand Up @@ -1717,7 +1728,7 @@ cwist_zod_result
quic_conn_fc
quic_stream_fc

== typedefs (201) ==
== typedefs (204) ==
__prim_cwist_error_t
bdr_blob_t
bdr_entry_t
Expand Down Expand Up @@ -1853,7 +1864,10 @@ cwist_nats_msg_cb
cwist_nats_t
cwist_nuke_db_t
cwist_orm_dialect_t
cwist_orm_result_t
cwist_orm_t
cwist_orm_value_t
cwist_orm_value_type_t
cwist_pb_field
cwist_pb_reader
cwist_pb_wire_type_t
Expand Down Expand Up @@ -1920,7 +1934,7 @@ quic_conn_fc_t
quic_net_quality_t
quic_stream_fc_t

== enum constants (224) ==
== enum constants (229) ==
CWIST_ASYNC_CLOSE
CWIST_ASYNC_DEFER
CWIST_ASYNC_DETACH
Expand Down Expand Up @@ -2123,6 +2137,11 @@ CWIST_ORM_MARIADB
CWIST_ORM_MYSQL
CWIST_ORM_POSTGRES
CWIST_ORM_SQLITE
CWIST_ORM_TYPE_BLOB
CWIST_ORM_TYPE_INTEGER
CWIST_ORM_TYPE_NULL
CWIST_ORM_TYPE_REAL
CWIST_ORM_TYPE_TEXT
CWIST_PB_32BIT
CWIST_PB_64BIT
CWIST_PB_LEN
Expand Down
115 changes: 114 additions & 1 deletion include/cwist/core/orm/orm.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,10 @@ cwist_error_t cwist_orm_exec(cwist_orm_t *orm, const char *sql);
* @brief Execute a SELECT query and return the result set.
*
* The returned cJSON object is an array. Each element is an object
* whose keys are column names and whose values are the row data.
* whose keys are column names and whose values are the row data. Values
* keep the legacy text-based typing (canonical integer text becomes a
* number); use cwist_orm_query_typed() for values typed by the column's
* SQLite storage class.
*
* @param orm ORM session.
* @param sql Null-terminated SELECT statement.
Expand Down Expand Up @@ -261,6 +264,116 @@ cwist_error_t cwist_orm_insert_returning_llong(cwist_orm_t *orm, const char *tab
long long *: cwist_orm_insert_returning_llong, \
cJSON **: cwist_orm_insert_returning_json)((orm), (table), (data), (returning_col), (out))

/* ------------------------------------------------------------------ */
/* Column-typed result sets (#354) */
/* ------------------------------------------------------------------ */

/**
* @brief Storage class of a typed result cell.
*
* The class comes from the SQLite value's storage class
* (sqlite3_column_type()), not from its text: a TEXT column holding
* canonical integer text is CWIST_ORM_TYPE_TEXT, and SQL NULL is
* CWIST_ORM_TYPE_NULL, distinct from an empty TEXT value.
*/
typedef enum cwist_orm_value_type {
CWIST_ORM_TYPE_NULL = 0, /**< SQL NULL */
CWIST_ORM_TYPE_INTEGER, /**< INTEGER, exact through the full int64 range */
CWIST_ORM_TYPE_REAL, /**< REAL as a double */
CWIST_ORM_TYPE_TEXT, /**< TEXT as a null-terminated string */
CWIST_ORM_TYPE_BLOB, /**< BLOB as (blob, blob_len) bytes */
} cwist_orm_value_type_t;

/**
* @brief One cell of a column-typed result set.
*
* Exactly one payload field is meaningful per cwist_orm_value_type_t:
* i64 for CWIST_ORM_TYPE_INTEGER (exact above 2^53), real for
* CWIST_ORM_TYPE_REAL, text for CWIST_ORM_TYPE_TEXT (never NULL), and
* (blob, blob_len) for CWIST_ORM_TYPE_BLOB (blob is NULL when the BLOB
* is empty). Owned by the result set; released by
* cwist_orm_result_free().
*/
typedef struct cwist_orm_value {
cwist_orm_value_type_t type;
int64_t i64; /**< CWIST_ORM_TYPE_INTEGER */
double real; /**< CWIST_ORM_TYPE_REAL */
char *text; /**< CWIST_ORM_TYPE_TEXT */
unsigned char *blob; /**< CWIST_ORM_TYPE_BLOB (NULL when empty) */
size_t blob_len; /**< CWIST_ORM_TYPE_BLOB */
} cwist_orm_value_t;

/**
* @brief Column-typed result set produced by cwist_orm_query_typed().
*
* Cells are stored row-major: cell (row, column) lives at
* cells[row * column_count + column]. Column names come from the
* result-producing statement. Everything is released by
* cwist_orm_result_free().
*/
typedef struct cwist_orm_result {
size_t column_count;
size_t row_count;
char **columns;
cwist_orm_value_t *cells;
} cwist_orm_result_t;

/**
* @brief Execute a SELECT-style query and return column-typed rows.
*
* Values are typed by each cell's SQLite storage class, not by its
* text, unlike cwist_orm_query() whose JSON values keep the legacy
* text-based typing (canonical integer text becomes a number there).
* Here a TEXT cell stays TEXT even when it holds digits, INTEGER keeps
* every digit above 2^53, and SQL NULL is a distinct type from an
* empty TEXT value.
*
* @param orm ORM session.
* @param sql Null-terminated SELECT statement.
* @param result [out] Receives a heap-allocated cwist_orm_result_t that
* the caller must release with cwist_orm_result_free().
* On failure the pointer is set to @c NULL.
* @return cwist_error_t with err_i16 == 0 on success;
* CWIST_ERROR_INVALID_PARAM for NULL arguments,
* CWIST_ERROR_IO on transport failure, CWIST_ERROR_PROTOCOL on
* a backend error or a response without typed column metadata.
*/
cwist_error_t cwist_orm_query_typed(cwist_orm_t *orm, const char *sql,
cwist_orm_result_t **result);

/**
* @brief Release a result set from cwist_orm_query_typed().
*
* Safe to call with @c NULL.
*
* @param result Result set to free.
*/
void cwist_orm_result_free(cwist_orm_result_t *result);

/**
* @brief Find a column index by name in a typed result set.
*
* @param result Result set.
* @param column Column name.
* @return The column index, or @p result->column_count when the name is
* not present (or either argument is NULL).
*/
size_t cwist_orm_result_column_index(const cwist_orm_result_t *result,
const char *column);

/**
* @brief Read one cell of a typed result set.
*
* @param result Result set.
* @param row Row index.
* @param column Column index.
* @return Pointer to the cell, or @c NULL when the coordinates are out
* of range or @p result is NULL. The pointer is owned by the
* result set and stays valid until cwist_orm_result_free().
*/
const cwist_orm_value_t *cwist_orm_result_cell(const cwist_orm_result_t *result,
size_t row, size_t column);

/* ------------------------------------------------------------------ */
/* _Generic type-dispatched SELECT single scalar */
/* ------------------------------------------------------------------ */
Expand Down
24 changes: 22 additions & 2 deletions scripts/ci/malloc-baseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
1 src/core/mem/arena.c:tls = (cwist_arena_tls_t *)calloc(1, sizeof(cwist_arena_tls_t));
1 src/core/mem/gc.c:cwist_conn_entry_t *heap = (cwist_conn_entry_t *)calloc(want, sizeof(*heap));
1 src/core/mem/gc.c:cwist_conn_entry_t *slots = (cwist_conn_entry_t *)calloc(new_cap, sizeof(*slots));
1 src/core/mem/gc.c:cwist_reg_entry_t *slots = (cwist_reg_entry_t *)calloc(new_cap, sizeof(*slots));
1 src/core/mem/gc.c:free(g_conn_slots);
1 src/core/mem/gc.c:free(g_reg_slots);
1 src/core/mem/gc.c:free(heap);
2 src/core/mem/gc.c:free(owner);
1 src/core/mem/gc.c:free(pending);
Expand All @@ -43,6 +45,7 @@
1 src/core/orm/orm.c:char *values = (char *)malloc(1);
1 src/core/orm/orm.c:cwist_orm_t *orm = (cwist_orm_t *)calloc(1, sizeof(*orm));
1 src/core/orm/orm.c:default: return strdup(id);
1 src/core/orm/orm.c:for (size_t i = 0; i < result->column_count; i++) free(result->columns[i]);
3 src/core/orm/orm.c:free(base);
7 src/core/orm/orm.c:free(columns);
1 src/core/orm/orm.c:free(commit_payload);
Expand All @@ -51,26 +54,43 @@
1 src/core/orm/orm.c:free(final);
7 src/core/orm/orm.c:free(literal);
1 src/core/orm/orm.c:free(orm);
4 src/core/orm/orm.c:free(payload);
2 src/core/orm/orm.c:free(out);
5 src/core/orm/orm.c:free(payload);
3 src/core/orm/orm.c:free(qcol);
7 src/core/orm/orm.c:free(qid);
2 src/core/orm/orm.c:free(qret);
17 src/core/orm/orm.c:free(qtable);
1 src/core/orm/orm.c:free(raw);
1 src/core/orm/orm.c:free(result);
1 src/core/orm/orm.c:free(result->cells);
1 src/core/orm/orm.c:free(result->cells[i].blob);
1 src/core/orm/orm.c:free(result->cells[i].text);
1 src/core/orm/orm.c:free(result->columns);
6 src/core/orm/orm.c:free(set_clause);
8 src/core/orm/orm.c:free(sql);
7 src/core/orm/orm.c:free(values);
1 src/core/orm/orm.c:if (!len) free(bytes);
1 src/core/orm/orm.c:if (!node) return strdup("NULL");
1 src/core/orm/orm.c:if (!node->valuestring) return strdup("''");
1 src/core/orm/orm.c:if (!raw) return strdup("NULL");
1 src/core/orm/orm.c:res = (cwist_orm_result_t *)calloc(1, sizeof(*res));
1 src/core/orm/orm.c:res->cells = (cwist_orm_value_t *)calloc(
1 src/core/orm/orm.c:res->columns = (char **)calloc(res->column_count ? res->column_count : 1, sizeof(char *));
1 src/core/orm/orm.c:res->columns[i] = strdup(name->valuestring);
1 src/core/orm/orm.c:return strdup("NULL");
1 src/core/orm/orm.c:return strdup(buf);
1 src/core/orm/orm.c:return strdup(cJSON_IsTrue(node) ? "1" : "0");
1 src/core/orm/orm.c:return strdup(cJSON_IsTrue(node) ? "TRUE" : "FALSE");
1 src/core/orm/orm.c:unsigned char *out = (unsigned char *)malloc(cap ? cap : 1);
1 src/core/orm/orm.c:v->text = strdup(payload->valuestring);
1 src/core/orm/orm_socket.c:(cwist_orm_worker_ctx_t *)calloc(1, sizeof(*ctx));
1 src/core/orm/orm_socket.c:char *out = (char *)malloc(out_cap);
1 src/core/orm/orm_socket.c:ctx->db_path = strdup(db_path);
2 src/core/orm/orm_socket.c:free(ctx);
1 src/core/orm/orm_socket.c:free(ctx->db_path);
1 src/core/orm/orm_socket.c:free(errmsg);
1 src/core/orm/orm_socket.c:free(out);
1 src/core/orm/orm_socket.c:if (rc != SQLITE_OK && errmsg) *errmsg = strdup(sqlite3_errmsg(db));
1 src/core/utils/json_heal.c:free(r->json);
1 src/core/utils/json_heal.c:free(recovered); /* SLLM callback uses plain malloc per contract */
9 src/net/db_sync/db_sync.c:free(blob);
Expand Down Expand Up @@ -108,10 +128,10 @@
1 src/net/http/http3.c:unsigned char *pkt_buf = malloc(65535);
1 src/net/http/http3.c:unsigned char *tmp = realloc(st->seq_buf, cap);
1 src/net/http/http3.c:unsigned char(*batch_bufs)[65535] = malloc(sizeof(*batch_bufs) * H3_RECV_BATCH);
1 src/net/http/http3_client.c:char *copy = strdup(host);
1 src/net/http/http3_client.c:char *name = malloc(name_len + 1);
1 src/net/http/http3_client.c:char *tmp = realloc(st->resp_body, new_cap);
1 src/net/http/http3_client.c:char *value = malloc(value_len + 1);
1 src/net/http/http3_client.c:char *copy = strdup(host);
1 src/net/http/http3_client.c:client->in_dgram.data = malloc(len);
1 src/net/http/http3_client.c:client->out_dgram.data = malloc(len);
1 src/net/http/http3_client.c:cwist_http3_client *client = calloc(1, sizeof(*client));
Expand Down
Loading
Loading