inital working metl
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
metl.exe
|
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
9
.idea/metl.iml
generated
Normal file
9
.idea/metl.iml
generated
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="Go" enabled="true" />
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/metl.iml" filepath="$PROJECT_DIR$/.idea/metl.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
29
.idea/watcherTasks.xml
generated
Normal file
29
.idea/watcherTasks.xml
generated
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectTasksOptions">
|
||||||
|
<TaskOptions isEnabled="true">
|
||||||
|
<option name="arguments" value="fmt $FilePath$" />
|
||||||
|
<option name="checkSyntaxErrors" value="true" />
|
||||||
|
<option name="description" />
|
||||||
|
<option name="exitCodeBehavior" value="ERROR" />
|
||||||
|
<option name="fileExtension" value="go" />
|
||||||
|
<option name="immediateSync" value="false" />
|
||||||
|
<option name="name" value="go fmt" />
|
||||||
|
<option name="output" value="$FilePath$" />
|
||||||
|
<option name="outputFilters">
|
||||||
|
<array />
|
||||||
|
</option>
|
||||||
|
<option name="outputFromStdout" value="false" />
|
||||||
|
<option name="program" value="$GoExecPath$" />
|
||||||
|
<option name="runOnExternalChanges" value="false" />
|
||||||
|
<option name="scopeName" value="Project Files" />
|
||||||
|
<option name="trackOnlyRoot" value="true" />
|
||||||
|
<option name="workingDir" value="$ProjectFileDir$" />
|
||||||
|
<envs>
|
||||||
|
<env name="GOROOT" value="$GOROOT$" />
|
||||||
|
<env name="GOPATH" value="$GOPATH$" />
|
||||||
|
<env name="PATH" value="$GoBinDirs$" />
|
||||||
|
</envs>
|
||||||
|
</TaskOptions>
|
||||||
|
</component>
|
||||||
|
</project>
|
102
action-modifycolumn.go
Normal file
102
action-modifycolumn.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActionModifyColumn struct {
|
||||||
|
ActionID uuid.UUID
|
||||||
|
OutputSchema *Schema
|
||||||
|
InputSchema *Schema
|
||||||
|
Config ModifyColumnConfig
|
||||||
|
status ActionStatus
|
||||||
|
bus *Bus
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModifyColumnConfig struct {
|
||||||
|
ActionConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewModifyColumn(config ModifyColumnConfig, b *Bus) (ActionModifyColumn, error) {
|
||||||
|
as := ActionModifyColumn{}
|
||||||
|
err := as.init(config, b)
|
||||||
|
if err != nil {
|
||||||
|
return ActionModifyColumn{}, err
|
||||||
|
}
|
||||||
|
return as, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionModifyColumn) init(config ModifyColumnConfig, b *Bus) error {
|
||||||
|
if len(config.Inputs) <= 0 {
|
||||||
|
return errors.New(fmt.Sprintf("error no input schema defined. ActionID: %v", config.ActionID.String()))
|
||||||
|
}
|
||||||
|
s.InputSchema = config.Inputs[0].InputSchema
|
||||||
|
s.OutputSchema = config.OutputSchema
|
||||||
|
s.ActionID = config.ActionID
|
||||||
|
s.Config = config
|
||||||
|
s.status = ActionStatus{
|
||||||
|
State: NotStarted,
|
||||||
|
HandledRows: 0,
|
||||||
|
ExpectedRows: -1,
|
||||||
|
}
|
||||||
|
s.bus = b
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionModifyColumn) Run(contentRow ContentRow) error {
|
||||||
|
|
||||||
|
// convert the schema to the new one
|
||||||
|
newContentRow := ContentRow{
|
||||||
|
Schema: s.Config.OutputSchema,
|
||||||
|
Values: make(map[uuid.UUID]string),
|
||||||
|
Raw: "",
|
||||||
|
Source: &s.ActionID,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, col := range newContentRow.Schema.Columns {
|
||||||
|
value, ok := contentRow.Values[col.ID]
|
||||||
|
if !ok {
|
||||||
|
value = col.DefaultValue
|
||||||
|
}
|
||||||
|
newContentRow.Values[col.ID] = value
|
||||||
|
if strings.EqualFold(newContentRow.Raw, "") {
|
||||||
|
newContentRow.Raw = value
|
||||||
|
} else {
|
||||||
|
newContentRow.Raw = newContentRow.Raw + "," + value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s.publishRow(newContentRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionModifyColumn) publishRow(row ContentRow) error {
|
||||||
|
// TODO: decide on the method of transporting data to the next action
|
||||||
|
return s.bus.Run(s.ActionID, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionModifyColumn) publishErrorRow(row ContentRow) error {
|
||||||
|
// TODO: decide on the method of transporting data to the next action
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionModifyColumn) Validate() error {
|
||||||
|
if len(s.Config.Inputs) <= 0 {
|
||||||
|
return errors.New(fmt.Sprintf("error no input schema defined. ActionID: %v", s.ActionID.String()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionModifyColumn) GetActionID() uuid.UUID {
|
||||||
|
return s.ActionID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionModifyColumn) Status() ActionStatus {
|
||||||
|
return s.status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionModifyColumn) GetActionConfig() ActionConfig {
|
||||||
|
return s.Config.ActionConfig
|
||||||
|
}
|
122
action-stdin.go
Normal file
122
action-stdin.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActionStdIn struct {
|
||||||
|
ActionID uuid.UUID
|
||||||
|
OutputSchema *Schema
|
||||||
|
Config StdInConfig
|
||||||
|
status ActionStatus
|
||||||
|
bus *Bus
|
||||||
|
}
|
||||||
|
|
||||||
|
type StdInConfig struct {
|
||||||
|
ActionConfig
|
||||||
|
HasHeader bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStdIn(config StdInConfig, b *Bus) (ActionStdIn, error) {
|
||||||
|
as := ActionStdIn{}
|
||||||
|
err := as.init(config, b)
|
||||||
|
if err != nil {
|
||||||
|
return ActionStdIn{}, err
|
||||||
|
}
|
||||||
|
return as, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionStdIn) init(config StdInConfig, b *Bus) error {
|
||||||
|
s.OutputSchema = config.OutputSchema
|
||||||
|
s.ActionID = config.ActionID
|
||||||
|
s.Config = config
|
||||||
|
s.status = ActionStatus{
|
||||||
|
State: NotStarted,
|
||||||
|
HandledRows: 0,
|
||||||
|
ExpectedRows: -1,
|
||||||
|
}
|
||||||
|
s.bus = b
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionStdIn) Run(_ ContentRow) error {
|
||||||
|
s.status.State = Running
|
||||||
|
rowsProcessed := int64(0)
|
||||||
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
for scanner.Scan() {
|
||||||
|
potentialRow := scanner.Text()
|
||||||
|
// TODO: handle headers
|
||||||
|
if rowsProcessed <= 0 && s.Config.HasHeader {
|
||||||
|
// currently ignoring the first row always
|
||||||
|
rowsProcessed = rowsProcessed + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
potentialColumns := strings.Split(potentialRow, ",")
|
||||||
|
contentRow := ContentRow{
|
||||||
|
Schema: s.OutputSchema,
|
||||||
|
Values: make(map[uuid.UUID]string),
|
||||||
|
Raw: potentialRow,
|
||||||
|
Source: &s.ActionID,
|
||||||
|
}
|
||||||
|
rowValid := true
|
||||||
|
for i, c := range potentialColumns {
|
||||||
|
value := strings.TrimSpace(c)
|
||||||
|
valid, err := ValidateDataType(value, s.OutputSchema.Columns[i].Type)
|
||||||
|
if err != nil {
|
||||||
|
rowValid = false
|
||||||
|
}
|
||||||
|
rowValid = rowValid && valid
|
||||||
|
contentRow.Values[s.OutputSchema.Columns[i].ID] = value
|
||||||
|
}
|
||||||
|
if !rowValid {
|
||||||
|
_ = s.publishErrorRow(contentRow)
|
||||||
|
}
|
||||||
|
err := s.publishRow(contentRow)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(fmt.Sprintf("error publishing row. ActionID: %v | %v", s.ActionID, err.Error()))
|
||||||
|
}
|
||||||
|
rowsProcessed = rowsProcessed + 1
|
||||||
|
s.status.HandledRows = rowsProcessed
|
||||||
|
}
|
||||||
|
s.status.State = Finished
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return errors.New(fmt.Sprintf("error scanning input. ActionID: %v | %v", s.ActionID, err.Error()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionStdIn) publishRow(row ContentRow) error {
|
||||||
|
// TODO: decide on the method of transporting data to the next action
|
||||||
|
return s.bus.Run(s.ActionID, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionStdIn) publishErrorRow(row ContentRow) error {
|
||||||
|
// TODO: decide on the method of transporting data to the next action
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionStdIn) Validate() error {
|
||||||
|
if len(s.Config.Inputs) <= 0 {
|
||||||
|
return errors.New(fmt.Sprintf("error no input schema defined. ActionID: %v", s.ActionID.String()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionStdIn) GetActionID() uuid.UUID {
|
||||||
|
return s.ActionID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionStdIn) Status() ActionStatus {
|
||||||
|
return s.status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionStdIn) GetActionConfig() ActionConfig {
|
||||||
|
return s.Config.ActionConfig
|
||||||
|
}
|
99
action-stdout.go
Normal file
99
action-stdout.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActionStdOut struct {
|
||||||
|
ActionID uuid.UUID
|
||||||
|
OutputSchema *Schema
|
||||||
|
InputSchema *Schema
|
||||||
|
Config StdOutConfig
|
||||||
|
status ActionStatus
|
||||||
|
bus *Bus
|
||||||
|
}
|
||||||
|
|
||||||
|
type StdOutConfig struct {
|
||||||
|
ActionConfig
|
||||||
|
AddHeader bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStdOut(config StdOutConfig, b *Bus) (ActionStdOut, error) {
|
||||||
|
as := ActionStdOut{}
|
||||||
|
err := as.init(config, b)
|
||||||
|
if err != nil {
|
||||||
|
return ActionStdOut{}, err
|
||||||
|
}
|
||||||
|
return as, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionStdOut) init(config StdOutConfig, b *Bus) error {
|
||||||
|
if len(config.Inputs) <= 0 {
|
||||||
|
return errors.New(fmt.Sprintf("error no input schema defined. ActionID: %v", config.ActionID.String()))
|
||||||
|
}
|
||||||
|
s.InputSchema = config.Inputs[0].InputSchema
|
||||||
|
s.OutputSchema = config.OutputSchema
|
||||||
|
s.ActionID = config.ActionID
|
||||||
|
s.Config = config
|
||||||
|
s.status = ActionStatus{
|
||||||
|
State: NotStarted,
|
||||||
|
HandledRows: 0,
|
||||||
|
ExpectedRows: -1,
|
||||||
|
}
|
||||||
|
s.bus = b
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionStdOut) Run(contentRow ContentRow) error {
|
||||||
|
s.status.State = Running
|
||||||
|
if s.status.HandledRows == 0 && s.Config.AddHeader {
|
||||||
|
headerRowRaw := ""
|
||||||
|
for _, h := range s.Config.OutputSchema.Columns {
|
||||||
|
if strings.EqualFold(headerRowRaw, "") {
|
||||||
|
headerRowRaw = h.Name
|
||||||
|
} else {
|
||||||
|
headerRowRaw = headerRowRaw + "," + h.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Printf("%v\n", headerRowRaw)
|
||||||
|
}
|
||||||
|
fmt.Printf("%v\n", contentRow.Raw)
|
||||||
|
s.status.HandledRows = s.status.HandledRows + 1
|
||||||
|
if s.status.HandledRows == s.status.ExpectedRows {
|
||||||
|
s.status.State = Finished
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionStdOut) publishRow(row ContentRow) error {
|
||||||
|
// TODO: decide on the method of transporting data to the next action
|
||||||
|
return s.bus.Run(s.ActionID, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionStdOut) publishErrorRow(row ContentRow) error {
|
||||||
|
// TODO: decide on the method of transporting data to the next action
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionStdOut) Validate() error {
|
||||||
|
if len(s.Config.Inputs) <= 0 {
|
||||||
|
return errors.New(fmt.Sprintf("error no input schema defined. ActionID: %v", s.ActionID.String()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionStdOut) GetActionID() uuid.UUID {
|
||||||
|
return s.ActionID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionStdOut) Status() ActionStatus {
|
||||||
|
return s.status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActionStdOut) GetActionConfig() ActionConfig {
|
||||||
|
return s.Config.ActionConfig
|
||||||
|
}
|
247
action.go
Normal file
247
action.go
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Action interface {
|
||||||
|
Run(row ContentRow) error
|
||||||
|
//publishRow(row ContentRow) error
|
||||||
|
//publishErrorRow(row ContentRow) error
|
||||||
|
//Start()
|
||||||
|
//Finish()
|
||||||
|
Status() ActionStatus
|
||||||
|
Validate() error
|
||||||
|
GetActionID() uuid.UUID
|
||||||
|
GetActionConfig() ActionConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type Schema struct {
|
||||||
|
Columns []ColumnDefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
type ColumnDefinition struct {
|
||||||
|
Name string
|
||||||
|
ID uuid.UUID
|
||||||
|
Type DataType
|
||||||
|
DefaultValue string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContentRow struct {
|
||||||
|
Schema *Schema
|
||||||
|
Values map[uuid.UUID]string
|
||||||
|
Raw string
|
||||||
|
Source *uuid.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActionConfig struct {
|
||||||
|
ActionID uuid.UUID
|
||||||
|
Type ActionType
|
||||||
|
OutputSchema *Schema
|
||||||
|
Inputs []InputDefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
type InputDefinition struct {
|
||||||
|
InputSchema *Schema
|
||||||
|
ActionID *uuid.UUID
|
||||||
|
// TODO: decide if input types exist
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActionStatus struct {
|
||||||
|
State StateValue
|
||||||
|
ExpectedRows int64
|
||||||
|
HandledRows int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataType - Custom type to hold value for data type ranging from 1-5
|
||||||
|
type DataType int
|
||||||
|
|
||||||
|
// Declare related constants for each datatype starting with index 1
|
||||||
|
const (
|
||||||
|
String DataType = iota + 1
|
||||||
|
Integer
|
||||||
|
Float
|
||||||
|
Date
|
||||||
|
DateTime
|
||||||
|
)
|
||||||
|
|
||||||
|
// String - Creating common behavior give the type a String function
|
||||||
|
func (d DataType) String() string {
|
||||||
|
return [...]string{"String", "Integer", "Float", "Date", "DateTime"}[d-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnumIndex - Creating common behavior give the type a EnumIndex function
|
||||||
|
func (d DataType) EnumIndex() int {
|
||||||
|
return int(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON marshals the enum as a quoted json string
|
||||||
|
func (s DataType) MarshalJSON() ([]byte, error) {
|
||||||
|
buffer := bytes.NewBufferString(`"`)
|
||||||
|
buffer.WriteString(s.String())
|
||||||
|
buffer.WriteString(`"`)
|
||||||
|
return buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmashals a quoted json string to the enum value
|
||||||
|
func (s *DataType) UnmarshalJSON(b []byte) error {
|
||||||
|
var j string
|
||||||
|
err := json.Unmarshal(b, &j)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Note that if the string cannot be found then it will be set to the zero value, 'String' in this case.
|
||||||
|
var toID = map[string]DataType{
|
||||||
|
"String": String,
|
||||||
|
"Integer": Integer,
|
||||||
|
"Float": Float,
|
||||||
|
"Date": Date,
|
||||||
|
"DateTime": DateTime,
|
||||||
|
}
|
||||||
|
*s = toID[j]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateDataType(in string, dataType DataType) (bool, error) {
|
||||||
|
switch dataType {
|
||||||
|
case String:
|
||||||
|
return ValidStringDataType(in), nil
|
||||||
|
case Integer:
|
||||||
|
return ValidIntegerDataType(in)
|
||||||
|
case Float:
|
||||||
|
return ValidFloatDataType(in)
|
||||||
|
case Date:
|
||||||
|
return ValidDateDataType(in)
|
||||||
|
case DateTime:
|
||||||
|
return ValidDateTimeDataType(in)
|
||||||
|
default:
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidStringDataType(in string) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidIntegerDataType(in string) (bool, error) {
|
||||||
|
i, err := strconv.ParseInt(in, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return strings.EqualFold(in, fmt.Sprintf("%v", i)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidFloatDataType(in string) (bool, error) {
|
||||||
|
i, err := strconv.ParseFloat(in, 64)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return strings.EqualFold(in, fmt.Sprintf("%v", i)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidDateDataType(in string) (bool, error) {
|
||||||
|
// TODO: implement
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidDateTimeDataType(in string) (bool, error) {
|
||||||
|
// TODO: implement
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionType - Custom type to hold value for data type ranging from 1-5
|
||||||
|
type ActionType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
StdIn ActionType = iota + 1
|
||||||
|
ModifyColumn
|
||||||
|
StdOut
|
||||||
|
)
|
||||||
|
|
||||||
|
// String - Creating common behavior give the type a String function
|
||||||
|
func (d ActionType) String() string {
|
||||||
|
return [...]string{"StdIn", "ModifyColumn", "StdOut"}[d-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnumIndex - Creating common behavior give the type a EnumIndex function
|
||||||
|
func (d ActionType) EnumIndex() int {
|
||||||
|
return int(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON marshals the enum as a quoted json string
|
||||||
|
func (s ActionType) MarshalJSON() ([]byte, error) {
|
||||||
|
buffer := bytes.NewBufferString(`"`)
|
||||||
|
buffer.WriteString(s.String())
|
||||||
|
buffer.WriteString(`"`)
|
||||||
|
return buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmashals a quoted json string to the enum value
|
||||||
|
func (s *ActionType) UnmarshalJSON(b []byte) error {
|
||||||
|
var j string
|
||||||
|
err := json.Unmarshal(b, &j)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Note that if the string cannot be found then it will be set to the zero value, 'String' in this case.
|
||||||
|
var toID = map[string]ActionType{
|
||||||
|
"StdIn": StdIn,
|
||||||
|
"ModifyColumn": ModifyColumn,
|
||||||
|
"StdOut": StdOut,
|
||||||
|
}
|
||||||
|
*s = toID[j]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StateValue - Custom type to hold value for data type ranging from 1-5
|
||||||
|
type StateValue int
|
||||||
|
|
||||||
|
// Declare related constants for each datatype starting with index 1
|
||||||
|
const (
|
||||||
|
NotStarted StateValue = iota + 1
|
||||||
|
Paused
|
||||||
|
Running
|
||||||
|
Finished
|
||||||
|
)
|
||||||
|
|
||||||
|
// String - Creating common behavior give the type a String function
|
||||||
|
func (d StateValue) String() string {
|
||||||
|
return [...]string{"NotStarted", "Paused", "Running", "Finished"}[d-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnumIndex - Creating common behavior give the type a EnumIndex function
|
||||||
|
func (d StateValue) EnumIndex() int {
|
||||||
|
return int(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON marshals the enum as a quoted json string
|
||||||
|
func (s StateValue) MarshalJSON() ([]byte, error) {
|
||||||
|
buffer := bytes.NewBufferString(`"`)
|
||||||
|
buffer.WriteString(s.String())
|
||||||
|
buffer.WriteString(`"`)
|
||||||
|
return buffer.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmashals a quoted json string to the enum value
|
||||||
|
func (s *StateValue) UnmarshalJSON(b []byte) error {
|
||||||
|
var j string
|
||||||
|
err := json.Unmarshal(b, &j)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Note that if the string cannot be found then it will be set to the zero value, 'String' in this case.
|
||||||
|
var toID = map[string]StateValue{
|
||||||
|
"NotStarted": NotStarted,
|
||||||
|
"Paused": Paused,
|
||||||
|
"Running": Running,
|
||||||
|
"Finished": Finished,
|
||||||
|
}
|
||||||
|
*s = toID[j]
|
||||||
|
return nil
|
||||||
|
}
|
154
bus.go
Normal file
154
bus.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MetlConfig struct {
|
||||||
|
ActionConfigs []parallelActionConfig `json:"actionConfigs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type parallelActionConfig struct {
|
||||||
|
ActionConfig
|
||||||
|
StdInConfig
|
||||||
|
ModifyColumnConfig
|
||||||
|
StdOutConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bus struct {
|
||||||
|
actions map[uuid.UUID]Action
|
||||||
|
events map[uuid.UUID][]func(contentRow ContentRow) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBus(config MetlConfig) (Bus, error) {
|
||||||
|
b := Bus{}
|
||||||
|
err := b.init(config)
|
||||||
|
if err != nil {
|
||||||
|
return Bus{}, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrap(err error, message string) error {
|
||||||
|
return errors.New(fmt.Sprintf("%v | %v", message, err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bus) init(config MetlConfig) error {
|
||||||
|
b.events = make(map[uuid.UUID][]func(contentRow ContentRow) error)
|
||||||
|
b.actions = make(map[uuid.UUID]Action)
|
||||||
|
for _, ac := range config.ActionConfigs {
|
||||||
|
// setup the event list for this action (it will contain all the Run funcs for actions that depend on this action
|
||||||
|
_, ok := b.events[ac.ActionID]
|
||||||
|
if !ok {
|
||||||
|
b.events[ac.ActionID] = make([]func(contentRow ContentRow) error, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
var action Action
|
||||||
|
switch ac.Type {
|
||||||
|
case StdIn:
|
||||||
|
stdIn, err := NewStdIn(StdInConfig{
|
||||||
|
ActionConfig: ac.ActionConfig,
|
||||||
|
HasHeader: ac.HasHeader,
|
||||||
|
}, b)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(fmt.Sprintf("error initializing StdIn Action. ActionID: %v | %v", ac.ActionID, err.Error()))
|
||||||
|
}
|
||||||
|
action = &stdIn
|
||||||
|
case ModifyColumn:
|
||||||
|
modCol, err := NewModifyColumn(ModifyColumnConfig{
|
||||||
|
ActionConfig: ac.ActionConfig,
|
||||||
|
}, b)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(fmt.Sprintf("error initializing ModifyColumn Action. ActionID: %v | %v", ac.ActionID, err.Error()))
|
||||||
|
}
|
||||||
|
action = &modCol
|
||||||
|
case StdOut:
|
||||||
|
stdOut, err := NewStdOut(StdOutConfig{
|
||||||
|
ActionConfig: ac.ActionConfig,
|
||||||
|
AddHeader: ac.AddHeader,
|
||||||
|
}, b)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(fmt.Sprintf("error initializing StdOut Action. ActionID: %v | %v", ac.ActionID, err.Error()))
|
||||||
|
}
|
||||||
|
action = &stdOut
|
||||||
|
// TODO: add other actions here
|
||||||
|
}
|
||||||
|
b.actions[ac.ActionID] = action
|
||||||
|
|
||||||
|
// add this action's Run func to all events it is dependent on
|
||||||
|
for _, input := range ac.Inputs {
|
||||||
|
_, ok := b.events[*input.ActionID]
|
||||||
|
if !ok {
|
||||||
|
b.events[*input.ActionID] = make([]func(contentRow ContentRow) error, 0)
|
||||||
|
}
|
||||||
|
if input.ActionID != nil {
|
||||||
|
b.events[*input.ActionID] = append(b.events[*input.ActionID], action.Run)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bus) ValidateAll() (bool, error) {
|
||||||
|
problems := make(map[uuid.UUID]string)
|
||||||
|
for key, a := range b.actions {
|
||||||
|
err := a.Validate()
|
||||||
|
if err != nil {
|
||||||
|
problems[key] = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check the input schemas against the actions they are coming from
|
||||||
|
}
|
||||||
|
if len(problems) != 0 {
|
||||||
|
errorOut := ""
|
||||||
|
for key, prob := range problems {
|
||||||
|
if !strings.EqualFold("", errorOut) {
|
||||||
|
errorOut = fmt.Sprintf("%v | %v: %v", errorOut, key, prob)
|
||||||
|
} else {
|
||||||
|
errorOut = fmt.Sprintf("%v: %v", key, prob)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, errors.New(errorOut)
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bus) Start() error {
|
||||||
|
for _, a := range b.actions {
|
||||||
|
aConfig := a.GetActionConfig()
|
||||||
|
if len(aConfig.Inputs) <= 0 {
|
||||||
|
err := a.Run(ContentRow{})
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(fmt.Sprintf("error running Action. ActionID: %v | %v", a.GetActionID(), err.Error()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bus) Run(actionID uuid.UUID, row ContentRow) error {
|
||||||
|
for _, e := range b.events[actionID] {
|
||||||
|
err := e(row)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New(fmt.Sprintf("error running Action. ActionID: %v | %v", actionID, err.Error()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Bus will manage the transporting of the data from an actions output to all actions that are dependent upon that data.
|
||||||
|
|
||||||
|
If an action does not have a defined action it depends on it will be executed right away using the Run() func and will be
|
||||||
|
passed a zeroed ContentRow.
|
||||||
|
|
||||||
|
Otherwise, all actions will have their run functions stored in arrays within a map of action uuids. When a certain action
|
||||||
|
publishes a row then we loop through the array of stored functions that are waiting for data from that action.
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
8
go.mod
Normal file
8
go.mod
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module github.com/payne8/metl
|
||||||
|
|
||||||
|
go 1.14
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/google/uuid v1.2.0
|
||||||
|
github.com/mitchellh/mapstructure v1.4.1
|
||||||
|
)
|
4
go.sum
Normal file
4
go.sum
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||||
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||||
|
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
95
metl.go
Normal file
95
metl.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
jsonFile, err := os.Open("metlTestConfig.json")
|
||||||
|
// if we os.Open returns an error then handle it
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Successfully Opened metlTestConfig.json")
|
||||||
|
// defer the closing of our jsonFile so that we can parse it later on
|
||||||
|
defer jsonFile.Close()
|
||||||
|
|
||||||
|
// read our opened xmlFile as a byte array.
|
||||||
|
byteValue, _ := ioutil.ReadAll(jsonFile)
|
||||||
|
|
||||||
|
// we initialize our Users array
|
||||||
|
var metlConfig MetlConfig
|
||||||
|
|
||||||
|
// we unmarshal our byteArray which contains our
|
||||||
|
// jsonFile's content into 'users' which we defined above
|
||||||
|
err = json.Unmarshal(byteValue, &metlConfig)
|
||||||
|
if err != nil {
|
||||||
|
panic("error unmarshalling JSON into MetlConfig struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
bus, err := NewBus(metlConfig)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bus.Start()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//stdInID, _ := uuid.Parse("a64b47b0-8ecb-409e-8fbc-5e2f5b93442b")
|
||||||
|
//stdInID, _ := uuid.Parse("a64b47b0-8ecb-409e-8fbc-5e2f5b93442b")
|
||||||
|
//
|
||||||
|
//testConfig := MetlConfig{
|
||||||
|
// ActionConfigs: []interface{}{
|
||||||
|
// StdInConfig{
|
||||||
|
// ActionConfig: ActionConfig{
|
||||||
|
// ActionID: stdInID,
|
||||||
|
// Type: StdIn,
|
||||||
|
// OutputSchema: &Schema{
|
||||||
|
// Columns: []ColumnDefinition{
|
||||||
|
// {
|
||||||
|
// Name: "id",
|
||||||
|
// ID:
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// HasHeader: true,
|
||||||
|
// },
|
||||||
|
// ModifyColumnConfig{
|
||||||
|
//
|
||||||
|
// },
|
||||||
|
// StdOutConfig{
|
||||||
|
//
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
//}
|
||||||
|
|
||||||
|
//fmt.Print("\033[H\033[2J")
|
||||||
|
//for i := 0; i < 100; i++ {
|
||||||
|
// fmt.Printf("\033[0;0H")
|
||||||
|
// fmt.Printf("On %d/100\nfarts %d ", i, i)
|
||||||
|
// //if isTerminal() {
|
||||||
|
// //}
|
||||||
|
// //else {
|
||||||
|
// // fmt.Printf("On %d/100 ", i)
|
||||||
|
// //}
|
||||||
|
// time.Sleep(200 * time.Millisecond)
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isTerminal() bool {
|
||||||
|
if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) != 0 {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
276
metlTestConfig.json
Normal file
276
metlTestConfig.json
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
{
|
||||||
|
"actionConfigs": [
|
||||||
|
{
|
||||||
|
"ActionID": "a64b47b0-8ecb-409e-8fbc-5e2f5b93442b",
|
||||||
|
"Type": "StdIn",
|
||||||
|
"OutputSchema": {
|
||||||
|
"Columns": [
|
||||||
|
{
|
||||||
|
"Name": "id",
|
||||||
|
"ID": "af19ecb0-cb48-497f-ada0-d349accb0e2f",
|
||||||
|
"Type": "Integer",
|
||||||
|
"DefaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "first_name",
|
||||||
|
"ID": "478b9c5a-a31b-411d-9e4c-47bb0cf86213",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "last_name",
|
||||||
|
"ID": "1b21193f-57da-4060-afa2-7b213d99e62a",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "email",
|
||||||
|
"ID": "7b636cdd-e131-4a3e-88bc-cfb43d676bc0",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "gender",
|
||||||
|
"ID": "378729da-de80-45b7-b675-bfa4e92d4e22",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "car_make",
|
||||||
|
"ID": "d35dc745-3d41-4076-8cc5-9cb7e93ab802",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "car_model",
|
||||||
|
"ID": "50e3013e-dc43-400c-9582-a3db9de23c0f",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Inputs": [],
|
||||||
|
"hasHeader": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ActionID": "30f7390c-c1cf-401b-aa81-d7156ddc5e1d",
|
||||||
|
"Type": "ModifyColumn",
|
||||||
|
"OutputSchema": {
|
||||||
|
"Columns": [
|
||||||
|
{
|
||||||
|
"Name": "id",
|
||||||
|
"ID": "af19ecb0-cb48-497f-ada0-d349accb0e2f",
|
||||||
|
"Type": "Integer",
|
||||||
|
"DefaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "first_name",
|
||||||
|
"ID": "478b9c5a-a31b-411d-9e4c-47bb0cf86213",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "last_name",
|
||||||
|
"ID": "1b21193f-57da-4060-afa2-7b213d99e62a",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "email",
|
||||||
|
"ID": "7b636cdd-e131-4a3e-88bc-cfb43d676bc0",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "gender",
|
||||||
|
"ID": "378729da-de80-45b7-b675-bfa4e92d4e22",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "car_make",
|
||||||
|
"ID": "d35dc745-3d41-4076-8cc5-9cb7e93ab802",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "car_model",
|
||||||
|
"ID": "50e3013e-dc43-400c-9582-a3db9de23c0f",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "new_column",
|
||||||
|
"ID": "e9cff198-2ebd-425d-be59-9b5f834718df",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": "x"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Inputs": [
|
||||||
|
{
|
||||||
|
"InputSchema": {
|
||||||
|
"Columns": [
|
||||||
|
{
|
||||||
|
"Name": "id",
|
||||||
|
"ID": "af19ecb0-cb48-497f-ada0-d349accb0e2f",
|
||||||
|
"Type": "Integer",
|
||||||
|
"DefaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "first_name",
|
||||||
|
"ID": "478b9c5a-a31b-411d-9e4c-47bb0cf86213",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "last_name",
|
||||||
|
"ID": "1b21193f-57da-4060-afa2-7b213d99e62a",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "email",
|
||||||
|
"ID": "7b636cdd-e131-4a3e-88bc-cfb43d676bc0",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "gender",
|
||||||
|
"ID": "378729da-de80-45b7-b675-bfa4e92d4e22",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "car_make",
|
||||||
|
"ID": "d35dc745-3d41-4076-8cc5-9cb7e93ab802",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "car_model",
|
||||||
|
"ID": "50e3013e-dc43-400c-9582-a3db9de23c0f",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ActionID": "a64b47b0-8ecb-409e-8fbc-5e2f5b93442b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ActionID": "c004eb92-24d7-453b-b474-bd49ac467f00",
|
||||||
|
"Type": "StdOut",
|
||||||
|
"OutputSchema": {
|
||||||
|
"Columns": [
|
||||||
|
{
|
||||||
|
"Name": "id",
|
||||||
|
"ID": "af19ecb0-cb48-497f-ada0-d349accb0e2f",
|
||||||
|
"Type": "Integer",
|
||||||
|
"DefaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "first_name",
|
||||||
|
"ID": "478b9c5a-a31b-411d-9e4c-47bb0cf86213",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "last_name",
|
||||||
|
"ID": "1b21193f-57da-4060-afa2-7b213d99e62a",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "email",
|
||||||
|
"ID": "7b636cdd-e131-4a3e-88bc-cfb43d676bc0",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "gender",
|
||||||
|
"ID": "378729da-de80-45b7-b675-bfa4e92d4e22",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "car_make",
|
||||||
|
"ID": "d35dc745-3d41-4076-8cc5-9cb7e93ab802",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "car_model",
|
||||||
|
"ID": "50e3013e-dc43-400c-9582-a3db9de23c0f",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "new_column",
|
||||||
|
"ID": "e9cff198-2ebd-425d-be59-9b5f834718df",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": "x"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Inputs": [
|
||||||
|
{
|
||||||
|
"InputSchema": {
|
||||||
|
"Columns": [
|
||||||
|
{
|
||||||
|
"Name": "id",
|
||||||
|
"ID": "af19ecb0-cb48-497f-ada0-d349accb0e2f",
|
||||||
|
"Type": "Integer",
|
||||||
|
"DefaultValue": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "first_name",
|
||||||
|
"ID": "478b9c5a-a31b-411d-9e4c-47bb0cf86213",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "last_name",
|
||||||
|
"ID": "1b21193f-57da-4060-afa2-7b213d99e62a",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "email",
|
||||||
|
"ID": "7b636cdd-e131-4a3e-88bc-cfb43d676bc0",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "gender",
|
||||||
|
"ID": "378729da-de80-45b7-b675-bfa4e92d4e22",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "car_make",
|
||||||
|
"ID": "d35dc745-3d41-4076-8cc5-9cb7e93ab802",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "car_model",
|
||||||
|
"ID": "50e3013e-dc43-400c-9582-a3db9de23c0f",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "new_column",
|
||||||
|
"ID": "e9cff198-2ebd-425d-be59-9b5f834718df",
|
||||||
|
"Type": "String",
|
||||||
|
"DefaultValue": "x"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ActionID": "30f7390c-c1cf-401b-aa81-d7156ddc5e1d"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Reference in New Issue
Block a user