|
@@ -53,61 +53,123 @@ func PlanWithSourcesAndSinks(rule *api.Rule, storePath string, sources []*nodes.
|
|
|
return tp, nil
|
|
|
}
|
|
|
|
|
|
-func decorateStmt(s *xsql.SelectStatement, ss []*xsql.StreamStmt, alias xsql.Fields, aggregateAlias xsql.Fields) (err error) {
|
|
|
+type aliasInfo struct {
|
|
|
+ alias xsql.Field
|
|
|
+ refSources []string
|
|
|
+ isAggregate bool
|
|
|
+}
|
|
|
+
|
|
|
+// Analyze the select statement by decorating the info from stream statement.
|
|
|
+// Typically, set the correct stream name for fieldRefs
|
|
|
+func decorateStmt(s *xsql.SelectStatement, store kv.KeyValue) ([]*xsql.StreamStmt, map[string]*aliasInfo, error) {
|
|
|
+ streamsFromStmt := xsql.GetStreams(s)
|
|
|
+ streamStmts := make([]*xsql.StreamStmt, len(streamsFromStmt))
|
|
|
+ aliasSourceMap := make(map[string]*aliasInfo)
|
|
|
isSchemaless := false
|
|
|
- for _, streamStmt := range ss {
|
|
|
+ for i, s := range streamsFromStmt {
|
|
|
+ streamStmt, err := xsql.GetDataSource(store, s)
|
|
|
+ if err != nil {
|
|
|
+ return nil, nil, fmt.Errorf("fail to get stream %s, please check if stream is created", s)
|
|
|
+ }
|
|
|
+ streamStmts[i] = streamStmt
|
|
|
if streamStmt.StreamFields == nil {
|
|
|
isSchemaless = true
|
|
|
- break
|
|
|
}
|
|
|
}
|
|
|
- xsql.WalkFunc(s, func(n xsql.Node) {
|
|
|
- if f, ok := n.(*xsql.FieldRef); ok && f.StreamName != "" {
|
|
|
- fname := f.Name
|
|
|
- isAlias := false
|
|
|
- if f.StreamName == xsql.DEFAULT_STREAM {
|
|
|
- for _, alias := range alias {
|
|
|
- if strings.EqualFold(fname, alias.AName) {
|
|
|
- fname = alias.Name
|
|
|
- isAlias = true
|
|
|
- break
|
|
|
+ var walkErr error
|
|
|
+ for _, f := range s.Fields {
|
|
|
+ if f.AName != "" {
|
|
|
+ if _, ok := aliasSourceMap[strings.ToLower(f.AName)]; ok {
|
|
|
+ return nil, nil, fmt.Errorf("duplicate alias %s", f.AName)
|
|
|
+ }
|
|
|
+ refStreams := make(map[string]struct{})
|
|
|
+ xsql.WalkFunc(f.Expr, func(n xsql.Node) {
|
|
|
+ switch expr := n.(type) {
|
|
|
+ case *xsql.FieldRef:
|
|
|
+ err := updateFieldRefStream(expr, streamStmts, isSchemaless)
|
|
|
+ if err != nil {
|
|
|
+ walkErr = err
|
|
|
+ return
|
|
|
}
|
|
|
- }
|
|
|
- if !isAlias {
|
|
|
- for _, alias := range aggregateAlias {
|
|
|
- if strings.EqualFold(fname, alias.AName) {
|
|
|
- fname = alias.Name
|
|
|
- isAlias = true
|
|
|
- break
|
|
|
- }
|
|
|
+ if expr.StreamName != "" {
|
|
|
+ refStreams[string(expr.StreamName)] = struct{}{}
|
|
|
}
|
|
|
}
|
|
|
+ })
|
|
|
+ if walkErr != nil {
|
|
|
+ return nil, nil, walkErr
|
|
|
+ }
|
|
|
+ refStreamKeys := make([]string, len(refStreams))
|
|
|
+ c := 0
|
|
|
+ for k, _ := range refStreams {
|
|
|
+ refStreamKeys[c] = k
|
|
|
+ c++
|
|
|
}
|
|
|
- count := 0
|
|
|
- for _, streamStmt := range ss {
|
|
|
- for _, field := range streamStmt.StreamFields {
|
|
|
- if strings.EqualFold(fname, field.Name) {
|
|
|
- if f.StreamName == xsql.DEFAULT_STREAM {
|
|
|
- f.StreamName = streamStmt.Name
|
|
|
- count++
|
|
|
- } else if f.StreamName == streamStmt.Name {
|
|
|
- count++
|
|
|
+ aliasSourceMap[strings.ToLower(f.AName)] = &aliasInfo{
|
|
|
+ alias: f,
|
|
|
+ refSources: refStreamKeys,
|
|
|
+ isAggregate: xsql.HasAggFuncs(f.Expr),
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // Select fields are visited firstly to make sure all aliases have streamName set
|
|
|
+ xsql.WalkFunc(s, func(n xsql.Node) {
|
|
|
+ //skip alias field
|
|
|
+ switch f := n.(type) {
|
|
|
+ case *xsql.Field:
|
|
|
+ if f.AName != "" {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ case *xsql.FieldRef:
|
|
|
+ if f.StreamName == xsql.DEFAULT_STREAM {
|
|
|
+ for aname, ainfo := range aliasSourceMap {
|
|
|
+ if strings.EqualFold(f.Name, aname) {
|
|
|
+ switch len(ainfo.refSources) {
|
|
|
+ case 0: // if no ref source, we can put it to any stream, here just assign it to the first stream
|
|
|
+ f.StreamName = streamStmts[0].Name
|
|
|
+ case 1:
|
|
|
+ f.StreamName = xsql.StreamName(ainfo.refSources[0])
|
|
|
+ default:
|
|
|
+ f.StreamName = xsql.MULTI_STREAM
|
|
|
}
|
|
|
- break
|
|
|
+ return
|
|
|
}
|
|
|
+
|
|
|
}
|
|
|
}
|
|
|
- if count > 1 {
|
|
|
- err = fmt.Errorf("ambiguous field %s", fname)
|
|
|
- } else if count == 0 && !isAlias && f.StreamName == xsql.DEFAULT_STREAM { // alias may refer to non stream field
|
|
|
- if !isSchemaless {
|
|
|
- err = fmt.Errorf("unknown field %s.%s", f.StreamName, f.Name)
|
|
|
- } else if len(ss) == 1 { // If only one schemaless stream, all the fields must be a field of that stream
|
|
|
- f.StreamName = ss[0].Name
|
|
|
- }
|
|
|
+ err := updateFieldRefStream(f, streamStmts, isSchemaless)
|
|
|
+ if err != nil {
|
|
|
+ walkErr = err
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
+ return streamStmts, aliasSourceMap, walkErr
|
|
|
+}
|
|
|
+
|
|
|
+func updateFieldRefStream(f *xsql.FieldRef, streamStmts []*xsql.StreamStmt, isSchemaless bool) (err error) {
|
|
|
+ count := 0
|
|
|
+ for _, streamStmt := range streamStmts {
|
|
|
+ for _, field := range streamStmt.StreamFields {
|
|
|
+ if strings.EqualFold(f.Name, field.Name) {
|
|
|
+ if f.StreamName == xsql.DEFAULT_STREAM {
|
|
|
+ f.StreamName = streamStmt.Name
|
|
|
+ count++
|
|
|
+ } else if f.StreamName == streamStmt.Name {
|
|
|
+ count++
|
|
|
+ }
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if count > 1 {
|
|
|
+ err = fmt.Errorf("ambiguous field %s", f.Name)
|
|
|
+ } else if count == 0 && f.StreamName == xsql.DEFAULT_STREAM { // alias may refer to non stream field
|
|
|
+ if !isSchemaless {
|
|
|
+ err = fmt.Errorf("unknown field %s.%s", f.StreamName, f.Name)
|
|
|
+ } else if len(streamStmts) == 1 { // If only one schemaless stream, all the fields must be a field of that stream
|
|
|
+ f.StreamName = streamStmts[0].Name
|
|
|
+ }
|
|
|
+ }
|
|
|
return
|
|
|
}
|
|
|
|
|
@@ -246,40 +308,29 @@ func getMockSource(sources []*nodes.SourceNode, name string) *nodes.SourceNode {
|
|
|
}
|
|
|
|
|
|
func createLogicalPlan(stmt *xsql.SelectStatement, opt *api.RuleOption, store kv.KeyValue) (LogicalPlan, error) {
|
|
|
- streamsFromStmt := xsql.GetStreams(stmt)
|
|
|
+
|
|
|
dimensions := stmt.Dimensions
|
|
|
var (
|
|
|
p LogicalPlan
|
|
|
children []LogicalPlan
|
|
|
// If there are tables, the plan graph will be different for join/window
|
|
|
- tableChildren []LogicalPlan
|
|
|
- tableEmitters []string
|
|
|
- w *xsql.Window
|
|
|
- ds xsql.Dimensions
|
|
|
- alias, aggregateAlias xsql.Fields
|
|
|
+ tableChildren []LogicalPlan
|
|
|
+ tableEmitters []string
|
|
|
+ w *xsql.Window
|
|
|
+ ds xsql.Dimensions
|
|
|
)
|
|
|
- for _, f := range stmt.Fields {
|
|
|
- if f.AName != "" {
|
|
|
- if !xsql.HasAggFuncs(f.Expr) {
|
|
|
- alias = append(alias, f)
|
|
|
- } else {
|
|
|
- aggregateAlias = append(aggregateAlias, f)
|
|
|
- }
|
|
|
- }
|
|
|
+
|
|
|
+ streamStmts, aliasMap, err := decorateStmt(stmt, store)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
}
|
|
|
|
|
|
- streamStmts := make([]*xsql.StreamStmt, len(streamsFromStmt))
|
|
|
- for i, s := range streamsFromStmt {
|
|
|
- streamStmt, err := xsql.GetDataSource(store, s)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("fail to get stream %s, please check if stream is created", s)
|
|
|
- }
|
|
|
- streamStmts[i] = streamStmt
|
|
|
+ for i, streamStmt := range streamStmts {
|
|
|
p = DataSourcePlan{
|
|
|
- name: s,
|
|
|
+ name: string(streamStmt.Name),
|
|
|
streamStmt: streamStmt,
|
|
|
iet: opt.IsEventTime,
|
|
|
- alias: alias,
|
|
|
+ alias: aliasFieldsForSource(aliasMap, streamStmt.Name, i == 0),
|
|
|
allMeta: opt.SendMetaToSink,
|
|
|
}.Init()
|
|
|
if streamStmt.StreamType == xsql.TypeStream {
|
|
@@ -289,11 +340,7 @@ func createLogicalPlan(stmt *xsql.SelectStatement, opt *api.RuleOption, store kv
|
|
|
tableEmitters = append(tableEmitters, string(streamStmt.Name))
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- err := decorateStmt(stmt, streamStmts, alias, aggregateAlias)
|
|
|
- if err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
+ aggregateAlias, _ := complexAlias(aliasMap)
|
|
|
if dimensions != nil {
|
|
|
w = dimensions.GetWindow()
|
|
|
if w != nil {
|
|
@@ -387,6 +434,38 @@ func createLogicalPlan(stmt *xsql.SelectStatement, opt *api.RuleOption, store kv
|
|
|
return optimize(p)
|
|
|
}
|
|
|
|
|
|
+func aliasFieldsForSource(aliasMap map[string]*aliasInfo, name xsql.StreamName, isFirst bool) (result xsql.Fields) {
|
|
|
+ for _, ainfo := range aliasMap {
|
|
|
+ if ainfo.isAggregate {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ switch len(ainfo.refSources) {
|
|
|
+ case 0:
|
|
|
+ if isFirst {
|
|
|
+ result = append(result, ainfo.alias)
|
|
|
+ }
|
|
|
+ case 1:
|
|
|
+ if strings.EqualFold(ainfo.refSources[0], string(name)) {
|
|
|
+ result = append(result, ainfo.alias)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+func complexAlias(aliasMap map[string]*aliasInfo) (aggregateAlias xsql.Fields, joinAlias xsql.Fields) {
|
|
|
+ for _, ainfo := range aliasMap {
|
|
|
+ if ainfo.isAggregate {
|
|
|
+ aggregateAlias = append(aggregateAlias, ainfo.alias)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if len(ainfo.refSources) > 1 {
|
|
|
+ joinAlias = append(joinAlias, ainfo.alias)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
func Transform(op nodes.UnOperation, name string, options *api.RuleOption) *nodes.UnaryOperator {
|
|
|
operator := nodes.New(name, xsql.FuncRegisters, options)
|
|
|
operator.SetOperation(op)
|