| 1 | #include "dbd_postgresql.h" |
| 2 | |
| 3 | #define BOOLOID 16 |
| 4 | #define INT2OID 21 |
| 5 | #define INT4OID 23 |
| 6 | #define INT8OID 20 |
| 7 | #define FLOAT4OID 700 |
| 8 | #define FLOAT8OID 701 |
| 9 | |
| 10 | static lua_push_type_t postgresql_to_lua_push(unsigned int postgresql_type) { |
| 11 | lua_push_type_t lua_type; |
| 12 | |
| 13 | switch(postgresql_type) { |
| 14 | case INT2OID: |
| 15 | case INT4OID: |
| 16 | case INT8OID: |
| 17 | lua_type = LUA_PUSH_INTEGER; |
| 18 | break; |
| 19 | |
| 20 | case FLOAT4OID: |
| 21 | case FLOAT8OID: |
| 22 | lua_type = LUA_PUSH_NUMBER; |
| 23 | break; |
| 24 | |
| 25 | case BOOLOID: |
| 26 | lua_type = LUA_PUSH_BOOLEAN; |
| 27 | break; |
| 28 | |
| 29 | default: |
| 30 | lua_type = LUA_PUSH_STRING; |
| 31 | } |
| 32 | |
| 33 | return lua_type; |
| 34 | } |
| 35 | |
| 36 | static int deallocate(statement_t *statement) { |
| 37 | char command[IDLEN+13]; |
| 38 | PGresult *result; |
| 39 | ExecStatusType status; |
| 40 | |
| 41 | snprintf(command, IDLEN+13, "DEALLOCATE \"%s\"", statement->name); |
| 42 | result = PQexec(statement->postgresql, command); |
| 43 | |
| 44 | if (!result) |
| 45 | return 1; |
| 46 | |
| 47 | status = PQresultStatus(result); |
| 48 | PQclear(result); |
| 49 | |
| 50 | if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) |
| 51 | return 1; |
| 52 | |
| 53 | return 0; |
| 54 | } |
| 55 | |
| 56 | |
| 57 | /* |
| 58 | * num_affected_rows = statement:affected() |
| 59 | */ |
| 60 | static int statement_affected(lua_State *L) { |
| 61 | statement_t *statement = (statement_t *)luaL_checkudata(L, 1, DBD_POSTGRESQL_STATEMENT); |
| 62 | |
| 63 | if (!statement->result) { |
| 64 | luaL_error(L, DBI_ERR_INVALID_STATEMENT); |
| 65 | } |
| 66 | |
| 67 | lua_pushinteger(L, atoi(PQcmdTuples(statement->result))); |
| 68 | |
| 69 | return 1; |
| 70 | } |
| 71 | |
| 72 | /* |
| 73 | * success = statement:close() |
| 74 | */ |
| 75 | static int statement_close(lua_State *L) { |
| 76 | statement_t *statement = (statement_t *)luaL_checkudata(L, 1, DBD_POSTGRESQL_STATEMENT); |
| 77 | |
| 78 | if (statement->result) { |
| 79 | /* |
| 80 | * Deallocate prepared statement on the |
| 81 | * server side |
| 82 | */ |
| 83 | deallocate(statement); |
| 84 | |
| 85 | PQclear(statement->result); |
| 86 | statement->result = NULL; |
| 87 | } |
| 88 | |
| 89 | return 0; |
| 90 | } |
| 91 | |
| 92 | /* |
| 93 | * column_names = statement:columns() |
| 94 | */ |
| 95 | static int statement_columns(lua_State *L) { |
| 96 | statement_t *statement = (statement_t *)luaL_checkudata(L, 1, DBD_POSTGRESQL_STATEMENT); |
| 97 | |
| 98 | int i; |
| 99 | int num_columns; |
| 100 | int d = 1; |
| 101 | |
| 102 | if (!statement->result) { |
| 103 | luaL_error(L, DBI_ERR_INVALID_STATEMENT); |
| 104 | return 0; |
| 105 | } |
| 106 | |
| 107 | num_columns = PQnfields(statement->result); |
| 108 | lua_newtable(L); |
| 109 | for (i = 0; i < num_columns; i++) { |
| 110 | const char *name = PQfname(statement->result, i); |
| 111 | |
| 112 | LUA_PUSH_ARRAY_STRING(d, name); |
| 113 | } |
| 114 | |
| 115 | return 1; |
| 116 | } |
| 117 | |
| 118 | /* |
| 119 | * success = statement:execute(...) |
| 120 | */ |
| 121 | static int statement_execute(lua_State *L) { |
| 122 | int n = lua_gettop(L); |
| 123 | statement_t *statement = (statement_t *)luaL_checkudata(L, 1, DBD_POSTGRESQL_STATEMENT); |
| 124 | int num_bind_params = n - 1; |
| 125 | ExecStatusType status; |
| 126 | int p; |
| 127 | const char *errstr = NULL; |
| 128 | |
| 129 | const char **params; |
| 130 | PGresult *result = NULL; |
| 131 | |
| 132 | statement->tuple = 0; |
| 133 | |
| 134 | params = malloc(num_bind_params * sizeof(params)); |
| 135 | memset(params, 0, num_bind_params * sizeof(params)); |
| 136 | |
| 137 | /* |
| 138 | * convert and copy parameters into a string array |
| 139 | */ |
| 140 | for (p = 2; p <= n; p++) { |
| 141 | int i = p - 2; |
| 142 | int type = lua_type(L, p); |
| 143 | char err[64]; |
| 144 | |
| 145 | switch(type) { |
| 146 | case LUA_TNIL: |
| 147 | params[i] = NULL; |
| 148 | break; |
| 149 | case LUA_TBOOLEAN: |
| 150 | /* |
| 151 | * boolean values in postgresql can either be |
| 152 | * t/f or 1/0. Pass integer values rather than |
| 153 | * strings to maintain semantic compatibility |
| 154 | * with other DBD drivers that pass booleans |
| 155 | * as integers. |
| 156 | */ |
| 157 | params[i] = lua_toboolean(L, p) ? "1" : "0"; |
| 158 | break; |
| 159 | case LUA_TNUMBER: |
| 160 | case LUA_TSTRING: |
| 161 | params[i] = lua_tostring(L, p); |
| 162 | break; |
| 163 | default: |
| 164 | snprintf(err, sizeof(err)-1, DBI_ERR_BINDING_TYPE_ERR, lua_typename(L, type)); |
| 165 | errstr = err; |
| 166 | goto cleanup; |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | result = PQexecPrepared( |
| 171 | statement->postgresql, |
| 172 | statement->name, |
| 173 | num_bind_params, |
| 174 | (const char **)params, |
| 175 | NULL, |
| 176 | NULL, |
| 177 | 0 |
| 178 | ); |
| 179 | |
| 180 | cleanup: |
| 181 | free(params); |
| 182 | |
| 183 | if (errstr) { |
| 184 | lua_pushboolean(L, 0); |
| 185 | lua_pushfstring(L, DBI_ERR_BINDING_PARAMS, errstr); |
| 186 | return 2; |
| 187 | } |
| 188 | |
| 189 | if (!result) { |
| 190 | lua_pushboolean(L, 0); |
| 191 | lua_pushfstring(L, DBI_ERR_ALLOC_RESULT, PQerrorMessage(statement->postgresql)); |
| 192 | return 2; |
| 193 | } |
| 194 | |
| 195 | status = PQresultStatus(result); |
| 196 | if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { |
| 197 | lua_pushboolean(L, 0); |
| 198 | lua_pushfstring(L, DBI_ERR_BINDING_EXEC, PQresultErrorMessage(result)); |
| 199 | return 2; |
| 200 | } |
| 201 | |
| 202 | statement->result = result; |
| 203 | |
| 204 | lua_pushboolean(L, 1); |
| 205 | return 1; |
| 206 | } |
| 207 | |
| 208 | /* |
| 209 | * can only be called after an execute |
| 210 | */ |
| 211 | static int statement_fetch_impl(lua_State *L, statement_t *statement, int named_columns) { |
| 212 | int tuple = statement->tuple++; |
| 213 | int i; |
| 214 | int num_columns; |
| 215 | int d = 1; |
| 216 | |
| 217 | if (!statement->result) { |
| 218 | luaL_error(L, DBI_ERR_FETCH_INVALID); |
| 219 | return 0; |
| 220 | } |
| 221 | |
| 222 | if (PQresultStatus(statement->result) != PGRES_TUPLES_OK) { |
| 223 | lua_pushnil(L); |
| 224 | return 1; |
| 225 | } |
| 226 | |
| 227 | if (tuple >= PQntuples(statement->result)) { |
| 228 | lua_pushnil(L); /* no more results */ |
| 229 | return 1; |
| 230 | } |
| 231 | |
| 232 | num_columns = PQnfields(statement->result); |
| 233 | lua_newtable(L); |
| 234 | for (i = 0; i < num_columns; i++) { |
| 235 | const char *name = PQfname(statement->result, i); |
| 236 | |
| 237 | if (PQgetisnull(statement->result, tuple, i)) { |
| 238 | if (named_columns) { |
| 239 | LUA_PUSH_ATTRIB_NIL(name); |
| 240 | } else { |
| 241 | LUA_PUSH_ARRAY_NIL(d); |
| 242 | } |
| 243 | } else { |
| 244 | const char *value = PQgetvalue(statement->result, tuple, i); |
| 245 | lua_push_type_t lua_push = postgresql_to_lua_push(PQftype(statement->result, i)); |
| 246 | |
| 247 | /* |
| 248 | * data is returned as strings from PSQL |
| 249 | * convert them here into Lua types |
| 250 | */ |
| 251 | |
| 252 | if (lua_push == LUA_PUSH_NIL) { |
| 253 | if (named_columns) { |
| 254 | LUA_PUSH_ATTRIB_NIL(name); |
| 255 | } else { |
| 256 | LUA_PUSH_ARRAY_NIL(d); |
| 257 | } |
| 258 | } else if (lua_push == LUA_PUSH_INTEGER) { |
| 259 | int val = atoi(value); |
| 260 | |
| 261 | if (named_columns) { |
| 262 | LUA_PUSH_ATTRIB_INT(name, val); |
| 263 | } else { |
| 264 | LUA_PUSH_ARRAY_INT(d, val); |
| 265 | } |
| 266 | } else if (lua_push == LUA_PUSH_NUMBER) { |
| 267 | double val = strtod(value, NULL); |
| 268 | |
| 269 | if (named_columns) { |
| 270 | LUA_PUSH_ATTRIB_FLOAT(name, val); |
| 271 | } else { |
| 272 | LUA_PUSH_ARRAY_FLOAT(d, val); |
| 273 | } |
| 274 | } else if (lua_push == LUA_PUSH_STRING) { |
| 275 | if (named_columns) { |
| 276 | LUA_PUSH_ATTRIB_STRING(name, value); |
| 277 | } else { |
| 278 | LUA_PUSH_ARRAY_STRING(d, value); |
| 279 | } |
| 280 | } else if (lua_push == LUA_PUSH_BOOLEAN) { |
| 281 | /* |
| 282 | * booleans are returned as a string |
| 283 | * either 't' or 'f' |
| 284 | */ |
| 285 | int val = value[0] == 't' ? 1 : 0; |
| 286 | |
| 287 | if (named_columns) { |
| 288 | LUA_PUSH_ATTRIB_BOOL(name, val); |
| 289 | } else { |
| 290 | LUA_PUSH_ARRAY_BOOL(d, val); |
| 291 | } |
| 292 | } else { |
| 293 | luaL_error(L, DBI_ERR_UNKNOWN_PUSH); |
| 294 | } |
| 295 | } |
| 296 | } |
| 297 | |
| 298 | return 1; |
| 299 | } |
| 300 | |
| 301 | |
| 302 | static int next_iterator(lua_State *L) { |
| 303 | statement_t *statement = (statement_t *)luaL_checkudata(L, lua_upvalueindex(1), DBD_POSTGRESQL_STATEMENT); |
| 304 | int named_columns = lua_toboolean(L, lua_upvalueindex(2)); |
| 305 | |
| 306 | return statement_fetch_impl(L, statement, named_columns); |
| 307 | } |
| 308 | |
| 309 | /* |
| 310 | * table = statement:fetch(named_indexes) |
| 311 | */ |
| 312 | static int statement_fetch(lua_State *L) { |
| 313 | statement_t *statement = (statement_t *)luaL_checkudata(L, 1, DBD_POSTGRESQL_STATEMENT); |
| 314 | int named_columns = lua_toboolean(L, 2); |
| 315 | |
| 316 | return statement_fetch_impl(L, statement, named_columns); |
| 317 | } |
| 318 | |
| 319 | /* |
| 320 | * num_rows = statement:rowcount() |
| 321 | */ |
| 322 | static int statement_rowcount(lua_State *L) { |
| 323 | statement_t *statement = (statement_t *)luaL_checkudata(L, 1, DBD_POSTGRESQL_STATEMENT); |
| 324 | |
| 325 | if (!statement->result) { |
| 326 | luaL_error(L, DBI_ERR_INVALID_STATEMENT); |
| 327 | } |
| 328 | |
| 329 | lua_pushinteger(L, PQntuples(statement->result)); |
| 330 | |
| 331 | return 1; |
| 332 | } |
| 333 | |
| 334 | /* |
| 335 | * iterfunc = statement:rows(named_indexes) |
| 336 | */ |
| 337 | static int statement_rows(lua_State *L) { |
| 338 | if (lua_gettop(L) == 1) { |
| 339 | lua_pushvalue(L, 1); |
| 340 | lua_pushboolean(L, 0); |
| 341 | } else { |
| 342 | lua_pushvalue(L, 1); |
| 343 | lua_pushboolean(L, lua_toboolean(L, 2)); |
| 344 | } |
| 345 | |
| 346 | lua_pushcclosure(L, next_iterator, 2); |
| 347 | return 1; |
| 348 | } |
| 349 | |
| 350 | /* |
| 351 | * __gc |
| 352 | */ |
| 353 | static int statement_gc(lua_State *L) { |
| 354 | /* always free the handle */ |
| 355 | statement_close(L); |
| 356 | |
| 357 | return 0; |
| 358 | } |
| 359 | |
| 360 | /* |
| 361 | * __tostring |
| 362 | */ |
| 363 | static int statement_tostring(lua_State *L) { |
| 364 | statement_t *statement = (statement_t *)luaL_checkudata(L, 1, DBD_POSTGRESQL_STATEMENT); |
| 365 | |
| 366 | lua_pushfstring(L, "%s: %p", DBD_POSTGRESQL_STATEMENT, statement); |
| 367 | |
| 368 | return 1; |
| 369 | } |
| 370 | |
| 371 | int dbd_postgresql_statement_create(lua_State *L, connection_t *conn, const char *sql_query) { |
| 372 | statement_t *statement = NULL; |
| 373 | ExecStatusType status; |
| 374 | PGresult *result = NULL; |
| 375 | char *new_sql; |
| 376 | char name[IDLEN]; |
| 377 | |
| 378 | /* |
| 379 | * convert SQL string into a PSQL API compatible SQL statement |
| 380 | */ |
| 381 | new_sql = replace_placeholders(L, '$', sql_query); |
| 382 | |
| 383 | snprintf(name, IDLEN, "dbd-postgresql-%017u", ++conn->statement_id); |
| 384 | |
| 385 | result = PQprepare(conn->postgresql, name, new_sql, 0, NULL); |
| 386 | |
| 387 | /* |
| 388 | * free converted statement after use |
| 389 | */ |
| 390 | free(new_sql); |
| 391 | |
| 392 | if (!result) { |
| 393 | lua_pushnil(L); |
| 394 | lua_pushfstring(L, DBI_ERR_ALLOC_STATEMENT, PQerrorMessage(statement->postgresql)); |
| 395 | return 2; |
| 396 | } |
| 397 | |
| 398 | status = PQresultStatus(result); |
| 399 | if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { |
| 400 | const char *err_string = PQresultErrorMessage(result); |
| 401 | PQclear(result); |
| 402 | |
| 403 | lua_pushnil(L); |
| 404 | lua_pushfstring(L, DBI_ERR_PREP_STATEMENT, err_string); |
| 405 | return 2; |
| 406 | } |
| 407 | |
| 408 | PQclear(result); |
| 409 | |
| 410 | statement = (statement_t *)lua_newuserdata(L, sizeof(statement_t)); |
| 411 | statement->postgresql = conn->postgresql; |
| 412 | statement->result = NULL; |
| 413 | statement->tuple = 0; |
| 414 | strncpy(statement->name, name, IDLEN-1); |
| 415 | statement->name[IDLEN-1] = '\0'; |
| 416 | |
| 417 | luaL_getmetatable(L, DBD_POSTGRESQL_STATEMENT); |
| 418 | lua_setmetatable(L, -2); |
| 419 | |
| 420 | return 1; |
| 421 | } |
| 422 | |
| 423 | int dbd_postgresql_statement(lua_State *L) { |
| 424 | static const luaL_Reg statement_methods[] = { |
| 425 | {"affected", statement_affected}, |
| 426 | {"close", statement_close}, |
| 427 | {"columns", statement_columns}, |
| 428 | {"execute", statement_execute}, |
| 429 | {"fetch", statement_fetch}, |
| 430 | {"rowcount", statement_rowcount}, |
| 431 | {"rows", statement_rows}, |
| 432 | {NULL, NULL} |
| 433 | }; |
| 434 | |
| 435 | static const luaL_Reg statement_class_methods[] = { |
| 436 | {NULL, NULL} |
| 437 | }; |
| 438 | |
| 439 | luaL_newmetatable(L, DBD_POSTGRESQL_STATEMENT); |
| 440 | luaL_register(L, 0, statement_methods); |
| 441 | lua_pushvalue(L,-1); |
| 442 | lua_setfield(L, -2, "__index"); |
| 443 | |
| 444 | lua_pushcfunction(L, statement_gc); |
| 445 | lua_setfield(L, -2, "__gc"); |
| 446 | |
| 447 | lua_pushcfunction(L, statement_tostring); |
| 448 | lua_setfield(L, -2, "__tostring"); |
| 449 | |
| 450 | luaL_register(L, DBD_POSTGRESQL_STATEMENT, statement_class_methods); |
| 451 | |
| 452 | return 1; |
| 453 | } |