// SPDX-License-Identifier: LGPL-2.1
/*
 * Copyright (C) 2021 VMware Inc, Steven Rostedt <rostedt@goodmis.org>
 *
 * Updates:
 * Copyright (C) 2021, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com>
 *
 */
#include <trace-seq.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <errno.h>

#include "tracefs.h"
#include "tracefs-local.h"
#include "sqlhist-parse.h"

extern int yylex_init(void* ptr_yy_globals);
extern int yylex_init_extra(struct sqlhist_bison *sb, void* ptr_yy_globals);
extern int yylex_destroy (void * yyscanner );

struct str_hash {
	struct str_hash		*next;
	char			*str;
};

enum alias_type {
	ALIAS_EVENT,
	ALIAS_FIELD,
};

enum field_type {
	FIELD_NONE,
	FIELD_FROM,
	FIELD_TO,
};

#define for_each_field(expr, field, table) \
	for (expr = (table)->fields; expr; expr = (field)->next)

#define TIMESTAMP_COMPARE "TIMESTAMP_DELTA"
#define TIMESTAMP_USECS_COMPARE "TIMESTAMP_DELTA_USECS"
#define EVENT_START	"__START_EVENT__"
#define EVENT_END	"__END_EVENT__"
#define TIMESTAMP_NSECS "TIMESTAMP"
#define TIMESTAMP_USECS "TIMESTAMP_USECS"

struct field {
	struct expr		*next;	/* private link list */
	const char		*system;
	const char		*event_name;
	struct tep_event	*event;
	const char		*raw;
	const char		*label;
	const char		*field;
	const char		*type;
	enum field_type		ftype;
};

struct filter {
	enum filter_type	type;
	struct expr		*lval;
	struct expr		*rval;
};

struct match {
	struct match		*next;
	struct expr		*lval;
	struct expr		*rval;
};

struct compare {
	enum compare_type	type;
	struct expr		*lval;
	struct expr		*rval;
	const char		*name;
};

enum expr_type
{
	EXPR_NUMBER,
	EXPR_STRING,
	EXPR_FIELD,
	EXPR_FILTER,
	EXPR_COMPARE,
};

struct expr {
	struct expr		*free_list;
	struct expr		*next;
	enum expr_type		type;
	int			line;
	int			idx;
	union {
		struct field	field;
		struct filter	filter;
		struct compare	compare;
		const char	*string;
		long		number;
	};
};

struct sql_table {
	struct sqlhist_bison	*sb;
	const char		*name;
	struct expr		*exprs;
	struct expr		*fields;
	struct expr		*from;
	struct expr		*to;
	struct expr		*where;
	struct expr		**next_where;
	struct match		*matches;
	struct match		**next_match;
	struct expr		*selections;
	struct expr		**next_selection;
};

__hidden int tfs_my_yyinput(void *extra, char *buf, int max)
{
	struct sqlhist_bison *sb = extra;

	if (!sb || !sb->buffer)
		return 0;

	if (sb->buffer_idx + max > sb->buffer_size)
		max = sb->buffer_size - sb->buffer_idx;

	if (max)
		memcpy(buf, sb->buffer + sb->buffer_idx, max);

	sb->buffer_idx += max;

	return max;
}

__hidden void tfs_sql_parse_error(struct sqlhist_bison *sb,
				  const char *text,
				  const char *fmt,
				  va_list ap)
{
	const char *buffer = sb->buffer;
	struct trace_seq s;
	int line = sb->line_no;
	int idx = sb->line_idx - strlen(text);
	int i;

	if (!buffer)
		return;

	trace_seq_init(&s);
	if (!s.buffer) {
		tracefs_warning("Error allocating internal buffer\n");
		return;
	}

	for (i = 0; line && buffer[i]; i++) {
		if (buffer[i] == '\n')
			line--;
	}
	for (; buffer[i] && buffer[i] != '\n'; i++)
		trace_seq_putc(&s, buffer[i]);
	trace_seq_putc(&s, '\n');
	for (i = idx; i > 0; i--)
		trace_seq_putc(&s, ' ');
	trace_seq_puts(&s, "^\n");
	trace_seq_printf(&s, "ERROR: '%s'\n", text);
	trace_seq_vprintf(&s, fmt, ap);

	trace_seq_terminate(&s);

	sb->parse_error_str = strdup(s.buffer);
	trace_seq_destroy(&s);
}

static void parse_error(struct sqlhist_bison *sb, const char *text,
			const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	tfs_sql_parse_error(sb, text, fmt, ap);
	va_end(ap);
}

__hidden unsigned int tfs_quick_hash(const char *str)
{
	unsigned int val = 0;
	int len = strlen(str);

	for (; len >= 4; str += 4, len -= 4) {
		val += str[0];
		val += str[1] << 8;
		val += str[2] << 16;
		val += str[3] << 24;
	}
	for (; len > 0; str++, len--)
		val += str[0] << (len * 8);

        val *= 2654435761;

        return val & ((1 << HASH_BITS) - 1);
}


static struct str_hash *find_string(struct sqlhist_bison *sb, const char *str)
{
	unsigned int key = tfs_quick_hash(str);
	struct str_hash *hash = sb->str_hash[key];

	for (; hash; hash = hash->next) {
		if (!strcmp(hash->str, str))
			return hash;
	}
	return NULL;
}

/*
 * If @str is found, then return the hash string.
 * This lets tfs_store_str() know to free str.
 */
static char **add_hash(struct sqlhist_bison *sb, const char *str)
{
	struct str_hash *hash;
	unsigned int key;

	if ((hash = find_string(sb, str))) {
		return &hash->str;
	}

	hash = malloc(sizeof(*hash));
	if (!hash)
		return NULL;
	key = tfs_quick_hash(str);
	hash->next = sb->str_hash[key];
	sb->str_hash[key] = hash;
	hash->str = NULL;
	return &hash->str;
}

__hidden char *tfs_store_str(struct sqlhist_bison *sb, const char *str)
{
	char **pstr = add_hash(sb, str);

	if (!pstr)
		return NULL;

	if (!(*pstr))
		*pstr = strdup(str);

	return *pstr;
}

__hidden void *tfs_add_cast(struct sqlhist_bison *sb, void *data, const char *type)
{
	struct expr *expr = data;
	struct field *field = &expr->field;

	field->type = type;
	return expr;
}

__hidden void *tfs_add_cast_buckets(struct sqlhist_bison *sb, void *data,
				    const char *type, int num)
{
	struct expr *expr = data;
	struct field *field = &expr->field;
	char *str;

	if (asprintf(&str, "%s=%u", type, num) < 0)
		return NULL;

	field->type = tfs_store_str(sb, str);
	free(str);
	if (!field->type)
		return NULL;
	return expr;
}

__hidden int tfs_add_selection(struct sqlhist_bison *sb, void *select, const char *name)
{
	struct sql_table *table = sb->table;
	struct expr *expr = select;

	switch (expr->type) {
	case EXPR_FIELD:
		expr->field.label = name;
		break;
	case EXPR_COMPARE:
		expr->compare.name = name;
		break;
	case EXPR_NUMBER:
	case EXPR_STRING:
	case EXPR_FILTER:
	default:
		return -1;
	}

	if (expr->next)
		return -1;

	*table->next_selection = expr;
	table->next_selection = &expr->next;

	return 0;
}

static struct expr *find_field(struct sqlhist_bison *sb, const char *raw, const char *label)
{
	struct field *field;
	struct expr *expr;

	for_each_field(expr, field, sb->table) {
		field = &expr->field;

		if (!strcmp(field->raw, raw)) {
			if (label && !field->label)
				field->label = label;
			if (label && strcmp(label, field->label) != 0)
				continue;
			return expr;
		}

		if (label && !strcmp(field->raw, label)) {
			if (!field->label) {
				field->label = label;
				field->raw = raw;
			}
			return expr;
		}

		if (!field->label)
			continue;

		if (!strcmp(field->label, raw))
			return expr;

		if (label && !strcmp(field->label, label))
			return expr;
	}
	return NULL;
}

static void *create_expr(struct sqlhist_bison *sb,
			 enum expr_type type, struct expr **expr_p)
{
	struct expr *expr;

	expr = calloc(1, sizeof(*expr));
	if (!expr)
		return NULL;

	if (expr_p)
		*expr_p = expr;

	expr->free_list = sb->table->exprs;
	sb->table->exprs = expr;

	expr->type = type;
	expr->line = sb->line_no;
	expr->idx = sb->line_idx;

	switch (type) {
	case EXPR_FIELD:	return &expr->field;
	case EXPR_COMPARE:	return &expr->compare;
	case EXPR_NUMBER:	return &expr->number;
	case EXPR_STRING:	return &expr->string;
	case EXPR_FILTER:	return &expr->filter;
	}

	return NULL;
}

#define __create_expr(var, type, ENUM, expr)			\
	do {							\
		var = (type *)create_expr(sb, EXPR_##ENUM, expr);	\
	} while(0)

#define create_field(var, expr)				\
	__create_expr(var, struct field, FIELD, expr)

#define create_filter(var, expr)			\
	__create_expr(var, struct filter, FILTER, expr)

#define create_compare(var, expr)				\
	__create_expr(var, struct compare, COMPARE, expr)

#define create_string(var, expr)			\
	__create_expr(var, const char *, STRING, expr)

#define create_number(var, expr)			\
	__create_expr(var, long, NUMBER, expr)

__hidden void *tfs_add_field(struct sqlhist_bison *sb, const char *field_name, const char *label)
{
	struct sql_table *table = sb->table;
	struct expr *expr;
	struct field *field;
	bool nsecs;

	/* Check if this is a TIMESTAMP compare */
	if ((nsecs = (strcmp(field_name, TIMESTAMP_COMPARE) == 0)) ||
	    strcmp(field_name, TIMESTAMP_USECS_COMPARE) == 0) {
		const char *field_nameA;
		const char *field_nameB;
		struct expr *exprA;
		struct expr *exprB;
		struct field *fieldA;
		struct field *fieldB;

		if (nsecs) {
			field_nameA = EVENT_END "." TIMESTAMP_NSECS;
			field_nameB = EVENT_START "." TIMESTAMP_NSECS;
		} else {
			field_nameA = EVENT_END "." TIMESTAMP_USECS;
			field_nameB = EVENT_START "." TIMESTAMP_USECS;
		}

		exprA = find_field(sb, field_nameA, NULL);
		if (!exprA) {
			create_field(fieldA, &exprA);
			fieldA->next = table->fields;
			table->fields = exprA;
			fieldA->raw = field_nameA;
		}

		exprB = find_field(sb, field_nameB, NULL);
		if (!exprB) {
			create_field(fieldB, &exprB);
			fieldB->next = table->fields;
			table->fields = exprB;
			fieldB->raw = field_nameB;
		}

		return tfs_add_compare(sb, exprA, exprB, COMPARE_SUB);
	}

	expr = find_field(sb, field_name, label);
	if (expr)
		return expr;

	create_field(field, &expr);

	field->next = table->fields;
	table->fields = expr;

	field->raw = field_name;
	field->label = label;

	return expr;
}

__hidden void *tfs_add_filter(struct sqlhist_bison *sb, void *A, void *B, enum filter_type op)
{
	struct filter *filter;
	struct expr *expr;

	create_filter(filter, &expr);

	filter->lval = A;
	filter->rval = B;

	filter->type = op;

	return expr;
}

__hidden int tfs_add_match(struct sqlhist_bison *sb, void *A, void *B)
{
	struct sql_table *table = sb->table;
	struct match *match;

	match = calloc(1, sizeof(*match));
	if (!match)
		return -1;

	match->lval = A;
	match->rval = B;

	*table->next_match = match;
	table->next_match = &match->next;

	return 0;
}

__hidden void *tfs_add_compare(struct sqlhist_bison *sb, void *A, void *B, enum compare_type type)
{
	struct compare *compare;
	struct expr *expr;

	create_compare(compare, &expr);

	compare = &expr->compare;
	compare->lval = A;
	compare->rval = B;
	compare->type = type;

	return expr;
}

__hidden int tfs_add_where(struct sqlhist_bison *sb, void *item)
{
	struct expr *expr = item;
	struct sql_table *table = sb->table;

	if (expr->type != EXPR_FILTER)
		return -1;

	*table->next_where = expr;
	table->next_where = &expr->next;

	if (expr->next)
		return -1;

	return 0;
}

__hidden int tfs_add_from(struct sqlhist_bison *sb, void *item)
{
	struct expr *expr = item;

	if (expr->type != EXPR_FIELD)
		return -1;

	sb->table->from = expr;

	return 0;
}

__hidden int tfs_add_to(struct sqlhist_bison *sb, void *item)
{
	struct expr *expr = item;

	if (expr->type != EXPR_FIELD)
		return -1;

	sb->table->to = expr;

	return 0;
}

__hidden void *tfs_add_string(struct sqlhist_bison *sb, const char *str)
{
	struct expr *expr;
	const char **str_p;

	create_string(str_p, &expr);
	*str_p = str;
	return expr;
}

__hidden void *tfs_add_number(struct sqlhist_bison *sb, long val)
{
	struct expr *expr;
	long *num;

	create_number(num, &expr);
	*num = val;
	return expr;
}

__hidden int tfs_table_start(struct sqlhist_bison *sb)
{
	struct sql_table *table;

	table = calloc(1, sizeof(*table));
	if (!table)
		return -ENOMEM;

	table->sb = sb;
	sb->table = table;

	table->next_where = &table->where;
	table->next_match = &table->matches;
	table->next_selection = &table->selections;

	return 0;
}

static int test_event_exists(struct tep_handle *tep,
			     struct sqlhist_bison *sb,
			     struct expr *expr, struct tep_event **pevent)
{
	struct field *field = &expr->field;
	const char *system = field->system;
	const char *event = field->event_name;

	if (!field->event)
		field->event = tep_find_event_by_name(tep, system, event);
	if (pevent)
		*pevent = field->event;

	if (field->event)
		return 0;

	sb->line_no = expr->line;
	sb->line_idx = expr->idx;

	parse_error(sb, field->raw, "event not found\n");
	return -1;
}

static int test_field_exists(struct tep_handle *tep,
			     struct sqlhist_bison *sb,
			     struct expr *expr)
{
	struct field *field = &expr->field;
	struct tep_format_field *tfield;
	char *field_name;
	const char *p;

	if (!field->event) {
		if (test_event_exists(tep, sb, expr, NULL))
			return -1;
	}

	/* The field could have a conversion */
	p = strchr(field->field, '.');
	if (p)
		field_name = strndup(field->field, p - field->field);
	else
		field_name = strdup(field->field);

	if (!field_name)
		return -1;

	if (!strcmp(field_name, TRACEFS_TIMESTAMP) ||
	    !strcmp(field->field, TRACEFS_TIMESTAMP_USECS) ||
	    !strcmp(field->field, TRACEFS_STACKTRACE))
		tfield = (void *)1L;
	else
		tfield = tep_find_any_field(field->event, field_name);
	free(field_name);

	if (!tfield && (!strcmp(field->field, "COMM") || !strcmp(field->field, "comm")))
		tfield = (void *)1L;

	if (tfield)
		return 0;

	sb->line_no = expr->line;
	sb->line_idx = expr->idx;

	parse_error(sb, field->raw,
		    "Field '%s' not part of event %s\n",
		    field->field, field->event_name);
	return -1;
}

static int update_vars(struct tep_handle *tep,
		       struct sql_table *table,
		       struct expr *expr)
{
	struct sqlhist_bison *sb = table->sb;
	struct field *event_field = &expr->field;
	enum field_type ftype = FIELD_NONE;
	struct tep_event *event;
	struct field *field;
	const char *extra_label = NULL;
	const char *label;
	const char *raw = event_field->raw;
	const char *event_name;
	const char *system;
	const char *p;
	int label_len = 0, event_len, system_len;
	int extra_label_len = 0;

	if (expr == table->to) {
		ftype = FIELD_TO;
		extra_label = EVENT_END;
	} else if (expr == table->from) {
		ftype = FIELD_FROM;
		extra_label = EVENT_START;
	}

	if (extra_label)
		extra_label_len = strlen(extra_label);

	p = strchr(raw, '.');
	if (p) {
		char *str;

		str = strndup(raw, p - raw);
		if (!str)
			return -1;
		event_field->system = tfs_store_str(sb, str);
		free(str);
		if (!event_field->system)
			return -1;
		p++;
	} else {
		p = raw;
	}

	event_field->event_name = tfs_store_str(sb, p);
	if (!event_field->event_name)
		return -1;

	if (test_event_exists(tep, sb, expr, &event))
		return -1;

	if (!event_field->system)
		event_field->system = tfs_store_str(sb, event->system);

	if (!event_field->system)
		return -1;

	label = event_field->label;
	if (label)
		label_len = strlen(label);

	system = event_field->system;
	system_len = strlen(system);

	event_name = event_field->event_name;
	event_len = strlen(event_name);

	for_each_field(expr, field, table) {
		int len;

		field = &expr->field;

		if (field->event)
			continue;

		raw = field->raw;

		/*
		 * The field could be:
		 *     system.event.field...
		 *     event.field...
		 *     label.field...
		 * We check label first.
		 */

		len = label_len;
		if (label && !strncmp(raw, label, len) &&
		    raw[len] == '.') {
			/* Label matches and takes precedence */
			goto found;
		}

		len = extra_label_len;
		if (extra_label && !strncmp(raw, extra_label, len) &&
		    raw[len] == '.') {
			/* Label matches and takes precedence */
			goto found;
		}

		if (!strncmp(raw, system, system_len) &&
		    raw[system_len] == '.') {
			raw += system_len + 1;
			/* Check the event portion next */
		}

		len = event_len;
		if (strncmp(raw, event_name, len) ||
		    raw[len] != '.') {
			/* Does not match */
			continue;
		}
 found:
		field->system = system;
		field->event_name = event_name;
		field->event = event;
		field->field = raw + len + 1;
		field->ftype = ftype;

		if (!strcmp(field->field, "TIMESTAMP"))
			field->field = tfs_store_str(sb, TRACEFS_TIMESTAMP);
		if (!strcmp(field->field, "TIMESTAMP_USECS"))
			field->field = tfs_store_str(sb, TRACEFS_TIMESTAMP_USECS);
		if (!strcmp(field->field, "STACKTRACE"))
			field->field = tfs_store_str(sb, TRACEFS_STACKTRACE);
		if (test_field_exists(tep, sb, expr))
			return -1;
	}

	return 0;
}

/*
 * Called when there's a FROM but no JOIN(to), which means that the
 * selections can be fields and not mention the event itself.
 */
static int update_fields(struct tep_handle *tep,
			 struct sql_table *table,
			 struct expr *expr)
{
	struct field *event_field = &expr->field;
	struct sqlhist_bison *sb = table->sb;
	struct tep_format_field *tfield;
	struct tep_event *event;
	struct field *field;
	const char *p;
	int len;

	/* First update fields with aliases an such and add event */
	update_vars(tep, table, expr);

	/*
	 * If event is not found, the creation of the synth will
	 * add a proper error, so return "success".
	*/
	if (!event_field->event)
		return 0;

	event = event_field->event;

	for_each_field(expr, field, table) {
		const char *field_name;

		field = &expr->field;

		if (field->event)
			continue;

		field_name = field->raw;

		p = strchr(field_name, '.');
		if (p) {
			len = p - field_name;
			p = strndup(field_name, len);
			if (!p)
				return -1;
			field_name = tfs_store_str(sb, p);
			free((char *)p);
			if (!field_name)
				return -1;
		}

		tfield = tep_find_any_field(event, field_name);
		/* Let it error properly later */
		if (!tfield)
			continue;

		field->system = event_field->system;
		field->event_name = event_field->event_name;
		field->event = event;
		field->field = field_name;
	}

	return 0;
}

static int match_error(struct sqlhist_bison *sb, struct match *match,
		       struct field *lmatch, struct field *rmatch)
{
	struct field *lval = &match->lval->field;
	struct field *rval = &match->rval->field;
	struct field *field;
	struct expr *expr;

	if (lval->system != lmatch->system ||
	    lval->event != lmatch->event) {
		expr = match->lval;
		field = lval;
	} else {
		expr = match->rval;
		field = rval;
	}

	sb->line_no = expr->line;
	sb->line_idx = expr->idx;

	parse_error(sb, field->raw,
		    "'%s' and '%s' must be a field for each event: '%s' and '%s'\n",
		    lval->raw, rval->raw, sb->table->to->field.raw,
		    sb->table->from->field.raw);

	return -1;
}

static int test_match(struct sql_table *table, struct match *match)
{
	struct field *lval, *rval;
	struct field *to, *from;

	if (!match->lval || !match->rval)
		return -1;

	if (match->lval->type != EXPR_FIELD || match->rval->type != EXPR_FIELD)
		return -1;

	to = &table->to->field;
	from = &table->from->field;

	lval = &match->lval->field;
	rval = &match->rval->field;

	/*
	 * Note, strings are stored in the string store, so all
	 * duplicate strings are the same value, and we can use
	 * normal "==" and "!=" instead of strcmp().
	 *
	 * Either lval == to and rval == from
	 * or lval == from and rval == to.
	 */
	if ((lval->system != to->system) ||
	    (lval->event != to->event)) {
		if ((rval->system != to->system) ||
		    (rval->event != to->event) ||
		    (lval->system != from->system) ||
		    (lval->event != from->event))
			return match_error(table->sb, match, from, to);
	} else {
		if ((rval->system != from->system) ||
		    (rval->event != from->event) ||
		    (lval->system != to->system) ||
		    (lval->event != to->event))
			return match_error(table->sb, match, to, from);
	}
	return 0;
}

static void assign_match(const char *system, const char *event,
			 struct match *match,
			 const char **start_match, const char **end_match)
{
	struct field *lval, *rval;

	lval = &match->lval->field;
	rval = &match->rval->field;

	if (lval->system == system &&
	    lval->event_name == event) {
		*start_match = lval->field;
		*end_match = rval->field;
	} else {
		*start_match = rval->field;
		*end_match = lval->field;
	}
}

static int build_compare(struct tracefs_synth *synth,
			 const char *system, const char *event,
			 struct compare *compare)
{
	const char *start_field;
	const char *end_field;
	struct field *lval, *rval;
	enum tracefs_synth_calc calc;
	int ret;

	if (!compare->name)
		return -1;

	lval = &compare->lval->field;
	rval = &compare->rval->field;

	if (lval->system == system &&
	    lval->event_name == event) {
		start_field = lval->field;
		end_field = rval->field;
		calc = TRACEFS_SYNTH_DELTA_START;
	} else {
		start_field = rval->field;
		end_field = lval->field;
		calc = TRACEFS_SYNTH_DELTA_END;
	}

	if (compare->type == COMPARE_ADD)
		calc = TRACEFS_SYNTH_ADD;

	ret = tracefs_synth_add_compare_field(synth, start_field,
					      end_field, calc,
					      compare->name);
	return ret;
}

static int verify_filter_error(struct sqlhist_bison *sb, struct expr *expr,
			       const char *event)
{
	struct field *field = &expr->field;

	sb->line_no = expr->line;
	sb->line_idx = expr->idx;

	parse_error(sb, field->raw,
		    "event '%s' can not be grouped or '||' together with '%s'\n"
		    "All filters between '&&' must be for the same event\n",
		    field->event, event);
	return -1;
}

static int do_verify_filter(struct sqlhist_bison *sb, struct filter *filter,
			    const char **system, const char **event,
			    enum field_type *ftype)
{
	int ret;

	if (filter->type == FILTER_OR ||
	    filter->type == FILTER_AND) {
		ret = do_verify_filter(sb, &filter->lval->filter, system, event, ftype);
		if (ret)
			return ret;
		return do_verify_filter(sb, &filter->rval->filter, system, event, ftype);
	}
	if (filter->type == FILTER_GROUP ||
	    filter->type == FILTER_NOT_GROUP) {
		return do_verify_filter(sb, &filter->lval->filter, system, event, ftype);
	}

	/*
	 * system and event will be NULL until we find the left most
	 * node. Then assign it, and compare on the way back up.
	 */
	if (!*system && !*event) {
		*system = filter->lval->field.system;
		*event = filter->lval->field.event_name;
		*ftype = filter->lval->field.ftype;
		return 0;
	}

	if (filter->lval->field.system != *system ||
	    filter->lval->field.event_name != *event)
		return verify_filter_error(sb, filter->lval, *event);

	return 0;
}

static int verify_filter(struct sqlhist_bison *sb, struct filter *filter,
			 const char **system, const char **event,
			 enum field_type *ftype)
{
	int ret;

	switch (filter->type) {
	case FILTER_OR:
	case FILTER_AND:
	case FILTER_GROUP:
	case FILTER_NOT_GROUP:
		break;
	default:
		return do_verify_filter(sb, filter, system, event, ftype);
	}

	ret = do_verify_filter(sb, &filter->lval->filter, system, event, ftype);
	if (ret)
		return ret;

	switch (filter->type) {
	case FILTER_OR:
	case FILTER_AND:
		return do_verify_filter(sb, &filter->rval->filter, system, event, ftype);
	default:
		return 0;
	}
}

static int test_field_exists(struct tep_handle *tep, struct sqlhist_bison *sb,
			     struct expr *expr);

static void filter_compare_error(struct tep_handle *tep,
				 struct sqlhist_bison *sb,
				 struct expr *expr)
{
	struct field *field = &expr->field;

	switch (errno) {
	case ENODEV:
	case EBADE:
		break;
	case EINVAL:
		parse_error(sb, field->raw, "Invalid compare\n");
		break;
	default:
		parse_error(sb, field->raw, "System error?\n");
		return;
	}

	/* ENODEV means that an event or field does not exist */
	if (errno == ENODEV) {
		if (test_field_exists(tep, sb, expr))
			return;
		if (test_field_exists(tep, sb, expr))
			return;
		return;
	}

	/* fields exist, but values are not compatible */
	sb->line_no = expr->line;
	sb->line_idx = expr->idx;

	parse_error(sb, field->raw,
		    "Field '%s' is not compatible to be compared with the given value\n",
		    field->field);
}

static void filter_error(struct tep_handle *tep,
			 struct sqlhist_bison *sb, struct expr *expr)
{
	struct filter *filter = &expr->filter;

	sb->line_no = expr->line;
	sb->line_idx = expr->idx;

	switch (filter->type) {
	case FILTER_NOT_GROUP:
	case FILTER_GROUP:
	case FILTER_OR:
	case FILTER_AND:
		break;
	default:
		filter_compare_error(tep, sb, filter->lval);
		return;
	}

	sb->line_no = expr->line;
	sb->line_idx = expr->idx;

	parse_error(sb, "", "Problem with filter entry?\n");
}

static int build_filter(struct tep_handle *tep, struct sqlhist_bison *sb,
			struct tracefs_synth *synth,
			bool start, struct expr *expr, bool *started)
{
	int (*append_filter)(struct tracefs_synth *synth,
			     enum tracefs_filter type,
			     const char *field,
			     enum tracefs_compare compare,
			     const char *val);
	struct filter *filter = &expr->filter;
	enum tracefs_compare cmp;
	const char *val = NULL;
	int and_or = TRACEFS_FILTER_AND;
	char num[64];
	int ret;

	if (start)
		append_filter = tracefs_synth_append_start_filter;
	else
		append_filter = tracefs_synth_append_end_filter;

	if (started && *started) {
		ret = append_filter(synth, and_or, NULL, 0, NULL);
		ret = append_filter(synth, TRACEFS_FILTER_OPEN_PAREN,
				    NULL, 0, NULL);
	}

	switch (filter->type) {
	case FILTER_NOT_GROUP:
		ret = append_filter(synth, TRACEFS_FILTER_NOT,
				    NULL, 0, NULL);
		if (ret < 0)
			goto out;
		/* Fall through */
	case FILTER_GROUP:
		ret = append_filter(synth, TRACEFS_FILTER_OPEN_PAREN,
				    NULL, 0, NULL);
		if (ret < 0)
			goto out;
		ret = build_filter(tep, sb, synth, start, filter->lval, NULL);
		if (ret < 0)
			goto out;
		ret = append_filter(synth, TRACEFS_FILTER_CLOSE_PAREN,
				    NULL, 0, NULL);
		goto out;

	case FILTER_OR:
		and_or = TRACEFS_FILTER_OR;
		/* Fall through */
	case FILTER_AND:
		ret = build_filter(tep, sb, synth, start, filter->lval, NULL);
		if (ret < 0)
			goto out;
		ret = append_filter(synth, and_or, NULL, 0, NULL);

		if (ret)
			goto out;
		ret = build_filter(tep, sb, synth, start, filter->rval, NULL);
		goto out;
	default:
		break;
	}

	switch (filter->rval->type) {
	case EXPR_NUMBER:
		sprintf(num, "%ld", filter->rval->number);
		val = num;
		break;
	case EXPR_STRING:
		val = filter->rval->string;
		break;
	default:
		break;
	}

	switch (filter->type) {
	case FILTER_EQ:		cmp = TRACEFS_COMPARE_EQ; break;
	case FILTER_NE:		cmp = TRACEFS_COMPARE_NE; break;
	case FILTER_LE:		cmp = TRACEFS_COMPARE_LE; break;
	case FILTER_LT:		cmp = TRACEFS_COMPARE_LT; break;
	case FILTER_GE:		cmp = TRACEFS_COMPARE_GE; break;
	case FILTER_GT:		cmp = TRACEFS_COMPARE_GT; break;
	case FILTER_BIN_AND:	cmp = TRACEFS_COMPARE_AND; break;
	case FILTER_STR_CMP:	cmp = TRACEFS_COMPARE_RE; break;
	default:
		tracefs_warning("Error invalid filter type '%d'", filter->type);
		return ERANGE;
	}

	ret = append_filter(synth, TRACEFS_FILTER_COMPARE,
			    filter->lval->field.field, cmp, val);

	if (ret)
		filter_error(tep, sb, expr);
 out:
	if (!ret && started) {
		if (*started)
			ret = append_filter(synth, TRACEFS_FILTER_CLOSE_PAREN,
					    NULL, 0, NULL);
		*started = true;
	}
	return ret;
}

static void *field_match_error(struct tep_handle *tep, struct sqlhist_bison *sb,
			       struct match *match)
{
	switch (errno) {
	case ENODEV:
	case EBADE:
		break;
	default:
		/* System error */
		return NULL;
	}

	/* ENODEV means that an event or field does not exist */
	if (errno == ENODEV) {
		if (test_field_exists(tep, sb, match->lval))
			return NULL;
		if (test_field_exists(tep, sb, match->rval))
			return NULL;
		return NULL;
	}

	/* fields exist, but values are not compatible */
	sb->line_no = match->lval->line;
	sb->line_idx = match->lval->idx;

	parse_error(sb, match->lval->field.raw,
		    "Field '%s' is not compatible to match field '%s'\n",
		    match->lval->field.raw, match->rval->field.raw);
	return NULL;
}

static void *synth_init_error(struct tep_handle *tep, struct sql_table *table)
{
	struct sqlhist_bison *sb = table->sb;
	struct match *match = table->matches;

	switch (errno) {
	case ENODEV:
	case EBADE:
		break;
	default:
		/* System error */
		return NULL;
	}

	/* ENODEV could mean that start or end events do not exist */
	if (errno == ENODEV) {
		if (test_event_exists(tep, sb, table->from, NULL))
			return NULL;
		if (test_event_exists(tep, sb, table->to, NULL))
			return NULL;
	}

	return field_match_error(tep, sb, match);
}

static void selection_error(struct tep_handle *tep,
			    struct sqlhist_bison *sb, struct expr *expr)
{
	/* We just care about event not existing */
	if (errno != ENODEV)
		return;

	test_field_exists(tep, sb, expr);
}

static void compare_error(struct tep_handle *tep,
			    struct sqlhist_bison *sb, struct expr *expr)
{
	struct compare *compare = &expr->compare;

	if (!compare->name) {
		sb->line_no = expr->line;
		sb->line_idx = expr->idx + strlen("no name");

		parse_error(sb, "no name",
		    "Field calculations must be labeled 'AS name'\n");
	}

	switch (errno) {
	case ENODEV:
	case EBADE:
		break;
	default:
		/* System error */
		return;
	}

	/* ENODEV means that an event or field does not exist */
	if (errno == ENODEV) {
		if (test_field_exists(tep, sb, compare->lval))
			return;
		if (test_field_exists(tep, sb, compare->rval))
			return;
		return;
	}

	/* fields exist, but values are not compatible */
	sb->line_no = compare->lval->line;
	sb->line_idx = compare->lval->idx;

	parse_error(sb, compare->lval->field.raw,
		    "'%s' is not compatible to compare with '%s'\n",
		    compare->lval->field.raw, compare->rval->field.raw);
}

static void compare_no_to_error(struct sqlhist_bison *sb, struct expr *expr)
{
	struct compare *compare = &expr->compare;

	sb->line_no = compare->lval->line;
	sb->line_idx = compare->lval->idx;

	parse_error(sb, compare->lval->field.raw,
		    "Simple SQL (without JOIN/ON) do not allow comparisons\n",
		    compare->lval->field.raw, compare->rval->field.raw);
}

static void where_no_to_error(struct sqlhist_bison *sb, struct expr *expr,
			      const char *from_event, const char *event)
{
	while (expr) {
		switch (expr->filter.type) {
		case FILTER_OR:
		case FILTER_AND:
		case FILTER_GROUP:
		case FILTER_NOT_GROUP:
			expr = expr->filter.lval;
			continue;
		default:
			break;
		}
		break;
	}
	sb->line_no = expr->filter.lval->line;
	sb->line_idx = expr->filter.lval->idx;

	parse_error(sb, expr->filter.lval->field.raw,
		    "Event '%s' does not match FROM event '%s'\n",
		    event, from_event);
}

static int verify_field_type(struct tep_handle *tep,
			     struct sqlhist_bison *sb,
			     struct expr *expr, int *cnt)
{
	struct field *field = &expr->field;
	struct tep_event *event;
	struct tep_format_field *tfield;
	char *type;
	int ret;
	int i;

	if (!field->type)
		return 0;

	sb->line_no = expr->line;
	sb->line_idx = expr->idx;

	event = tep_find_event_by_name(tep, field->system, field->event_name);
	if (!event) {
		parse_error(sb, field->raw,
			    "Event '%s' not found\n",
			    field->event_name ? : "(null)");
		return -1;
	}

	tfield = tep_find_any_field(event, field->field);
	if (!tfield) {
		parse_error(sb, field->raw,
			    "Field '%s' not part of event '%s'\n",
			    field->field ? : "(null)", field->event);
		return -1;
	}

	type = strdup(field->type);
	if (!type)
		return -1;

	if (!strcmp(type, TRACEFS_HIST_COUNTER) ||
	    !strcmp(type, "_COUNTER_")) {
		ret = HIST_COUNTER_TYPE;
		if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY)) {
			parse_error(sb, field->raw,
				    "'%s' is a string, and counters may only be used with numbers\n");
			ret = -1;
		}
		goto out;
	}

	for (i = 0; type[i]; i++)
		type[i] = tolower(type[i]);

	if (!strcmp(type, "hex")) {
		if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY))
			goto fail_type;
		ret = TRACEFS_HIST_KEY_HEX;
	} else if (!strcmp(type, "sym")) {
		if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY))
			goto fail_type;
		ret = TRACEFS_HIST_KEY_SYM;
	} else if (!strcmp(type, "sym-offset")) {
		if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY))
			goto fail_type;
		ret = TRACEFS_HIST_KEY_SYM_OFFSET;
	} else if (!strcmp(type, "syscall")) {
		if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY))
			goto fail_type;
		ret = TRACEFS_HIST_KEY_SYSCALL;
	} else if (!strcmp(type, "execname") ||
		   !strcmp(type, "comm")) {
		ret = TRACEFS_HIST_KEY_EXECNAME;
		if (strcmp(field->field, "common_pid")) {
			parse_error(sb, field->raw,
				    "'%s' is only allowed for common_pid\n",
				    type);
			ret = -1;
		}
	} else if (!strcmp(type, "log") ||
		   !strcmp(type, "log2")) {
		if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY))
			goto fail_type;
		ret = TRACEFS_HIST_KEY_LOG;
	} else if (!strncmp(type, "buckets", 7)) {
		if (type[7] != '=' || !isdigit(type[8])) {
			parse_error(sb, field->raw,
				    "buckets type must have '=[number]' after it\n");
			ret = -1;
			goto out;
		}
		*cnt = atoi(&type[8]);
		if (tfield->flags & (TEP_FIELD_IS_STRING | TEP_FIELD_IS_ARRAY))
			goto fail_type;
		ret = TRACEFS_HIST_KEY_BUCKETS;
	} else if (!strcmp(type, "stacktrace")) {
		if (!(tfield->flags & (TEP_FIELD_IS_ARRAY)))
			goto fail_type;
		ret = TRACEFS_HIST_KEY_STACKTRACE;
	} else {
		parse_error(sb, field->raw,
			    "Cast of '%s' to unknown type '%s'\n",
			    field->raw, type);
		ret = -1;
	}
 out:
	free(type);
	return ret;
 fail_type:
	parse_error(sb, field->raw,
		    "Field '%s' cast to '%s' but is of type %s\n",
		    field->field, type, tfield->flags & TEP_FIELD_IS_STRING ?
		    "string" : "array");
	free(type);
	return -1;
}

static struct tracefs_synth *build_synth(struct tep_handle *tep,
					 const char *name,
					 struct sql_table *table)
{
	struct tracefs_synth *synth;
	struct field *field;
	struct match *match;
	struct expr *expr;
	const char *start_system;
	const char *start_event;
	const char *end_system;
	const char *end_event;
	const char *start_match;
	const char *end_match;
	bool started_start = false;
	bool started_end = false;
	bool non_val = false;
	int ret;

	if (!table->from)
		return NULL;

	/* This could be a simple SQL statement to only build a histogram */
	if (!table->to) {
		ret = update_fields(tep, table, table->from);
		if (ret < 0)
			return NULL;

		start_system = table->from->field.system;
		start_event = table->from->field.event_name;

		synth = tfs_synth_init_from(tep, start_system, start_event);
		if (!synth)
			return synth_init_error(tep, table);
		goto hist_only;
	}

	ret = update_vars(tep, table, table->from);
	if (ret < 0)
		return NULL;

	ret = update_vars(tep, table, table->to);
	if (ret < 0)
		return NULL;

	start_system = table->from->field.system;
	start_event = table->from->field.event_name;

	match = table->matches;
	if (!match)
		return NULL;

	ret = test_match(table, match);
	if (ret < 0)
		return NULL;

	end_system = table->to->field.system;
	end_event = table->to->field.event_name;

	assign_match(start_system, start_event, match,
		     &start_match, &end_match);

	synth = tracefs_synth_alloc(tep, name, start_system,
				    start_event, end_system, end_event,
				    start_match, end_match, NULL);
	if (!synth)
		return synth_init_error(tep, table);

	for (match = match->next; match; match = match->next) {
		ret = test_match(table, match);
		if (ret < 0)
			goto free;

		assign_match(start_system, start_event, match,
			     &start_match, &end_match);

		ret = tracefs_synth_add_match_field(synth,
						    start_match,
						    end_match, NULL);
		if (ret < 0) {
			field_match_error(tep, table->sb, match);
			goto free;
		}
	}

 hist_only:
	/* table->to may be NULL here */

	for (expr = table->selections; expr; expr = expr->next) {
		if (expr->type == EXPR_FIELD) {
			ret = -1;
			field = &expr->field;
			if (field->ftype != FIELD_TO &&
			    field->system == start_system &&
			    field->event_name == start_event) {
				int type;
				int cnt = 0;
				type = verify_field_type(tep, table->sb, expr, &cnt);
				if (type < 0)
					goto free;
				if (type != HIST_COUNTER_TYPE)
					non_val = true;
				ret = tfs_synth_add_start_field(synth,
						field->field, field->label,
						type, cnt);
			} else if (table->to) {
				ret = tracefs_synth_add_end_field(synth,
						field->field, field->label);
			}
			if (ret < 0) {
				selection_error(tep, table->sb, expr);
				goto free;
			}
			continue;
		}

		if (!table->to) {
			compare_no_to_error(table->sb, expr);
			goto free;
		}

		if (expr->type != EXPR_COMPARE)
			goto free;

		ret = build_compare(synth, start_system, end_system,
				    &expr->compare);
		if (ret < 0) {
			compare_error(tep, table->sb, expr);
			goto free;
		}
	}

	if (!non_val && !table->to) {
		table->sb->line_no = 0;
		table->sb->line_idx = 10;
		parse_error(table->sb, "CAST",
			    "Not all SELECT items can be of type _COUNTER_\n");
		goto free;
	}

	for (expr = table->where; expr; expr = expr->next) {
		const char *filter_system = NULL;
		const char *filter_event = NULL;
		enum field_type ftype = FIELD_NONE;
		bool *started;
		bool start;

		ret = verify_filter(table->sb, &expr->filter, &filter_system,
				    &filter_event, &ftype);
		if (ret < 0)
			goto free;

		start = filter_system == start_system &&
			filter_event == start_event &&
			ftype != FIELD_TO;

		if (start)
			started = &started_start;
		else if (!table->to) {
			where_no_to_error(table->sb, expr, start_event,
					  filter_event);
			goto free;
		} else
			started = &started_end;

		ret = build_filter(tep, table->sb, synth, start, expr, started);
		if (ret < 0)
			goto free;
	}

	return synth;
 free:
	tracefs_synth_free(synth);
	return NULL;
}

static void free_sql_table(struct sql_table *table)
{
	struct match *match;
	struct expr *expr;

	if (!table)
		return;

	while ((expr = table->exprs)) {
		table->exprs = expr->free_list;
		free(expr);
	}

	while ((match = table->matches)) {
		table->matches = match->next;
		free(match);
	}

	free(table);
}

static void free_str_hash(struct str_hash **hash)
{
	struct str_hash *item;
	int i;

	for (i = 0; i < 1 << HASH_BITS; i++) {
		while ((item = hash[i])) {
			hash[i] = item->next;
			free(item->str);
			free(item);
		}
	}
}

static void free_sb(struct sqlhist_bison *sb)
{
	free_sql_table(sb->table);
	free_str_hash(sb->str_hash);
	free(sb->parse_error_str);
}

struct tracefs_synth *tracefs_sql(struct tep_handle *tep, const char *name,
				  const char *sql_buffer, char **err)
{
	struct tracefs_synth *synth = NULL;
	struct sqlhist_bison sb;
	int ret;

	if (!tep || !sql_buffer) {
		errno = EINVAL;
		return NULL;
	}

	memset(&sb, 0, sizeof(sb));

	sb.buffer = sql_buffer;
	sb.buffer_size = strlen(sql_buffer);
	sb.buffer_idx = 0;

	ret = yylex_init_extra(&sb, &sb.scanner);
	if (ret < 0) {
		yylex_destroy(sb.scanner);
		return NULL;
	}

	ret = tracefs_parse(&sb);
	yylex_destroy(sb.scanner);

	if (ret)
		goto free;

	synth = build_synth(tep, name, sb.table);

 free:
	if (!synth) {
		if (sb.parse_error_str && err) {
			*err = sb.parse_error_str;
			sb.parse_error_str = NULL;
		}
	}
	free_sb(&sb);
	return synth;
}
