MEDIUM: cli: Allow multiple filter entries for "show table"

For complex stick tables with many entries/columns, it can be beneficial
to filter using multiple criteria. The maximum number of filter entries
can be controlled by defining STKTABLE_FILTER_LEN during build time.

This patch can be backported to older releases.
diff --git a/src/hlua_fcn.c b/src/hlua_fcn.c
index 7ef708c..f8024aa 100644
--- a/src/hlua_fcn.c
+++ b/src/hlua_fcn.c
@@ -39,7 +39,6 @@
 static int class_regex_ref;
 static int class_stktable_ref;
 
-#define MAX_STK_FILTER_LEN 4
 #define STATS_LEN (MAX((int)ST_F_TOTAL_FIELDS, (int)INF_TOTAL_FIELDS))
 
 static THREAD_LOCAL struct field stats[STATS_LEN];
@@ -682,7 +681,7 @@
 	int op;
 	int dt;
 	long long val;
-	struct stk_filter filter[MAX_STK_FILTER_LEN];
+	struct stk_filter filter[STKTABLE_FILTER_LEN];
 	int filter_count = 0;
 	int i;
 	int skip_entry;
@@ -700,8 +699,8 @@
 		while (lua_next(L, 2) != 0) {
 			int entry_idx = 0;
 
-			if (filter_count >= MAX_STK_FILTER_LEN)
-				return hlua_error(L, "Filter table too large (len > %d)", MAX_STK_FILTER_LEN);
+			if (filter_count >= STKTABLE_FILTER_LEN)
+				return hlua_error(L, "Filter table too large (len > %d)", STKTABLE_FILTER_LEN);
 
 			if (lua_type(L, -1) != LUA_TTABLE  || lua_rawlen(L, -1) != 3)
 				return hlua_error(L, "Filter table entry must be a triplet: {\"data_col\", \"op\", val} (entry #%d)", filter_count + 1);
diff --git a/src/stick_table.c b/src/stick_table.c
index 7b64847..1393b1f 100644
--- a/src/stick_table.c
+++ b/src/stick_table.c
@@ -3600,23 +3600,29 @@
  */
 static int table_prepare_data_request(struct appctx *appctx, char **args)
 {
+	int i;
+
 	if (appctx->ctx.table.action != STK_CLI_ACT_SHOW && appctx->ctx.table.action != STK_CLI_ACT_CLR)
 		return cli_err(appctx, "content-based lookup is only supported with the \"show\" and \"clear\" actions\n");
 
-	/* condition on stored data value */
-	appctx->ctx.table.data_type = stktable_get_data_type(args[3] + 5);
-	if (appctx->ctx.table.data_type < 0)
-		return cli_err(appctx, "Unknown data type\n");
+	for (i = 0; i < STKTABLE_FILTER_LEN; i++) {
+		if (i > 0 && !*args[3+3*i])  // number of filter entries can be less than STKTABLE_FILTER_LEN
+			break;
+		/* condition on stored data value */
+		appctx->ctx.table.data_type[i] = stktable_get_data_type(args[3+3*i] + 5);
+		if (appctx->ctx.table.data_type[i] < 0)
+			return cli_err(appctx, "Unknown data type\n");
 
-	if (!((struct stktable *)appctx->ctx.table.target)->data_ofs[appctx->ctx.table.data_type])
-		return cli_err(appctx, "Data type not stored in this table\n");
+		if (!((struct stktable *)appctx->ctx.table.target)->data_ofs[appctx->ctx.table.data_type[i]])
+			return cli_err(appctx, "Data type not stored in this table\n");
 
-	appctx->ctx.table.data_op = get_std_op(args[4]);
-	if (appctx->ctx.table.data_op < 0)
-		return cli_err(appctx, "Require and operator among \"eq\", \"ne\", \"le\", \"ge\", \"lt\", \"gt\"\n");
+		appctx->ctx.table.data_op[i] = get_std_op(args[4+3*i]);
+		if (appctx->ctx.table.data_op < 0)
+			return cli_err(appctx, "Require and operator among \"eq\", \"ne\", \"le\", \"ge\", \"lt\", \"gt\"\n");
 
-	if (!*args[5] || strl2llrc(args[5], strlen(args[5]), &appctx->ctx.table.value) != 0)
-		return cli_err(appctx, "Require a valid integer value to compare against\n");
+		if (!*args[5] || strl2llrc(args[5+3*i], strlen(args[5+3*i]), &appctx->ctx.table.value[i]) != 0)
+			return cli_err(appctx, "Require a valid integer value to compare against\n");
+	}
 
 	/* OK we're done, all the fields are set */
 	return 0;
@@ -3625,7 +3631,10 @@
 /* returns 0 if wants to be called, 1 if has ended processing */
 static int cli_parse_table_req(char **args, char *payload, struct appctx *appctx, void *private)
 {
-	appctx->ctx.table.data_type = -1;
+	int i;
+
+	for (i = 0; i < STKTABLE_FILTER_LEN; i++)
+		appctx->ctx.table.data_type[i] = -1;
 	appctx->ctx.table.target = NULL;
 	appctx->ctx.table.entry = NULL;
 	appctx->ctx.table.action = (long)private; // keyword argument, one of STK_CLI_ACT_*
@@ -3672,7 +3681,6 @@
 	struct stream_interface *si = appctx->owner;
 	struct stream *s = si_strm(si);
 	struct ebmb_node *eb;
-	int dt;
 	int skip_entry;
 	int show = appctx->ctx.table.action == STK_CLI_ACT_SHOW;
 
@@ -3744,48 +3752,53 @@
 
 			HA_RWLOCK_RDLOCK(STK_SESS_LOCK, &appctx->ctx.table.entry->lock);
 
-			if (appctx->ctx.table.data_type >= 0) {
+			if (appctx->ctx.table.data_type[0] >= 0) {
 				/* we're filtering on some data contents */
 				void *ptr;
-				long long data;
+				int dt;
+				signed char op;
+				long long data, value;
 
 
-				dt = appctx->ctx.table.data_type;
-				ptr = stktable_data_ptr(appctx->ctx.table.t,
-							appctx->ctx.table.entry,
-							dt);
+				for (int i = 0; i < STKTABLE_FILTER_LEN; i++) {
+					if (appctx->ctx.table.data_type[i] == -1)
+						break;
+					dt = appctx->ctx.table.data_type[i];
+					ptr = stktable_data_ptr(appctx->ctx.table.t,
+								appctx->ctx.table.entry,
+								dt);
 
-				data = 0;
-				switch (stktable_data_types[dt].std_type) {
-				case STD_T_SINT:
-					data = stktable_data_cast(ptr, std_t_sint);
-					break;
-				case STD_T_UINT:
-					data = stktable_data_cast(ptr, std_t_uint);
-					break;
-				case STD_T_ULL:
-					data = stktable_data_cast(ptr, std_t_ull);
-					break;
-				case STD_T_FRQP:
-					data = read_freq_ctr_period(&stktable_data_cast(ptr, std_t_frqp),
-								    appctx->ctx.table.t->data_arg[dt].u);
-					break;
-				}
+					data = 0;
+					switch (stktable_data_types[dt].std_type) {
+					case STD_T_SINT:
+						data = stktable_data_cast(ptr, std_t_sint);
+						break;
+					case STD_T_UINT:
+						data = stktable_data_cast(ptr, std_t_uint);
+						break;
+					case STD_T_ULL:
+						data = stktable_data_cast(ptr, std_t_ull);
+						break;
+					case STD_T_FRQP:
+						data = read_freq_ctr_period(&stktable_data_cast(ptr, std_t_frqp),
+									    appctx->ctx.table.t->data_arg[dt].u);
+						break;
+					}
+
+					op = appctx->ctx.table.data_op[i];
+					value = appctx->ctx.table.value[i];
 
-				/* skip the entry if the data does not match the test and the value */
-				if ((data < appctx->ctx.table.value &&
-				     (appctx->ctx.table.data_op == STD_OP_EQ ||
-				      appctx->ctx.table.data_op == STD_OP_GT ||
-				      appctx->ctx.table.data_op == STD_OP_GE)) ||
-				    (data == appctx->ctx.table.value &&
-				     (appctx->ctx.table.data_op == STD_OP_NE ||
-				      appctx->ctx.table.data_op == STD_OP_GT ||
-				      appctx->ctx.table.data_op == STD_OP_LT)) ||
-				    (data > appctx->ctx.table.value &&
-				     (appctx->ctx.table.data_op == STD_OP_EQ ||
-				      appctx->ctx.table.data_op == STD_OP_LT ||
-				      appctx->ctx.table.data_op == STD_OP_LE)))
-					skip_entry = 1;
+					/* skip the entry if the data does not match the test and the value */
+					if ((data < value &&
+					     (op == STD_OP_EQ || op == STD_OP_GT || op == STD_OP_GE)) ||
+					    (data == value &&
+					     (op == STD_OP_NE || op == STD_OP_GT || op == STD_OP_LT)) ||
+					    (data > value &&
+					     (op == STD_OP_EQ || op == STD_OP_LT || op == STD_OP_LE))) {
+						skip_entry = 1;
+						break;
+					}
+				}
 			}
 
 			if (show && !skip_entry &&