/* $%BEGINLICENSE%$
Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; version 2 of the
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA
$%ENDLICENSE%$ */
#include
#include "network-injection-lua.h"
#include "network-mysqld-proto.h"
#include "network-mysqld-packet.h"
#include "glib-ext.h"
#include "glib-ext-ref.h"
#include "lua-env.h"
#include "lua-scope.h"
#define C(x) x, sizeof(x) - 1
#define S(x) x->str, x->len
#define TIME_DIFF_US(t2, t1) \
((t2.tv_sec - t1.tv_sec) * 1000000.0 + (t2.tv_usec - t1.tv_usec))
static int proxy_resultset_lua_push_ref(lua_State *L, GRef *ref);
typedef enum {
PROXY_QUEUE_ADD_PREPEND,
PROXY_QUEUE_ADD_APPEND
} proxy_queue_add_t;
/**
* handle _append() and _prepend()
*
* _append() and _prepend() have the same behaviour, parameters, ...
* just different in position
*/
#ifdef WIN32
#pragma warning (push)
#pragma warning (disable : 4715) /* don't warn about the unreached assert at the end of this function */
#endif
static int proxy_queue_add(lua_State *L, proxy_queue_add_t type) {
GQueue *q = *(GQueue **)luaL_checkself(L);
int resp_type = luaL_checkinteger(L, 2);
size_t str_len;
const char *str = luaL_checklstring(L, 3, &str_len);
injection *inj;
GString *query = g_string_sized_new(str_len);
g_string_append_len(query, str, str_len);
inj = injection_new(resp_type, query);
inj->resultset_is_needed = FALSE;
/* check the 4th (last) param */
switch (luaL_opt(L, lua_istable, 4, -1)) {
case -1:
/* none or nil */
break;
case 1:
lua_getfield(L, 4, "resultset_is_needed");
if (lua_isnil(L, -1)) {
/* no defined */
} else if (lua_isboolean(L, -1)) {
inj->resultset_is_needed = lua_toboolean(L, -1);
} else {
switch (type) {
case PROXY_QUEUE_ADD_APPEND:
return luaL_argerror(L, 4, ":append(..., { resultset_is_needed = boolean } ), is %s");
case PROXY_QUEUE_ADD_PREPEND:
return luaL_argerror(L, 4, ":prepend(..., { resultset_is_needed = boolean } ), is %s");
}
}
lua_pop(L, 1);
break;
default:
proxy_lua_dumpstack_verbose(L);
luaL_typerror(L, 4, "table");
break;
}
switch (type) {
case PROXY_QUEUE_ADD_APPEND:
network_injection_queue_append(q, inj);
return 0;
case PROXY_QUEUE_ADD_PREPEND:
network_injection_queue_prepend(q, inj);
return 0;
}
g_assert_not_reached();
}
#ifdef WIN32
#pragma warning (pop) /* restore the pragma state from before this function */
#endif
/**
* proxy.queries:append(id, packet[, { options }])
*
* id: opaque numeric id (numeric)
* packet: mysql packet to append (string) FIXME: support table for multiple packets
* options: table of options (table)
* backend_ndx: backend_ndx to send it to (numeric)
* resultset_is_needed: expose the result-set into lua (bool)
*/
static int proxy_queue_append(lua_State *L) {
return proxy_queue_add(L, PROXY_QUEUE_ADD_APPEND);
}
static int proxy_queue_prepend(lua_State *L) {
return proxy_queue_add(L, PROXY_QUEUE_ADD_PREPEND);
}
static int proxy_queue_reset(lua_State *L) {
/* we expect 2 parameters */
GQueue *q = *(GQueue **)luaL_checkself(L);
network_injection_queue_reset(q);
return 0;
}
static int proxy_queue_len(lua_State *L) {
/* we expect 2 parameters */
GQueue *q = *(GQueue **)luaL_checkself(L);
lua_pushinteger(L, q->length);
return 1;
}
static const struct luaL_reg methods_proxy_queue[] = {
{ "prepend", proxy_queue_prepend },
{ "append", proxy_queue_append },
{ "reset", proxy_queue_reset },
{ "__len", proxy_queue_len },
{ NULL, NULL },
};
/**
* Push a metatable onto the Lua stack containing methods to
* handle the query injection queue.
*/
void proxy_getqueuemetatable(lua_State *L) {
proxy_getmetatable(L, methods_proxy_queue);
}
/**
* Free a resultset struct when the corresponding Lua userdata is garbage collected.
*/
static int proxy_resultset_gc(lua_State *L) {
GRef *ref = *(GRef **)luaL_checkself(L);
g_ref_unref(ref);
return 0;
}
static int proxy_resultset_fields_len(lua_State *L) {
GRef *ref = *(GRef **)luaL_checkself(L);
proxy_resultset_t *res = ref->udata;
GPtrArray *fields = res->fields;
lua_pushinteger(L, fields->len);
return 1;
}
static int proxy_resultset_field_get(lua_State *L) {
MYSQL_FIELD *field = *(MYSQL_FIELD **)luaL_checkself(L);
gsize keysize = 0;
const char *key = luaL_checklstring(L, 2, &keysize);
if (strleq(key, keysize, C("type"))) {
lua_pushinteger(L, field->type);
} else if (strleq(key, keysize, C("name"))) {
lua_pushstring(L, field->name);
} else if (strleq(key, keysize, C("org_name"))) {
lua_pushstring(L, field->org_name);
} else if (strleq(key, keysize, C("org_table"))) {
lua_pushstring(L, field->org_table);
} else if (strleq(key, keysize, C("table"))) {
lua_pushstring(L, field->table);
} else {
lua_pushnil(L);
}
return 1;
}
static const struct luaL_reg methods_proxy_resultset_fields_field[] = {
{ "__index", proxy_resultset_field_get },
{ NULL, NULL },
};
/**
* get a field from the result-set
*
*/
static int proxy_resultset_fields_get(lua_State *L) {
GRef *ref = *(GRef **)luaL_checkself(L);
proxy_resultset_t *res = ref->udata;
GPtrArray *fields = res->fields;
MYSQL_FIELD *field;
MYSQL_FIELD **field_p;
lua_Integer ndx = luaL_checkinteger(L, 2);
/* protect the compare */
if (fields->len > G_MAXINT) {
return 0;
}
if (ndx < 1 || ndx > (lua_Integer)fields->len) {
lua_pushnil(L);
return 1;
}
field = fields->pdata[ndx - 1]; /** lua starts at 1, C at 0 */
field_p = lua_newuserdata(L, sizeof(field));
*field_p = field;
proxy_getmetatable(L, methods_proxy_resultset_fields_field);
lua_setmetatable(L, -2);
return 1;
}
/**
* get the next row from the resultset
*
* returns a lua-table with the fields (starting at 1)
*
* @return 0 on error, 1 on success
*
*/
static int proxy_resultset_rows_iter(lua_State *L) {
GRef *ref = *(GRef **)lua_touserdata(L, lua_upvalueindex(1));
proxy_resultset_t *res = ref->udata;
network_packet packet;
GPtrArray *fields = res->fields;
gsize i;
int err = 0;
network_mysqld_lenenc_type lenenc_type;
GList *chunk = res->row;
g_return_val_if_fail(chunk != NULL, 0);
packet.data = chunk->data;
packet.offset = 0;
err = err || network_mysqld_proto_skip_network_header(&packet);
err = err || network_mysqld_proto_peek_lenenc_type(&packet, &lenenc_type);
g_return_val_if_fail(err == 0, 0); /* protocol error */
switch (lenenc_type) {
case NETWORK_MYSQLD_LENENC_TYPE_ERR:
/* a ERR packet instead of real rows
*
* like "explain select fld3 from t2 ignore index (fld3,not_existing)"
*
* see mysql-test/t/select.test
*/
case NETWORK_MYSQLD_LENENC_TYPE_EOF:
/* if we find the 2nd EOF packet we are done */
return 0;
case NETWORK_MYSQLD_LENENC_TYPE_INT:
case NETWORK_MYSQLD_LENENC_TYPE_NULL:
break;
}
lua_newtable(L);
for (i = 0; i < fields->len; i++) {
guint64 field_len;
err = err || network_mysqld_proto_peek_lenenc_type(&packet, &lenenc_type);
g_return_val_if_fail(err == 0, 0); /* protocol error */
switch (lenenc_type) {
case NETWORK_MYSQLD_LENENC_TYPE_NULL:
network_mysqld_proto_skip(&packet, 1);
lua_pushnil(L);
break;
case NETWORK_MYSQLD_LENENC_TYPE_INT:
err = err || network_mysqld_proto_get_lenenc_int(&packet, &field_len);
err = err || !(field_len <= packet.data->len); /* just to check that we don't overrun by the addition */
err = err || !(packet.offset + field_len <= packet.data->len); /* check that we have enough string-bytes for the length-encoded string */
if (err) return luaL_error(L, "%s: row-data is invalid", G_STRLOC);
lua_pushlstring(L, packet.data->str + packet.offset, field_len);
err = err || network_mysqld_proto_skip(&packet, field_len);
break;
default:
/* EOF and ERR should come up here */
err = 1;
break;
}
/* lua starts its tables at 1 */
lua_rawseti(L, -2, i + 1);
g_return_val_if_fail(err == 0, 0); /* protocol error */
}
res->row = res->row->next;
return 1;
}
/**
* parse the result-set of the query
*
* @return if this is not a result-set we return -1
*/
int parse_resultset_fields(proxy_resultset_t *res) {
GList *chunk;
g_return_val_if_fail(res->result_queue != NULL, -1);
if (res->fields) return 0;
/* parse the fields */
res->fields = network_mysqld_proto_fielddefs_new();
if (!res->fields) return -1;
chunk = network_mysqld_proto_get_fielddefs(res->result_queue->head, res->fields);
/* no result-set found */
if (!chunk) return -1;
/* skip the end-of-fields chunk */
res->rows_chunk_head = chunk->next;
return 0;
}
static int proxy_resultset_fields_gc(lua_State *L) {
GRef *ref = *(GRef **)luaL_checkself(L);
g_ref_unref(ref);
return 0;
}
static const struct luaL_reg methods_proxy_resultset_fields[] = {
{ "__index", proxy_resultset_fields_get },
{ "__len", proxy_resultset_fields_len },
{ "__gc", proxy_resultset_fields_gc },
{ NULL, NULL },
};
static int proxy_resultset_fields_lua_push_ref(lua_State *L, GRef *ref) {
GRef **ref_p;
g_ref_ref(ref);
ref_p = lua_newuserdata(L, sizeof(GRef *));
*ref_p = ref;
proxy_getmetatable(L, methods_proxy_resultset_fields);
lua_setmetatable(L, -2);
return 1;
}
static int proxy_resultset_get(lua_State *L) {
GRef *ref = *(GRef **)luaL_checkself(L);
proxy_resultset_t *res = ref->udata;
gsize keysize = 0;
const char *key = luaL_checklstring(L, 2, &keysize);
if (strleq(key, keysize, C("fields"))) {
if (!res->result_queue) {
luaL_error(L, ".resultset.fields isn't available if 'resultset_is_needed ~= true'");
} else {
if (0 != parse_resultset_fields(res)) {
/* failed */
}
if (res->fields) {
proxy_resultset_fields_lua_push_ref(L, ref);
} else {
lua_pushnil(L);
}
}
} else if (strleq(key, keysize, C("rows"))) {
if (!res->result_queue) {
luaL_error(L, ".resultset.rows isn't available if 'resultset_is_needed ~= true'");
} else if (res->qstat.binary_encoded) {
luaL_error(L, ".resultset.rows isn't available for prepared statements");
} else {
parse_resultset_fields(res); /* set up the ->rows_chunk_head pointer */
if (res->rows_chunk_head) {
res->row = res->rows_chunk_head;
proxy_resultset_lua_push_ref(L, ref);
lua_pushcclosure(L, proxy_resultset_rows_iter, 1);
} else {
lua_pushnil(L);
}
}
} else if (strleq(key, keysize, C("row_count"))) {
lua_pushinteger(L, res->rows);
} else if (strleq(key, keysize, C("bytes"))) {
lua_pushinteger(L, res->bytes);
} else if (strleq(key, keysize, C("raw"))) {
if (!res->result_queue) {
luaL_error(L, ".resultset.raw isn't available if 'resultset_is_needed ~= true'");
} else {
GString *s;
s = res->result_queue->head->data;
lua_pushlstring(L, s->str + 4, s->len - 4); /* skip the network-header */
}
} else if (strleq(key, keysize, C("flags"))) {
lua_newtable(L);
lua_pushboolean(L, (res->qstat.server_status & SERVER_STATUS_IN_TRANS) != 0);
lua_setfield(L, -2, "in_trans");
lua_pushboolean(L, (res->qstat.server_status & SERVER_STATUS_AUTOCOMMIT) != 0);
lua_setfield(L, -2, "auto_commit");
lua_pushboolean(L, (res->qstat.server_status & SERVER_QUERY_NO_GOOD_INDEX_USED) != 0);
lua_setfield(L, -2, "no_good_index_used");
lua_pushboolean(L, (res->qstat.server_status & SERVER_QUERY_NO_INDEX_USED) != 0);
lua_setfield(L, -2, "no_index_used");
} else if (strleq(key, keysize, C("warning_count"))) {
lua_pushinteger(L, res->qstat.warning_count);
} else if (strleq(key, keysize, C("affected_rows"))) {
/**
* if the query had a result-set (SELECT, ...)
* affected_rows and insert_id are not valid
*/
if (res->qstat.was_resultset) {
lua_pushnil(L);
} else {
lua_pushnumber(L, res->qstat.affected_rows);
}
} else if (strleq(key, keysize, C("insert_id"))) {
if (res->qstat.was_resultset) {
lua_pushnil(L);
} else {
lua_pushnumber(L, res->qstat.insert_id);
}
} else if (strleq(key, keysize, C("query_status"))) {
/* hmm, is there another way to figure out if this is a 'resultset' ?
* one that doesn't require the parse the meta-data */
if (res->qstat.query_status == MYSQLD_PACKET_NULL) {
lua_pushnil(L);
} else {
lua_pushinteger(L, res->qstat.query_status);
}
} else {
lua_pushnil(L);
}
return 1;
}
static const struct luaL_reg methods_proxy_resultset[] = {
{ "__index", proxy_resultset_get },
{ "__gc", proxy_resultset_gc },
{ NULL, NULL },
};
static int proxy_resultset_lua_push_ref(lua_State *L, GRef *ref) {
GRef **ref_p;
g_ref_ref(ref);
ref_p = lua_newuserdata(L, sizeof(GRef *));
*ref_p = ref;
proxy_getmetatable(L, methods_proxy_resultset);
lua_setmetatable(L, -2);
return 1;
}
static int proxy_resultset_lua_push(lua_State *L, proxy_resultset_t *_res) {
GRef **ref_p;
GRef *ref;
ref = g_ref_new();
g_ref_set(ref, _res, (GDestroyNotify)proxy_resultset_free);
ref_p = lua_newuserdata(L, sizeof(GRef *));
*ref_p = ref;
proxy_getmetatable(L, methods_proxy_resultset);
lua_setmetatable(L, -2);
return 1;
}
static int proxy_injection_get(lua_State *L) {
injection *inj = *(injection **)luaL_checkself(L);
gsize keysize = 0;
const char *key = luaL_checklstring(L, 2, &keysize);
if (strleq(key, keysize, C("type"))) {
lua_pushinteger(L, inj->id); /** DEPRECATED: use "inj.id" instead */
} else if (strleq(key, keysize, C("id"))) {
lua_pushinteger(L, inj->id);
} else if (strleq(key, keysize, C("query"))) {
lua_pushlstring(L, inj->query->str, inj->query->len);
} else if (strleq(key, keysize, C("query_time"))) {
lua_pushinteger(L, chassis_calc_rel_microseconds(inj->ts_read_query, inj->ts_read_query_result_first));
} else if (strleq(key, keysize, C("response_time"))) {
lua_pushinteger(L, chassis_calc_rel_microseconds(inj->ts_read_query, inj->ts_read_query_result_last));
} else if (strleq(key, keysize, C("resultset"))) {
/* fields, rows */
proxy_resultset_t *res;
res = proxy_resultset_new();
/* only expose the resultset if really needed
FIXME: if the resultset is encoded in binary form, we can't provide it either.
*/
if (inj->resultset_is_needed && !inj->qstat.binary_encoded) {
res->result_queue = inj->result_queue;
}
res->qstat = inj->qstat;
res->rows = inj->rows;
res->bytes = inj->bytes;
proxy_resultset_lua_push(L, res);
} else {
g_message("%s.%d: inj[%s] ... not found", __FILE__, __LINE__, key);
lua_pushnil(L);
}
return 1;
}
static const struct luaL_reg methods_proxy_injection[] = {
{ "__index", proxy_injection_get },
{ NULL, NULL },
};
/**
* Push a metatable onto the Lua stack containing methods to
* access the resultsets.
*/
void proxy_getinjectionmetatable(lua_State *L) {
proxy_getmetatable(L, methods_proxy_injection);
}