22#include <sys/socket.h>
40static volatile bool g_immediate_commit =
false;
50static int orm_recv_all(
int fd,
void *buf,
size_t n) {
52 char *
p = (
char *)buf;
55 if (r <= 0)
return -1;
65static int orm_send_all(
int fd,
const void *buf,
size_t n) {
67 const char *
p = (
const char *)buf;
70 if (
w <= 0)
return -1;
90 if (!
orm ||
orm->sock_fd < 0 || !
sql)
return -1;
102 if (payload_len > 64 * 1024 * 1024)
return -1;
104 char *payload = (
char *)
malloc(payload_len + 1);
105 if (!payload)
return -1;
106 if (payload_len > 0 && orm_recv_all(
orm->sock_fd, payload, payload_len) != 0) {
110 payload[payload_len] =
'\0';
121static int orm_parse_status(
const char *payload) {
122 if (!payload)
return -1;
124 if (!
root)
return -1;
150 orm->auto_commit = g_immediate_commit;
165 if (
orm->sock_fd >= 0) {
189 g_immediate_commit =
enable;
202 if (
orm)
orm->dialect = dialect;
248 char *payload =
NULL;
249 if (orm_exchange(
orm,
sql, &payload) != 0) {
254 int status = orm_parse_status(payload);
263 if (g_immediate_commit) {
293 if (result) *result =
NULL;
294 if (!
orm || !
sql || !result) {
299 char *payload =
NULL;
300 if (orm_exchange(
orm,
sql, &payload) != 0) {
343static char *cwist_orm_escape_sqlite(
const char *src) {
344 if (!src)
return NULL;
347 for (
size_t i = 0;
i < len;
i++) {
348 if (src[
i] ==
'\'')
extra++;
353 for (
size_t i = 0;
i < len;
i++) {
354 if (src[
i] ==
'\'')
dst[
j++] =
'\'';
370static char *cwist_orm_quote_identifier(
const cwist_orm_t *
orm,
const char *
id) {
374 switch (
orm->dialect) {
385 default:
return strdup(
id);
390 for (
size_t i = 0;
i < len;
i++) {
394 if (!out)
return NULL;
397 for (
size_t i = 0;
i < len;
i++) {
419 if (!node)
return strdup(
"NULL");
432 snprintf(buf,
sizeof(buf),
"%g", node->valuedouble);
436 if (!node->valuestring)
return strdup(
"''");
437 char *
escaped = cwist_orm_escape_sqlite(node->valuestring);
455 if (!raw)
return strdup(
"NULL");
456 char *
escaped = cwist_orm_escape_sqlite(raw);
493 char *columns = (
char *)
malloc(1);
495 if (!columns || !
values) {
513 char *
qid = cwist_orm_quote_identifier(
orm,
item->string);
593 char *
sql = orm_build_insert_sql(
orm,
table, data);
607 final[len + 1] =
'\0';
655 char *
qid = cwist_orm_quote_identifier(
orm,
item->string);
820 char *base = orm_build_insert_sql(
orm,
table, data);
821 if (!base)
return NULL;
830 switch (
orm->dialect) {
835 default:
fmt =
"%s; SELECT last_insert_rowid() AS %s;";
break;
876 if (out) *out =
NULL;
891 if (
err.error.err_i16 != 0) {
916 err.error.err_i16 = 0;
936 if (
err.error.err_i16 != 0)
return err;
938 *out = (
int)
val->valuedouble;
963 if (
err.error.err_i16 != 0)
return err;
990 if (
err.error.err_i16 != 0)
return err;
992 *out = (
long long)
val->valuedouble;
1056 if (out) *out =
NULL;
1071 if (
err.error.err_i16 != 0) {
1096 err.error.err_i16 = 0;
1116 if (
err.error.err_i16 != 0)
return err;
1118 *out = (
int)
val->valuedouble;
1142 if (
err.error.err_i16 != 0)
return err;
1144 *out = (
long)
val->valuedouble;
1168 if (
err.error.err_i16 != 0)
return err;
1170 *out = (
long long)
val->valuedouble;
1194 if (
err.error.err_i16 != 0)
return err;
1196 *out =
val->valuedouble;
1222 if (
err.error.err_i16 != 0)
return err;
1242static const char *orm_json_ws(
const char *
p) {
1243 while (*
p ==
' ' || *
p ==
'\t' || *
p ==
'\n' || *
p ==
'\r')
p++;
1248static const char *orm_json_skip_string(
const char *
p) {
1249 for (
p++; *
p && *
p !=
'"';
p++) {
1250 if (*
p ==
'\\' && !*++
p)
return NULL;
1252 return *
p ==
'"' ?
p + 1 :
NULL;
1256static const char *orm_json_skip_value(
const char *
p) {
1258 if (*
p ==
'"')
return orm_json_skip_string(
p);
1259 if (*
p ==
'{' || *
p ==
'[') {
1263 p = orm_json_skip_string(
p);
1264 if (!
p)
return NULL;
1267 if (*
p ==
'{' || *
p ==
'[') {
1269 }
else if (*
p ==
'}' || *
p ==
']') {
1270 if (--
depth == 0)
return p + 1;
1277 while (*
p && *
p !=
',' && *
p !=
'}' && *
p !=
']' && *
p !=
' ' && *
p !=
'\t' && *
p !=
'\n' &&
1291static const char *orm_json_object_get(
const char *
p,
const char *key) {
1294 if (*
p !=
'{')
return NULL;
1295 p = orm_json_ws(
p + 1);
1298 const char *
ke = orm_json_skip_string(
p);
1300 p = orm_json_ws(
ke);
1301 if (*
p !=
':')
return NULL;
1302 p = orm_json_ws(
p + 1);
1305 p = orm_json_skip_value(
p);
1306 if (!
p)
return NULL;
1308 if (*
p !=
',')
return NULL;
1309 p = orm_json_ws(
p + 1);
1320static bool orm_parse_exact_i64(
const char *
p,
int64_t *out) {
1324 e = orm_json_skip_string(
p);
1325 if (!
e)
return false;
1329 e = orm_json_skip_value(
p);
1330 if (!
e)
return false;
1333 size_t i = (
n > 0 && s[0] ==
'-') ? 1 : 0;
1334 if (
i >=
n ||
n > 20)
return false;
1335 if (s[
i] ==
'0' && (
n -
i > 1 ||
i == 1))
return false;
1336 for (
size_t k =
i;
k <
n;
k++) {
1337 if (s[
k] <
'0' || s[
k] >
'9')
return false;
1357 char *payload =
NULL;
1358 if (orm_exchange(
orm,
sql, &payload) != 0) {
1374 const char *rows = orm_json_object_get(payload,
"rows");
1375 const char *
row = rows && *rows ==
'[' ? orm_json_ws(rows + 1) :
NULL;
1378 if (
cell && orm_parse_exact_i64(
cell, &
v)) {
1429static int orm_b64_value(
unsigned char c) {
1430 if (
c >=
'A' &&
c <=
'Z')
return c -
'A';
1431 if (
c >=
'a' &&
c <=
'z')
return c -
'a' + 26;
1432 if (
c >=
'0' &&
c <=
'9')
return c -
'0' + 52;
1433 if (
c ==
'+')
return 62;
1434 if (
c ==
'/')
return 63;
1443static unsigned char *orm_b64_decode(
const char *
in,
size_t *out_len) {
1450 if (
n > 0 &&
in[
n - 1] ==
'=')
pad++;
1451 if (
n > 1 &&
in[
n - 2] ==
'=')
pad++;
1453 unsigned char *out = (
unsigned char *)
malloc(
cap ?
cap : 1);
1459 for (
size_t i = 0;
i <
n;
i += 4) {
1462 for (
int k = 0;
k < 4;
k++) {
1465 if (
i + 4 !=
n ||
k < 2 || (
k == 2 &&
in[
i + 3] !=
'=')) {
1473 v[
k] = orm_b64_value((
unsigned char)
c);
1502 const char *
t = tag->valuestring;
1511 const char *s = payload->valuestring;
1524 v->real = payload->valuedouble;
1530 v->text =
strdup(payload->valuestring);
1536 unsigned char *bytes = orm_b64_decode(payload->valuestring, &len);
1539 v->blob = len ? bytes :
NULL;
1541 if (!len)
free(bytes);
1567 if (result) *result =
NULL;
1568 if (!
orm || !
sql || !result) {
1573 char *payload =
NULL;
1574 if (orm_exchange(
orm,
sql, &payload) != 0) {
1638 err.error.err_i16 =
rc;
1664 if (!result)
return;
1665 if (result->
cells) {
1667 for (
size_t i = 0;
i < total;
i++) {
Memory allocation wrappers backed by libttak.
@ CWIST_ERR_INT16
used when checking common errcodes in Unix/Linux
cwist_error_t make_error(cwist_errtype_t type)
Creates an error object initialized with the specified type.
cwist_error_t cwist_orm_select_one_int(cwist_orm_t *orm, const char *table, const char *column, const char *where_clause, int *out)
Select a single column value from the first matching row as an int.
cwist_error_t cwist_orm_query(cwist_orm_t *orm, const char *sql, cJSON **result)
Execute a SELECT-style query and return the result rows.
const cwist_orm_value_t * cwist_orm_result_cell(const cwist_orm_result_t *result, size_t row, size_t column)
Read one cell of a typed result set.
cwist_error_t cwist_orm_select_one_json(cwist_orm_t *orm, const char *table, const char *column, const char *where_clause, cJSON **out)
Select a single column value from the first matching row as JSON.
cwist_error_t cwist_orm_insert_returning_i64(cwist_orm_t *orm, const char *table, const cJSON *data, const char *returning_col, int64_t *out)
Insert a row and return an integer column exactly as an int64_t.
cwist_error_t cwist_orm_select_one_double(cwist_orm_t *orm, const char *table, const char *column, const char *where_clause, double *out)
Select a single column value from the first matching row as a double.
cwist_error_t cwist_orm_select(cwist_orm_t *orm, const char *table, const char *columns, const char *where_clause, cJSON **result)
Select rows from a table.
cwist_error_t cwist_orm_insert_returning_json(cwist_orm_t *orm, const char *table, const cJSON *data, const char *returning_col, cJSON **out)
Insert a row and return a JSON value from the new row.
cwist_error_t cwist_orm_delete(cwist_orm_t *orm, const char *table, const char *where_clause)
Delete rows from a table.
void cwist_orm_use_dialect(cwist_orm_t *orm, cwist_orm_dialect_t dialect)
Select the SQL dialect used for query generation.
cwist_error_t cwist_orm_insert_returning_int(cwist_orm_t *orm, const char *table, const cJSON *data, const char *returning_col, int *out)
Insert a row and return a column value as an int.
cwist_error_t cwist_orm_insert_returning_llong(cwist_orm_t *orm, const char *table, const cJSON *data, const char *returning_col, long long *out)
Insert a row and return a column value as a long long.
cwist_orm_t * cwist_orm_open_socket(int socket_fd)
Wrap a connected socket descriptor in an ORM session.
cwist_error_t cwist_orm_select_one_llong(cwist_orm_t *orm, const char *table, const char *column, const char *where_clause, long long *out)
Select a single column value from the first matching row as a long long.
cwist_error_t cwist_orm_select_one_i64(cwist_orm_t *orm, const char *table, const char *column, const char *where_clause, int64_t *out)
Select a single integer cell exactly as an int64_t.
void cwist_orm_immediate_commit(bool enable)
Set the global immediate-commit default for new ORM sessions.
cwist_error_t cwist_orm_select_one_string(cwist_orm_t *orm, const char *table, const char *column, const char *where_clause, char **out)
Select a single column value from the first matching row as a string.
cwist_error_t cwist_orm_exec(cwist_orm_t *orm, const char *sql)
Execute a single SQL statement, discarding any result rows.
void cwist_orm_close_socket(cwist_orm_t *orm)
Close an ORM session and release its socket.
cwist_error_t cwist_orm_insert(cwist_orm_t *orm, const char *table, const cJSON *data)
Insert a JSON object into a table.
void cwist_orm_result_free(cwist_orm_result_t *result)
Release a result set from cwist_orm_query_typed().
cwist_error_t cwist_orm_commit(cwist_orm_t *orm)
Commit the current transaction.
cwist_error_t cwist_orm_update(cwist_orm_t *orm, const char *table, const cJSON *data, const char *where_clause)
Update rows of a table with values from a JSON object.
cwist_error_t cwist_orm_rollback(cwist_orm_t *orm)
Roll back the current transaction.
cwist_error_t cwist_orm_insert_returning_long(cwist_orm_t *orm, const char *table, const cJSON *data, const char *returning_col, long *out)
Insert a row and return a column value as a long.
cwist_error_t cwist_orm_query_typed(cwist_orm_t *orm, const char *sql, cwist_orm_result_t **result)
Execute a SELECT-style query and return column-typed rows.
size_t cwist_orm_result_column_index(const cwist_orm_result_t *result, const char *column)
Find a column index by name in a typed result set.
cwist_error_t cwist_orm_select_one_long(cwist_orm_t *orm, const char *table, const char *column, const char *where_clause, long *out)
Select a single column value from the first matching row as a long.
Tiny ORM over a SQL socket stream.
enum cwist_orm_dialect cwist_orm_dialect_t
Supported SQL dialects for query generation.
struct cwist_orm cwist_orm_t
Opaque ORM session handle.
Integrated error structure used throughout the system.
Column-typed result set produced by cwist_orm_query_typed().
cwist_orm_value_t * cells
One cell of a column-typed result set.
#define CWIST_ERROR_INVALID_PARAM
#define CWIST_ERROR_PROTOCOL
#define CWIST_ERROR_NOMEM