inital working metl

This commit is contained in:
2021-05-23 22:15:30 -06:00
commit 118ffd908f
15 changed files with 1168 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
metl.exe

8
.idea/.gitignore generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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"
}
]
}
]
}