diff --git a/src/sqlhist-parse.h b/src/sqlhist-parse.h
index aeb1738d693f..0933bfe9a574 100644
--- a/src/sqlhist-parse.h
+++ b/src/sqlhist-parse.h
@@ -57,13 +57,19 @@ int table_start(struct sqlhist_bison *sb);
 
 void *add_field(struct sqlhist_bison *sb, const char *field, const char *label);
 
+void *add_filter(struct sqlhist_bison *sb, void *A, void *B, enum filter_type op);
+
 int add_match(struct sqlhist_bison *sb, void *A, void *B);
 void *add_compare(struct sqlhist_bison *sb, void *A, void *B, enum compare_type type);
+int add_where(struct sqlhist_bison *sb, void *expr);
 
 int add_selection(struct sqlhist_bison *sb, void *item, const char *label);
 int add_from(struct sqlhist_bison *sb, void *item);
 int add_to(struct sqlhist_bison *sb, void *item);
 
+void *add_string(struct sqlhist_bison *sb, const char *str);
+void *add_number(struct sqlhist_bison *sb, long val);
+
 extern void sql_parse_error(struct sqlhist_bison *sb, const char *text,
 			    const char *fmt, va_list ap);
 
diff --git a/src/sqlhist.l b/src/sqlhist.l
index 5dfd17517c6e..5baba0992afc 100644
--- a/src/sqlhist.l
+++ b/src/sqlhist.l
@@ -32,6 +32,7 @@ as { HANDLE_COLUMN; return AS; }
 from { HANDLE_COLUMN; return FROM; }
 join { HANDLE_COLUMN; return JOIN; }
 on { HANDLE_COLUMN; return ON; }
+where { HANDLE_COLUMN; return WHERE; }
 
 {qstring} {
 	HANDLE_COLUMN;
diff --git a/src/sqlhist.y b/src/sqlhist.y
index 1ba3bf62ee43..8dcc824bb9f1 100644
--- a/src/sqlhist.y
+++ b/src/sqlhist.y
@@ -34,7 +34,7 @@ extern void yyerror(char *fmt, ...);
 	void	*expr;
 }
 
-%token AS SELECT FROM JOIN ON PARSE_ERROR
+%token AS SELECT FROM JOIN ON WHERE PARSE_ERROR
 %token <number> NUMBER
 %token <string> STRING
 %token <string> FIELD
@@ -49,6 +49,9 @@ extern void yyerror(char *fmt, ...);
 
 %type <expr>  selection_expr field item named_field join_clause
 %type <expr>  selection_addition
+%type <expr>  compare compare_list compare_cmds compare_items
+%type <expr>  compare_and_or
+%type <expr>  str_val val
 
 %%
 
@@ -120,8 +123,71 @@ name :
    FIELD
  ;
 
+str_val :
+   STRING	{ $$ = add_string(sb, $1); CHECK_RETURN_PTR($$); }
+ ;
+
+val :
+   str_val
+ | NUMBER	{ $$ = add_number(sb, $1); CHECK_RETURN_PTR($$); }
+ ;
+
+
+compare :
+   field '<' val	{ $$ = add_filter(sb, $1, $3, FILTER_LT); CHECK_RETURN_PTR($$); }
+ | field '>' val	{ $$ = add_filter(sb, $1, $3, FILTER_GT); CHECK_RETURN_PTR($$); }
+ | field LE val	{ $$ = add_filter(sb, $1, $3, FILTER_LE); CHECK_RETURN_PTR($$); }
+ | field GE val	{ $$ = add_filter(sb, $1, $3, FILTER_GE); CHECK_RETURN_PTR($$); }
+ | field '=' val	{ $$ = add_filter(sb, $1, $3, FILTER_EQ); CHECK_RETURN_PTR($$); }
+ | field EQ val	{ $$ = add_filter(sb, $1, $3, FILTER_EQ); CHECK_RETURN_PTR($$); }
+ | field NEQ val	{ $$ = add_filter(sb, $1, $3, FILTER_NE); CHECK_RETURN_PTR($$); }
+ | field "!=" val	{ $$ = add_filter(sb, $1, $3, FILTER_NE); CHECK_RETURN_PTR($$); }
+ | field '&' val	{ $$ = add_filter(sb, $1, $3, FILTER_BIN_AND); CHECK_RETURN_PTR($$); }
+ | field '~' str_val	{ $$ = add_filter(sb, $1, $3, FILTER_STR_CMP); CHECK_RETURN_PTR($$); }
+;
+
+compare_and_or :
+   compare_and_or OR compare_and_or	{ $$ = add_filter(sb, $1, $3, FILTER_OR); CHECK_RETURN_PTR($$); }
+ | compare_and_or AND compare_and_or	{ $$ = add_filter(sb, $1, $3, FILTER_AND); CHECK_RETURN_PTR($$); }
+ | '!' '(' compare_and_or ')'		{ $$ = add_filter(sb, $3, NULL, FILTER_NOT_GROUP); CHECK_RETURN_PTR($$); }
+ | '!' compare				{ $$ = add_filter(sb, $2, NULL, FILTER_NOT_GROUP); CHECK_RETURN_PTR($$); }
+ | compare
+ ;
+
+compare_items :
+   compare_items OR compare_items	{ $$ = add_filter(sb, $1, $3, FILTER_OR); CHECK_RETURN_PTR($$); }
+ | '(' compare_and_or ')'		{ $$ = add_filter(sb, $2, NULL, FILTER_GROUP); CHECK_RETURN_PTR($$); }
+ | '!' '(' compare_and_or ')'		{ $$ = add_filter(sb, $3, NULL, FILTER_NOT_GROUP); CHECK_RETURN_PTR($$); }
+ | '!' compare				{ $$ = add_filter(sb, $2, NULL, FILTER_NOT_GROUP); CHECK_RETURN_PTR($$); }
+ | compare
+ ;
+
+compare_cmds :
+   compare_items		{ CHECK_RETURN_VAL(add_where(sb, $1)); }
+ ;
+
+/*
+ * Top level AND is equal to ',' but the compare_cmds in them must
+ * all be of for the same event (start or end exclusive).
+ * That is, OR is not to be used between start and end events.
+ */
+compare_list :
+   compare_cmds
+ | compare_cmds ',' compare_list
+ | compare_cmds AND compare_list
+ ;
+
+where_clause :
+   WHERE compare_list
+ ;
+
+opt_where_clause :
+   /* empty */
+ | where_clause
+;
+
 table_exp :
-   from_clause join_clause
+   from_clause join_clause opt_where_clause
  ;
 
 from_clause :
diff --git a/src/tracefs-sqlhist.c b/src/tracefs-sqlhist.c
index cf2661773679..e47bc57c5add 100644
--- a/src/tracefs-sqlhist.c
+++ b/src/tracefs-sqlhist.c
@@ -90,6 +90,8 @@ struct sql_table {
 	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;
@@ -317,9 +319,18 @@ static void *create_expr(enum expr_type type, struct expr **expr_p)
 #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 *add_field(struct sqlhist_bison *sb,
 			 const char *field_name, const char *label)
 {
@@ -342,6 +353,22 @@ __hidden void *add_field(struct sqlhist_bison *sb,
 	return expr;
 }
 
+__hidden void *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 add_match(struct sqlhist_bison *sb, void *A, void *B)
 {
 	struct sql_table *table = sb->table;
@@ -375,6 +402,23 @@ __hidden void *add_compare(struct sqlhist_bison *sb,
 	return expr;
 }
 
+__hidden int 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 add_from(struct sqlhist_bison *sb, void *item)
 {
 	struct expr *expr = item;
@@ -399,6 +443,34 @@ __hidden int add_to(struct sqlhist_bison *sb, void *item)
 	return 0;
 }
 
+__hidden void *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 *add_number(struct sqlhist_bison *sb, long val)
+{
+	struct expr *expr;
+	long *num;
+
+	create_number(num, &expr);
+	*num = val;
+	return expr;
+
+	expr = calloc(1, sizeof(expr));
+	if (!expr)
+		return NULL;
+
+	expr->type = EXPR_NUMBER;
+	expr->number = val;
+	return expr;
+}
+
 __hidden int table_start(struct sqlhist_bison *sb)
 {
 	struct sql_table *table;
@@ -410,6 +482,7 @@ __hidden int table_start(struct sqlhist_bison *sb)
 	table->sb = sb;
 	sb->table = table;
 
+	table->next_where = &table->where;
 	table->next_match = &table->matches;
 	table->next_selection = &table->selections;
 
@@ -598,6 +671,167 @@ static int build_compare(struct tracefs_synth *synth,
 	return ret;
 }
 
+static int do_verify_filter(struct filter *filter,
+			    const char **system, const char **event)
+{
+	int ret;
+
+	if (filter->type == FILTER_OR ||
+	    filter->type == FILTER_AND) {
+		ret = do_verify_filter(&filter->lval->filter, system, event);
+		if (ret)
+			return ret;
+		return do_verify_filter(&filter->rval->filter, system, event);
+	}
+	if (filter->type == FILTER_GROUP ||
+	    filter->type == FILTER_NOT_GROUP) {
+		return do_verify_filter(&filter->lval->filter, system, event);
+	}
+
+	/*
+	 * 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;
+		return 0;
+	}
+
+	if (filter->lval->field.system != *system ||
+	    filter->lval->field.event != *event)
+		return -1;
+
+	return 0;
+}
+
+static int verify_filter(struct filter *filter,
+			 const char **system, const char **event)
+{
+	int ret;
+
+	switch (filter->type) {
+	case FILTER_OR:
+	case FILTER_AND:
+	case FILTER_GROUP:
+	case FILTER_NOT_GROUP:
+		break;
+	default:
+		return do_verify_filter(filter, system, event);
+	}
+
+	ret = do_verify_filter(&filter->lval->filter, system, event);
+	if (ret)
+		return ret;
+
+	switch (filter->type) {
+	case FILTER_OR:
+	case FILTER_AND:
+		return do_verify_filter(&filter->rval->filter, system, event);
+	default:
+		return 0;
+	}
+}
+
+static int build_filter(struct tracefs_synth *synth,
+			bool start, struct filter *filter, bool *started)
+{
+	int (*append_filter)(struct tracefs_synth *synth,
+			     enum tracefs_filter type,
+			     const char *field,
+			     enum tracefs_compare compare,
+			     const char *val);
+	enum tracefs_compare cmp;
+	const char *val;
+	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(synth, start, &filter->lval->filter, 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(synth, start, &filter->lval->filter, NULL);
+		if (ret < 0)
+			goto out;
+		ret = append_filter(synth, and_or, NULL, 0, NULL);
+
+		if (ret)
+			goto out;
+		ret = build_filter(synth, start, &filter->rval->filter, 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:
+		break;
+	}
+
+	ret = append_filter(synth, TRACEFS_FILTER_COMPARE,
+			    filter->lval->field.field, cmp, val);
+
+ out:
+	if (!ret && started) {
+		if (*started)
+			ret = append_filter(synth, TRACEFS_FILTER_CLOSE_PAREN,
+					    NULL, 0, NULL);
+		*started = true;
+	}
+	return ret;
+}
+
 static struct tracefs_synth *build_synth(struct tep_handle *tep,
 					 const char *name,
 					 struct sql_table *table)
@@ -612,6 +846,8 @@ static struct tracefs_synth *build_synth(struct tep_handle *tep,
 	const char *end_event;
 	const char *start_match;
 	const char *end_match;
+	bool started_start = false;
+	bool started_end = false;
 	int ret;
 
 	if (!table->to || !table->from)
@@ -688,6 +924,30 @@ static struct tracefs_synth *build_synth(struct tep_handle *tep,
 			goto free;
 	}
 
+	for (expr = table->where; expr; expr = expr->next) {
+		const char *filter_system = NULL;
+		const char *filter_event = NULL;
+		bool *started;
+		bool start;
+
+		ret = verify_filter(&expr->filter, &filter_system,
+				    &filter_event);
+		if (ret < 0)
+			goto free;
+
+		start = filter_system == start_system &&
+			filter_event == start_event;
+
+		if (start)
+			started = &started_start;
+		else
+			started = &started_end;
+
+		ret = build_filter(synth, start, &expr->filter, started);
+		if (ret < 0)
+			goto free;
+	}
+
 	return synth;
  free:
 	tracefs_synth_free(synth);
