Merge pull request #376 from influxdb/list-series-376

"list series" should allow filtering using a regular expression
pull/820/head
Paul Dix 2014-08-12 13:17:51 -04:00
commit 23b7ae02ce
9 changed files with 153 additions and 13 deletions

View File

@ -170,12 +170,23 @@ func (self *CoordinatorImpl) runQuery(querySpec *parser.QuerySpec, seriesWriter
} }
func (self *CoordinatorImpl) runListSeriesQuery(querySpec *parser.QuerySpec, seriesWriter SeriesWriter) error { func (self *CoordinatorImpl) runListSeriesQuery(querySpec *parser.QuerySpec, seriesWriter SeriesWriter) error {
series := self.clusterConfiguration.MetaStore.GetSeriesForDatabase(querySpec.Database()) allSeries := self.clusterConfiguration.MetaStore.GetSeriesForDatabase(querySpec.Database())
matchingSeries := allSeries
if q := querySpec.Query().GetListSeriesQuery(); q.HasRegex() {
matchingSeries = nil
regex := q.GetRegex()
for _, s := range allSeries {
if !regex.MatchString(s) {
continue
}
matchingSeries = append(matchingSeries, s)
}
}
name := "list_series_result" name := "list_series_result"
fields := []string{"name"} fields := []string{"name"}
points := make([]*protocol.Point, len(series), len(series)) points := make([]*protocol.Point, len(matchingSeries), len(matchingSeries))
for i, s := range series { for i, s := range matchingSeries {
fieldValues := []*protocol.FieldValue{{StringValue: proto.String(s)}} fieldValues := []*protocol.FieldValue{{StringValue: proto.String(s)}}
points[i] = &protocol.Point{Values: fieldValues} points[i] = &protocol.Point{Values: fieldValues}
} }

View File

@ -106,6 +106,42 @@ func (self *SingleServerSuite) TestDroppingSeries(c *C) {
c.Assert(maps[0]["column1"], Equals, 1.0) c.Assert(maps[0]["column1"], Equals, 1.0)
} }
// issue #376
func (self *SingleServerSuite) TestListSeriesRegex(c *C) {
client := self.server.GetClient("", c)
c.Assert(client.CreateDatabase("test_list_series_regex"), IsNil)
c.Assert(client.CreateDatabaseUser("test_list_series_regex", "user", "pass"), IsNil)
user := self.server.GetClientWithUser("test_list_series_regex", "user", "pass", c)
for _, n := range []string{"foo1", "foo2", "foobar"} {
err := user.WriteSeries([]*influxdb.Series{{
Name: n,
Columns: []string{"column1"},
Points: [][]interface{}{{1}},
}})
c.Assert(err, IsNil)
}
for _, r := range []string{"/Foo\\d+/i", "/foo\\d+/"} {
s, err := user.Query(fmt.Sprintf("list series %s", r))
c.Assert(err, IsNil)
c.Assert(s, HasLen, 1)
maps := ToMap(s[0])
c.Assert(maps, HasLen, 2)
names := map[string]bool{}
for _, p := range maps {
names[p["name"].(string)] = true
}
c.Assert(names["foo1"], Equals, true)
c.Assert(names["foo2"], Equals, true)
}
s, err := user.Query("list series")
c.Assert(err, IsNil)
c.Assert(s, HasLen, 1)
maps := ToMap(s[0])
c.Assert(maps, HasLen, 3)
}
// pr #483 // pr #483
func (self *SingleServerSuite) TestConflictStatusCode(c *C) { func (self *SingleServerSuite) TestConflictStatusCode(c *C) {
client := self.server.GetClient("", c) client := self.server.GetClient("", c)

View File

@ -145,6 +145,12 @@ close_query (query *q)
free(q->select_query); free(q->select_query);
} }
if (q->list_series_query) {
if (q->list_series_query->has_regex)
free_value(q->list_series_query->regex);
free(q->list_series_query);
}
if (q->drop_series_query) { if (q->drop_series_query) {
free_drop_series_query(q->drop_series_query); free_drop_series_query(q->drop_series_query);
free(q->drop_series_query); free(q->drop_series_query);

View File

@ -55,10 +55,12 @@ type ListType int
const ( const (
Series ListType = iota Series ListType = iota
ContinuousQueries ContinuousQueries
SeriesWithRegex
) )
type ListQuery struct { type ListQuery struct {
Type ListType Type ListType
value *Value
} }
type DropQuery struct { type DropQuery struct {
@ -126,14 +128,31 @@ func (self *Query) IsExplainQuery() bool {
return self.SelectQuery != nil && self.SelectQuery.Explain return self.SelectQuery != nil && self.SelectQuery.Explain
} }
func (self *Query) GetListSeriesQuery() *ListQuery {
return self.ListQuery
}
func (self *Query) IsListSeriesQuery() bool { func (self *Query) IsListSeriesQuery() bool {
return self.ListQuery != nil && self.ListQuery.Type == Series return self.ListQuery != nil && (self.ListQuery.Type == Series || self.ListQuery.Type == SeriesWithRegex)
} }
func (self *Query) IsListContinuousQueriesQuery() bool { func (self *Query) IsListContinuousQueriesQuery() bool {
return self.ListQuery != nil && self.ListQuery.Type == ContinuousQueries return self.ListQuery != nil && self.ListQuery.Type == ContinuousQueries
} }
func (self *ListQuery) HasRegex() bool {
return self.Type == SeriesWithRegex
}
func (self *ListQuery) IsCaseSensitive() bool {
return self.value.IsInsensitive
}
func (self *ListQuery) GetRegex() *regexp.Regexp {
regex, _ := self.value.GetCompiledRegex()
return regex
}
func (self *DeleteQuery) GetQueryString(withTime bool) string { func (self *DeleteQuery) GetQueryString(withTime bool) string {
buffer := bytes.NewBufferString("delete ") buffer := bytes.NewBufferString("delete ")
fmt.Fprintf(buffer, "from %s", self.FromClause.GetString()) fmt.Fprintf(buffer, "from %s", self.FromClause.GetString())
@ -596,8 +615,18 @@ func ParseQuery(query string) ([]*Query, error) {
} }
} }
if q.list_series_query != 0 { if q.list_series_query != nil {
return []*Query{{QueryString: query, ListQuery: &ListQuery{Type: Series}}}, nil var value *Value
var err error
t := Series
if q.list_series_query.has_regex != 0 {
t = SeriesWithRegex
value, err = GetValue(q.list_series_query.regex)
if err != nil {
return nil, err
}
}
return []*Query{{QueryString: query, ListQuery: &ListQuery{Type: t, value: value}}}, nil
} }
if q.list_continuous_queries_query != 0 { if q.list_continuous_queries_query != 0 {

View File

@ -2,6 +2,7 @@ package parser
import ( import (
"fmt" "fmt"
"regexp"
"testing" "testing"
"time" "time"
@ -269,11 +270,41 @@ func (self *QueryParserSuite) TestParseFromWithJoinedTable(c *C) {
c.Assert(fromClause.Names[1].Name.Name, Equals, "user.signups") c.Assert(fromClause.Names[1].Name.Name, Equals, "user.signups")
} }
func (self *QueryParserSuite) TestIncompleteRegex(c *C) {
_, err := ParseQuery("list series /")
c.Assert(err, NotNil)
}
func (self *QueryParserSuite) TestParseListSeries(c *C) { func (self *QueryParserSuite) TestParseListSeries(c *C) {
queries, err := ParseQuery("list series") queries, err := ParseQuery("list series")
c.Assert(err, IsNil) c.Assert(err, IsNil)
c.Assert(queries, HasLen, 1) c.Assert(queries, HasLen, 1)
c.Assert(queries[0].IsListQuery(), Equals, true) c.Assert(queries[0].IsListQuery(), Equals, true)
listSeriesQuery := queries[0].GetListSeriesQuery()
c.Assert(listSeriesQuery, NotNil)
c.Assert(listSeriesQuery.HasRegex(), Equals, false)
// test the case sensitive and case insensitive list series
for i := 0; i < 2; i++ {
query := "list series /^foo.*/"
if i == 1 {
query += "i"
}
queries, err = ParseQuery(query)
c.Assert(err, IsNil)
c.Assert(queries, HasLen, 1)
c.Assert(queries[0].IsListSeriesQuery(), Equals, true)
listSeriesQuery = queries[0].GetListSeriesQuery()
c.Assert(listSeriesQuery, NotNil)
c.Assert(listSeriesQuery.HasRegex(), Equals, true)
var regularExpression *regexp.Regexp
if i == 1 {
regularExpression, _ = regexp.Compile("(?i)^foo.*")
} else {
regularExpression, _ = regexp.Compile("^foo.*")
}
c.Assert(listSeriesQuery.GetRegex(), DeepEquals, regularExpression)
}
} }
// issue #267 // issue #267

View File

@ -21,6 +21,7 @@ static int yycolumn = 1;
%option bison-locations %option bison-locations
%option noyywrap %option noyywrap
%s FROM_CLAUSE REGEX_CONDITION %s FROM_CLAUSE REGEX_CONDITION
%x LIST_SERIES
%x IN_REGEX %x IN_REGEX
%x IN_TABLE_NAME %x IN_TABLE_NAME
%x IN_SIMPLE_NAME %x IN_SIMPLE_NAME
@ -30,17 +31,22 @@ static int yycolumn = 1;
, { return *yytext; } , { return *yytext; }
"merge" { return MERGE; } "merge" { return MERGE; }
"list" { return LIST; } "list" { return LIST; }
"series" { return SERIES; } "series" { BEGIN(LIST_SERIES); return SERIES; }
"continuous query" { return CONTINUOUS_QUERY; } "continuous query" { return CONTINUOUS_QUERY; }
"continuous queries" { return CONTINUOUS_QUERIES; } "continuous queries" { return CONTINUOUS_QUERIES; }
"inner" { return INNER; } "inner" { return INNER; }
"join" { return JOIN; } "join" { return JOIN; }
"from" { BEGIN(FROM_CLAUSE); return FROM; } "from" { BEGIN(FROM_CLAUSE); return FROM; }
<FROM_CLAUSE,REGEX_CONDITION>\/ { BEGIN(IN_REGEX); yylval->string=calloc(1, sizeof(char)); } <LIST_SERIES,FROM_CLAUSE,REGEX_CONDITION>\/ { BEGIN(IN_REGEX); yylval->string=calloc(1, sizeof(char)); }
<IN_REGEX>\\\/ { <IN_REGEX>\\\/ {
yylval->string = realloc(yylval->string, strlen(yylval->string) + 2); yylval->string = realloc(yylval->string, strlen(yylval->string) + 2);
strcat(yylval->string, "/"); strcat(yylval->string, "/");
} }
<IN_REGEX><<EOF>> {
free(yylval->string);
BEGIN(INITIAL);
return UNKNOWN;
}
<IN_REGEX>\\ { <IN_REGEX>\\ {
yylval->string = realloc(yylval->string, strlen(yylval->string) + 2); yylval->string = realloc(yylval->string, strlen(yylval->string) + 2);
strcat(yylval->string, "\\"); strcat(yylval->string, "\\");

View File

@ -75,7 +75,7 @@ value *create_expression_value(char *operator, size_t size, ...) {
%lex-param {void *scanner} %lex-param {void *scanner}
// define types of tokens (terminals) // define types of tokens (terminals)
%token SELECT DELETE FROM WHERE EQUAL GROUP BY LIMIT ORDER ASC DESC MERGE INNER JOIN AS LIST SERIES INTO CONTINUOUS_QUERIES CONTINUOUS_QUERY DROP DROP_SERIES EXPLAIN %token SELECT DELETE FROM WHERE EQUAL GROUP BY LIMIT ORDER ASC DESC MERGE INNER JOIN AS LIST SERIES INTO CONTINUOUS_QUERIES CONTINUOUS_QUERY DROP DROP_SERIES EXPLAIN UNKNOWN
%token <string> STRING_VALUE INT_VALUE FLOAT_VALUE BOOLEAN_VALUE TABLE_NAME SIMPLE_NAME INTO_NAME REGEX_OP %token <string> STRING_VALUE INT_VALUE FLOAT_VALUE BOOLEAN_VALUE TABLE_NAME SIMPLE_NAME INTO_NAME REGEX_OP
%token <string> NEGATION_REGEX_OP REGEX_STRING INSENSITIVE_REGEX_STRING DURATION %token <string> NEGATION_REGEX_OP REGEX_STRING INSENSITIVE_REGEX_STRING DURATION
@ -165,7 +165,15 @@ QUERY:
LIST SERIES LIST SERIES
{ {
$$ = calloc(1, sizeof(query)); $$ = calloc(1, sizeof(query));
$$->list_series_query = TRUE; $$->list_series_query = calloc(1, sizeof(list_series_query));
}
|
LIST SERIES REGEX_VALUE
{
$$ = calloc(1, sizeof(query));
$$->list_series_query = calloc(1, sizeof(list_series_query));
$$->list_series_query->has_regex = TRUE;
$$->list_series_query->regex = $3;
} }
| |
DROP_SERIES_QUERY DROP_SERIES_QUERY

View File

@ -3,7 +3,7 @@
#define FALSE 0 #define FALSE 0
#define TRUE !FALSE #define TRUE !FALSE
// #define DEBUG /* #define DEBUG */
typedef struct { typedef struct {
size_t size; size_t size;
@ -95,6 +95,11 @@ typedef struct {
char explain; char explain;
} select_query; } select_query;
typedef struct {
char has_regex;
value *regex;
} list_series_query;
typedef struct { typedef struct {
from_clause *from_clause; from_clause *from_clause;
condition *where_condition; condition *where_condition;
@ -114,7 +119,7 @@ typedef struct {
delete_query *delete_query; delete_query *delete_query;
drop_series_query *drop_series_query; drop_series_query *drop_series_query;
drop_query *drop_query; drop_query *drop_query;
char list_series_query; list_series_query *list_series_query;
char list_continuous_queries_query; char list_continuous_queries_query;
error *error; error *error;
} query; } query;

View File

@ -14,6 +14,14 @@ int main(int argc, char **argv) {
q = parse_query("select * from foo where time < -1s"); q = parse_query("select * from foo where time < -1s");
close_query(&q); close_query(&q);
// test partial regex
q = parse_query("list series /");
close_query(&q);
// test freeing list series query
q = parse_query("list series /foo/ bar");
close_query(&q);
// test freeing on error // test freeing on error
q = parse_query("select count(*) from users.events group_by user_email,time(1h) where time >> now()-1d;"); q = parse_query("select count(*) from users.events group_by user_email,time(1h) where time >> now()-1d;");
close_query(&q); close_query(&q);