github.com/mithrandie/csvq@v1.18.1/lib/query/processor_test.go (about)

     1  package query
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"reflect"
     7  	"strings"
     8  	"sync"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/mithrandie/csvq/lib/option"
    13  	"github.com/mithrandie/csvq/lib/parser"
    14  	"github.com/mithrandie/csvq/lib/value"
    15  
    16  	"github.com/mithrandie/go-text"
    17  	"github.com/mithrandie/ternary"
    18  )
    19  
    20  func TestContextForStoringResults(t *testing.T) {
    21  	defer func() {
    22  		TestTx.SelectedViews = nil
    23  		TestTx.AffectedRows = 0
    24  	}()
    25  
    26  	tx := TestTx
    27  	proc := NewProcessor(tx)
    28  
    29  	ctx := ContextForStoringResults(context.Background())
    30  	_, err := proc.Execute(ctx, []parser.Statement{
    31  		parser.SelectQuery{
    32  			SelectEntity: parser.SelectEntity{
    33  				SelectClause: parser.SelectClause{
    34  					Fields: []parser.QueryExpression{
    35  						parser.Field{Object: parser.NewIntegerValueFromString("1")},
    36  					},
    37  				},
    38  			},
    39  		},
    40  	})
    41  	if err != nil {
    42  		t.Fatalf("unexpected error %q", err)
    43  	}
    44  
    45  	if len(proc.Tx.SelectedViews) < 1 {
    46  		t.Fatalf("result set is not set to the transaction")
    47  	}
    48  }
    49  
    50  var processorExecuteStatementTests = []struct {
    51  	Input            parser.Statement
    52  	UncommittedViews UncommittedViews
    53  	Logs             string
    54  	SelectLogs       []string
    55  	Error            string
    56  	ReturnCode       int
    57  }{
    58  	{
    59  		Input: parser.SetFlag{
    60  			Flag:  parser.Flag{Name: "invalid"},
    61  			Value: parser.NewStringValue("\t"),
    62  		},
    63  		Error:      "@@INVALID is an unknown flag",
    64  		ReturnCode: ReturnCodeApplicationError,
    65  	},
    66  	{
    67  		Input: parser.SetFlag{
    68  			Flag:  parser.Flag{Name: "delimiter"},
    69  			Value: parser.NewStringValue(","),
    70  		},
    71  	},
    72  	{
    73  		Input: parser.Echo{
    74  			Value: parser.Function{
    75  				Name: "trunc_time",
    76  				Args: []parser.QueryExpression{
    77  					parser.Function{
    78  						Name: "datetime",
    79  						Args: []parser.QueryExpression{
    80  							parser.NewStringValue("2001::01::01"),
    81  						},
    82  					},
    83  				},
    84  			},
    85  		},
    86  		Logs: "NULL\n",
    87  	},
    88  	{
    89  		Input: parser.AddFlagElement{
    90  			Flag:  parser.Flag{Name: "datetime_format"},
    91  			Value: parser.NewStringValue("%Y::%m::%d"),
    92  		},
    93  	},
    94  	{
    95  		Input: parser.Echo{
    96  			Value: parser.Function{
    97  				Name: "trunc_time",
    98  				Args: []parser.QueryExpression{
    99  					parser.Function{
   100  						Name: "datetime",
   101  						Args: []parser.QueryExpression{
   102  							parser.NewStringValue("2001::01::01"),
   103  						},
   104  					},
   105  				},
   106  			},
   107  		},
   108  		Logs: "2001-01-01T00:00:00Z\n",
   109  	},
   110  	{
   111  		Input: parser.RemoveFlagElement{
   112  			Flag:  parser.Flag{Name: "datetime_format"},
   113  			Value: parser.NewStringValue("%Y::%m::%d"),
   114  		},
   115  	},
   116  	{
   117  		Input: parser.Echo{
   118  			Value: parser.Function{
   119  				Name: "trunc_time",
   120  				Args: []parser.QueryExpression{
   121  					parser.Function{
   122  						Name: "datetime",
   123  						Args: []parser.QueryExpression{
   124  							parser.NewStringValue("2001::01::01"),
   125  						},
   126  					},
   127  				},
   128  			},
   129  		},
   130  		Logs: "NULL\n",
   131  	},
   132  	{
   133  		Input: parser.ShowFlag{
   134  			Flag: parser.Flag{Name: "repository"},
   135  		},
   136  		Logs: "@@REPOSITORY: " + TestDir + "\n",
   137  	},
   138  	{
   139  		Input: parser.SetEnvVar{
   140  			EnvVar: parser.EnvironmentVariable{Name: "CSVQ_PROC_TEST"},
   141  			Value:  parser.NewStringValue("foo"),
   142  		},
   143  	},
   144  	{
   145  		Input: parser.Echo{
   146  			Value: parser.EnvironmentVariable{Name: "CSVQ_PROC_TEST"},
   147  		},
   148  		Logs: "foo\n",
   149  	},
   150  	{
   151  		Input: parser.Print{
   152  			Value: parser.EnvironmentVariable{Name: "CSVQ_PROC_TEST"},
   153  		},
   154  		Logs: "'foo'\n",
   155  	},
   156  	{
   157  		Input: parser.UnsetEnvVar{
   158  			EnvVar: parser.EnvironmentVariable{Name: "CSVQ_PROC_TEST"},
   159  		},
   160  	},
   161  	{
   162  		Input: parser.Print{
   163  			Value: parser.EnvironmentVariable{Name: "CSVQ_PROC_TEST"},
   164  		},
   165  		Logs: "''\n",
   166  	},
   167  	{
   168  		Input: parser.VariableDeclaration{
   169  			Assignments: []parser.VariableAssignment{
   170  				{
   171  					Variable: parser.Variable{Name: "var1"},
   172  				},
   173  			},
   174  		},
   175  	},
   176  	{
   177  		Input: parser.VariableDeclaration{
   178  			Assignments: []parser.VariableAssignment{
   179  				{
   180  					Variable: parser.Variable{Name: "var2"},
   181  				},
   182  			},
   183  		},
   184  	},
   185  	{
   186  		Input: parser.VariableDeclaration{
   187  			Assignments: []parser.VariableAssignment{
   188  				{
   189  					Variable: parser.Variable{Name: "var3"},
   190  				},
   191  			},
   192  		},
   193  	},
   194  	{
   195  		Input: parser.VariableDeclaration{
   196  			Assignments: []parser.VariableAssignment{
   197  				{
   198  					Variable: parser.Variable{Name: "var4"},
   199  				},
   200  			},
   201  		},
   202  	},
   203  	{
   204  		Input: parser.VariableSubstitution{
   205  			Variable: parser.Variable{Name: "var1"},
   206  			Value:    parser.NewIntegerValueFromString("1"),
   207  		},
   208  	},
   209  	{
   210  		Input: parser.Print{
   211  			Value: parser.Variable{Name: "var1"},
   212  		},
   213  		Logs: "1\n",
   214  	},
   215  	{
   216  		Input: parser.DisposeVariable{
   217  			Variable: parser.Variable{Name: "var4"},
   218  		},
   219  	},
   220  	{
   221  		Input: parser.VariableDeclaration{
   222  			Assignments: []parser.VariableAssignment{
   223  				{
   224  					Variable: parser.Variable{Name: "var4"},
   225  				},
   226  			},
   227  		},
   228  	},
   229  	{
   230  		Input: parser.FunctionDeclaration{
   231  			Name: parser.Identifier{Literal: "userfunc"},
   232  			Parameters: []parser.VariableAssignment{
   233  				{
   234  					Variable: parser.Variable{Name: "arg1"},
   235  				},
   236  			},
   237  			Statements: []parser.Statement{
   238  				parser.Print{
   239  					Value: parser.Variable{Name: "arg1"},
   240  				},
   241  			},
   242  		},
   243  	},
   244  	{
   245  		Input: parser.Function{
   246  			Name: "userfunc",
   247  			Args: []parser.QueryExpression{
   248  				parser.NewIntegerValueFromString("1"),
   249  			},
   250  		},
   251  		Logs: "1\n",
   252  	},
   253  	{
   254  		Input: parser.DisposeFunction{
   255  			Name: parser.Identifier{Literal: "userfunc"},
   256  		},
   257  		Logs: "",
   258  	},
   259  	{
   260  		Input: parser.Function{
   261  			Name: "userfunc",
   262  			Args: []parser.QueryExpression{
   263  				parser.NewIntegerValueFromString("1"),
   264  			},
   265  		},
   266  		Error:      "function userfunc does not exist",
   267  		ReturnCode: ReturnCodeApplicationError,
   268  	},
   269  	{
   270  		Input: parser.CursorDeclaration{
   271  			Cursor: parser.Identifier{Literal: "cur"},
   272  			Query:  selectQueryForCursorTest,
   273  		},
   274  	},
   275  	{
   276  		Input: parser.OpenCursor{
   277  			Cursor: parser.Identifier{Literal: "cur"},
   278  		},
   279  	},
   280  	{
   281  		Input: parser.FetchCursor{
   282  			Cursor: parser.Identifier{Literal: "cur"},
   283  			Position: parser.FetchPosition{
   284  				Position: parser.Token{Token: parser.NEXT, Literal: "next"},
   285  			},
   286  			Variables: []parser.Variable{
   287  				{Name: "var2"},
   288  				{Name: "var3"},
   289  			},
   290  		},
   291  	},
   292  	{
   293  		Input: parser.Print{
   294  			Value: parser.Variable{Name: "var2"},
   295  		},
   296  		Logs: "'1'\n",
   297  	},
   298  	{
   299  		Input: parser.Print{
   300  			Value: parser.Variable{Name: "var3"},
   301  		},
   302  		Logs: "'str1'\n",
   303  	},
   304  	{
   305  		Input: parser.CloseCursor{
   306  			Cursor: parser.Identifier{Literal: "cur"},
   307  		},
   308  	},
   309  	{
   310  		Input: parser.DisposeCursor{
   311  			Cursor: parser.Identifier{Literal: "cur"},
   312  		},
   313  	},
   314  	{
   315  		Input: parser.ViewDeclaration{
   316  			View: parser.Identifier{Literal: "tbl"},
   317  			Fields: []parser.QueryExpression{
   318  				parser.Identifier{Literal: "column1"},
   319  				parser.Identifier{Literal: "column2"},
   320  			},
   321  			Query: parser.SelectQuery{
   322  				SelectEntity: parser.SelectEntity{
   323  					SelectClause: parser.SelectClause{
   324  						Fields: []parser.QueryExpression{
   325  							parser.Field{Object: parser.NewIntegerValueFromString("1")},
   326  							parser.Field{Object: parser.NewIntegerValueFromString("2")},
   327  						},
   328  					},
   329  				},
   330  			},
   331  		},
   332  	},
   333  	{
   334  		Input: parser.StatementPreparation{
   335  			Name:      parser.Identifier{Literal: "stmt"},
   336  			Statement: value.NewString("select 1"),
   337  		},
   338  	},
   339  	{
   340  		Input: parser.ExecuteStatement{
   341  			Name: parser.Identifier{Literal: "invalidstmt"},
   342  		},
   343  		Error:      "statement invalidstmt does not exist",
   344  		ReturnCode: ReturnCodeApplicationError,
   345  	},
   346  	{
   347  		Input: parser.ExecuteStatement{
   348  			Name: parser.Identifier{Literal: "stmt"},
   349  		},
   350  		Logs: "1\n1\n",
   351  	},
   352  	{
   353  		Input: parser.DisposeStatement{
   354  			Name: parser.Identifier{Literal: "stmt"},
   355  		},
   356  	},
   357  	{
   358  		Input: parser.SelectQuery{
   359  			SelectEntity: parser.SelectEntity{
   360  				SelectClause: parser.SelectClause{
   361  					Fields: []parser.QueryExpression{
   362  						parser.Field{
   363  							Object: parser.FieldReference{Column: parser.Identifier{Literal: "column1"}},
   364  						},
   365  						parser.Field{
   366  							Object: parser.FieldReference{Column: parser.Identifier{Literal: "column2"}},
   367  						},
   368  					},
   369  				},
   370  				FromClause: parser.FromClause{
   371  					Tables: []parser.QueryExpression{
   372  						parser.Table{Object: parser.Identifier{Literal: "tbl"}},
   373  					},
   374  				},
   375  			},
   376  		},
   377  		Logs: "column1,column2\n1,2\n",
   378  	},
   379  	{
   380  		Input: parser.SetFlag{
   381  			Flag:  parser.Flag{Name: "format"},
   382  			Value: parser.NewStringValue("text"),
   383  		},
   384  	},
   385  	{
   386  		Input: parser.SelectQuery{
   387  			SelectEntity: parser.SelectEntity{
   388  				SelectClause: parser.SelectClause{
   389  					Fields: []parser.QueryExpression{
   390  						parser.Field{
   391  							Object: parser.FieldReference{Column: parser.Identifier{Literal: "column1"}},
   392  						},
   393  						parser.Field{
   394  							Object: parser.FieldReference{Column: parser.Identifier{Literal: "column2"}},
   395  						},
   396  					},
   397  				},
   398  				FromClause: parser.FromClause{
   399  					Tables: []parser.QueryExpression{
   400  						parser.Table{Object: parser.Identifier{Literal: "tbl"}},
   401  					},
   402  				},
   403  				WhereClause: parser.WhereClause{
   404  					BaseExpr: nil,
   405  					Filter:   parser.NewTernaryValueFromString("false"),
   406  				},
   407  			},
   408  		},
   409  		Logs: "Empty RecordSet\n",
   410  	},
   411  	{
   412  		Input: parser.SetFlag{
   413  			Flag:  parser.Flag{Name: "without_header"},
   414  			Value: parser.NewTernaryValueFromString("true"),
   415  		},
   416  	},
   417  	{
   418  		Input: parser.SetFlag{
   419  			Flag:  parser.Flag{Name: "format"},
   420  			Value: parser.NewStringValue("csv"),
   421  		},
   422  	},
   423  	{
   424  		Input: parser.SelectQuery{
   425  			SelectEntity: parser.SelectEntity{
   426  				SelectClause: parser.SelectClause{
   427  					Fields: []parser.QueryExpression{
   428  						parser.Field{
   429  							Object: parser.FieldReference{Column: parser.Identifier{Literal: "column1"}},
   430  						},
   431  						parser.Field{
   432  							Object: parser.FieldReference{Column: parser.Identifier{Literal: "column2"}},
   433  						},
   434  					},
   435  				},
   436  				FromClause: parser.FromClause{
   437  					Tables: []parser.QueryExpression{
   438  						parser.Table{Object: parser.Identifier{Literal: "tbl"}},
   439  					},
   440  				},
   441  				WhereClause: parser.WhereClause{
   442  					BaseExpr: nil,
   443  					Filter:   parser.NewTernaryValueFromString("false"),
   444  				},
   445  			},
   446  		},
   447  		Logs: "",
   448  	},
   449  	{
   450  		Input: parser.SetFlag{
   451  			Flag:  parser.Flag{Name: "without_header"},
   452  			Value: parser.NewTernaryValueFromString("false"),
   453  		},
   454  	},
   455  	{
   456  		Input: parser.SelectQuery{
   457  			SelectEntity: parser.SelectEntity{
   458  				SelectClause: parser.SelectClause{
   459  					Fields: []parser.QueryExpression{
   460  						parser.Field{
   461  							Object: parser.NewIntegerValueFromString("1234"),
   462  						},
   463  					},
   464  				},
   465  				IntoClause: parser.IntoClause{
   466  					Variables: []parser.Variable{
   467  						{Name: "var4"},
   468  					},
   469  				},
   470  			},
   471  		},
   472  	},
   473  	{
   474  		Input: parser.Print{
   475  			Value: parser.Variable{Name: "var4"},
   476  		},
   477  		Logs: "1234\n",
   478  	},
   479  	{
   480  		Input: parser.DisposeView{
   481  			View: parser.Identifier{Literal: "tbl"},
   482  		},
   483  	},
   484  	{
   485  		Input: parser.AggregateDeclaration{
   486  			Name:   parser.Identifier{Literal: "useraggfunc"},
   487  			Cursor: parser.Identifier{Literal: "list"},
   488  			Statements: []parser.Statement{
   489  				parser.VariableDeclaration{
   490  					Assignments: []parser.VariableAssignment{
   491  						{
   492  							Variable: parser.Variable{Name: "value"},
   493  						},
   494  						{
   495  							Variable: parser.Variable{Name: "fetch"},
   496  						},
   497  					},
   498  				},
   499  				parser.WhileInCursor{
   500  					Variables: []parser.Variable{
   501  						{Name: "fetch"},
   502  					},
   503  					Cursor: parser.Identifier{Literal: "list"},
   504  					Statements: []parser.Statement{
   505  						parser.If{
   506  							Condition: parser.Is{
   507  								LHS: parser.Variable{Name: "fetch"},
   508  								RHS: parser.NewNullValue(),
   509  							},
   510  							Statements: []parser.Statement{
   511  								parser.FlowControl{Token: parser.CONTINUE},
   512  							},
   513  						},
   514  						parser.If{
   515  							Condition: parser.Is{
   516  								LHS: parser.Variable{Name: "value"},
   517  								RHS: parser.NewNullValue(),
   518  							},
   519  							Statements: []parser.Statement{
   520  								parser.VariableSubstitution{
   521  									Variable: parser.Variable{Name: "value"},
   522  									Value:    parser.Variable{Name: "fetch"},
   523  								},
   524  								parser.FlowControl{Token: parser.CONTINUE},
   525  							},
   526  						},
   527  						parser.VariableSubstitution{
   528  							Variable: parser.Variable{Name: "value"},
   529  							Value: parser.Arithmetic{
   530  								LHS:      parser.Variable{Name: "value"},
   531  								RHS:      parser.Variable{Name: "fetch"},
   532  								Operator: parser.Token{Token: '*', Literal: "*"},
   533  							},
   534  						},
   535  					},
   536  				},
   537  				parser.Return{
   538  					Value: parser.Variable{Name: "value"},
   539  				},
   540  			},
   541  		},
   542  	},
   543  	{
   544  		Input: parser.SelectQuery{
   545  			SelectEntity: parser.SelectEntity{
   546  				SelectClause: parser.SelectClause{
   547  					Fields: []parser.QueryExpression{
   548  						parser.Field{
   549  							Object: parser.Function{
   550  								Name: "useraggfunc",
   551  								Args: []parser.QueryExpression{
   552  									parser.FieldReference{Column: parser.Identifier{Literal: "column1"}},
   553  								},
   554  							},
   555  							Alias: parser.Identifier{Literal: "multiplication"},
   556  						},
   557  					},
   558  				},
   559  				FromClause: parser.FromClause{
   560  					Tables: []parser.QueryExpression{
   561  						parser.Table{Object: parser.Identifier{Literal: "table1"}},
   562  					},
   563  				},
   564  			},
   565  		},
   566  		Logs: "multiplication\n6\n",
   567  	},
   568  	{
   569  		Input: parser.SelectQuery{
   570  			SelectEntity: parser.SelectEntity{
   571  				SelectClause: parser.SelectClause{
   572  					Fields: []parser.QueryExpression{
   573  						parser.Field{
   574  							Object: parser.Variable{Name: "var1"},
   575  							Alias:  parser.Identifier{Literal: "var1"},
   576  						},
   577  					},
   578  				},
   579  			},
   580  		},
   581  		Logs: "var1\n1\n",
   582  	},
   583  	{
   584  		Input: parser.SetFlag{
   585  			Flag:  parser.Flag{Name: "strip_ending_line_break"},
   586  			Value: parser.NewTernaryValue(ternary.TRUE),
   587  		},
   588  	},
   589  	{
   590  		Input: parser.SelectQuery{
   591  			SelectEntity: parser.SelectEntity{
   592  				SelectClause: parser.SelectClause{
   593  					Fields: []parser.QueryExpression{
   594  						parser.Field{
   595  							Object: parser.Variable{Name: "var1"},
   596  							Alias:  parser.Identifier{Literal: "var1"},
   597  						},
   598  					},
   599  				},
   600  			},
   601  		},
   602  		Logs: "var1\n1",
   603  	},
   604  	{
   605  		Input: parser.SetFlag{
   606  			Flag:  parser.Flag{Name: "strip_ending_line_break"},
   607  			Value: parser.NewTernaryValue(ternary.FALSE),
   608  		},
   609  	},
   610  	{
   611  		Input: parser.VariableDeclaration{
   612  			Assignments: []parser.VariableAssignment{
   613  				{
   614  					Variable: parser.Variable{Name: "var1"},
   615  				},
   616  			},
   617  		},
   618  		Error:      "variable @var1 is redeclared",
   619  		ReturnCode: ReturnCodeApplicationError,
   620  	},
   621  	{
   622  		Input: parser.VariableSubstitution{
   623  			Variable: parser.Variable{Name: "var9"},
   624  			Value:    parser.NewIntegerValueFromString("1"),
   625  		},
   626  		Error:      "variable @var9 is undeclared",
   627  		ReturnCode: ReturnCodeApplicationError,
   628  	},
   629  	{
   630  		Input: parser.InsertQuery{
   631  			Table: parser.Table{Object: parser.Identifier{Literal: "table1"}},
   632  			Fields: []parser.QueryExpression{
   633  				parser.FieldReference{Column: parser.Identifier{Literal: "column1"}},
   634  				parser.FieldReference{Column: parser.Identifier{Literal: "column2"}},
   635  			},
   636  			ValuesList: []parser.QueryExpression{
   637  				parser.RowValue{
   638  					Value: parser.ValueList{
   639  						Values: []parser.QueryExpression{
   640  							parser.NewIntegerValueFromString("4"),
   641  							parser.NewStringValue("str4"),
   642  						},
   643  					},
   644  				},
   645  				parser.RowValue{
   646  					Value: parser.ValueList{
   647  						Values: []parser.QueryExpression{
   648  							parser.NewIntegerValueFromString("5"),
   649  							parser.NewStringValue("str5"),
   650  						},
   651  					},
   652  				},
   653  			},
   654  		},
   655  		UncommittedViews: UncommittedViews{
   656  			mtx:     &sync.RWMutex{},
   657  			Created: map[string]*FileInfo{},
   658  			Updated: map[string]*FileInfo{
   659  				strings.ToUpper(GetTestFilePath("TABLE1.CSV")): {
   660  					Path:      GetTestFilePath("table1.csv"),
   661  					Delimiter: ',',
   662  					NoHeader:  false,
   663  					Encoding:  text.UTF8,
   664  					LineBreak: text.LF,
   665  					ForUpdate: true,
   666  				},
   667  			},
   668  		},
   669  		Logs: fmt.Sprintf("2 records inserted on %q.\n", GetTestFilePath("table1.csv")),
   670  	},
   671  	{
   672  		Input: parser.UpdateQuery{
   673  			Tables: []parser.QueryExpression{
   674  				parser.Table{Object: parser.Identifier{Literal: "table1"}},
   675  			},
   676  			SetList: []parser.UpdateSet{
   677  				{
   678  					Field: parser.FieldReference{Column: parser.Identifier{Literal: "column2"}},
   679  					Value: parser.NewStringValue("update"),
   680  				},
   681  			},
   682  			WhereClause: parser.WhereClause{
   683  				Filter: parser.Comparison{
   684  					LHS:      parser.FieldReference{Column: parser.Identifier{Literal: "column1"}},
   685  					RHS:      parser.NewIntegerValueFromString("2"),
   686  					Operator: parser.Token{Token: '=', Literal: "="},
   687  				},
   688  			},
   689  		},
   690  		UncommittedViews: UncommittedViews{
   691  			mtx:     &sync.RWMutex{},
   692  			Created: map[string]*FileInfo{},
   693  			Updated: map[string]*FileInfo{
   694  				strings.ToUpper(GetTestFilePath("TABLE1.CSV")): {
   695  					Path:      GetTestFilePath("table1.csv"),
   696  					Delimiter: ',',
   697  					NoHeader:  false,
   698  					Encoding:  text.UTF8,
   699  					LineBreak: text.LF,
   700  					ForUpdate: true,
   701  				},
   702  			},
   703  		},
   704  		Logs: fmt.Sprintf("1 record updated on %q.\n", GetTestFilePath("table1.csv")),
   705  	},
   706  	{
   707  		Input: parser.ReplaceQuery{
   708  			Table: parser.Table{Object: parser.Identifier{Literal: "table1"}},
   709  			Fields: []parser.QueryExpression{
   710  				parser.FieldReference{Column: parser.Identifier{Literal: "column1"}},
   711  				parser.FieldReference{Column: parser.Identifier{Literal: "column2"}},
   712  			},
   713  			Keys: []parser.QueryExpression{
   714  				parser.FieldReference{Column: parser.Identifier{Literal: "column1"}},
   715  			},
   716  			ValuesList: []parser.QueryExpression{
   717  				parser.RowValue{
   718  					Value: parser.ValueList{
   719  						Values: []parser.QueryExpression{
   720  							parser.NewIntegerValueFromString("4"),
   721  							parser.NewStringValue("str44"),
   722  						},
   723  					},
   724  				},
   725  				parser.RowValue{
   726  					Value: parser.ValueList{
   727  						Values: []parser.QueryExpression{
   728  							parser.NewIntegerValueFromString("6"),
   729  							parser.NewStringValue("str6"),
   730  						},
   731  					},
   732  				},
   733  			},
   734  		},
   735  		UncommittedViews: UncommittedViews{
   736  			mtx:     &sync.RWMutex{},
   737  			Created: map[string]*FileInfo{},
   738  			Updated: map[string]*FileInfo{
   739  				strings.ToUpper(GetTestFilePath("TABLE1.CSV")): {
   740  					Path:      GetTestFilePath("table1.csv"),
   741  					Delimiter: ',',
   742  					NoHeader:  false,
   743  					Encoding:  text.UTF8,
   744  					LineBreak: text.LF,
   745  					ForUpdate: true,
   746  				},
   747  			},
   748  		},
   749  		Logs: fmt.Sprintf("2 records replaced on %q.\n", GetTestFilePath("table1.csv")),
   750  	},
   751  	{
   752  		Input: parser.DeleteQuery{
   753  			FromClause: parser.FromClause{
   754  				Tables: []parser.QueryExpression{
   755  					parser.Table{
   756  						Object: parser.Identifier{Literal: "table1"},
   757  					},
   758  				},
   759  			},
   760  			WhereClause: parser.WhereClause{
   761  				Filter: parser.Comparison{
   762  					LHS:      parser.FieldReference{Column: parser.Identifier{Literal: "column1"}},
   763  					RHS:      parser.NewIntegerValueFromString("2"),
   764  					Operator: parser.Token{Token: '=', Literal: "="},
   765  				},
   766  			},
   767  		},
   768  		UncommittedViews: UncommittedViews{
   769  			mtx:     &sync.RWMutex{},
   770  			Created: map[string]*FileInfo{},
   771  			Updated: map[string]*FileInfo{
   772  				strings.ToUpper(GetTestFilePath("TABLE1.CSV")): {
   773  					Path:      GetTestFilePath("table1.csv"),
   774  					Delimiter: ',',
   775  					NoHeader:  false,
   776  					Encoding:  text.UTF8,
   777  					LineBreak: text.LF,
   778  					ForUpdate: true,
   779  				},
   780  			},
   781  		},
   782  		Logs: fmt.Sprintf("1 record deleted on %q.\n", GetTestFilePath("table1.csv")),
   783  	},
   784  	{
   785  		Input: parser.CreateTable{
   786  			Table: parser.Identifier{Literal: "newtable.csv"},
   787  			Fields: []parser.QueryExpression{
   788  				parser.Identifier{Literal: "column1"},
   789  				parser.Identifier{Literal: "column2"},
   790  			},
   791  		},
   792  		UncommittedViews: UncommittedViews{
   793  			mtx: &sync.RWMutex{},
   794  			Created: map[string]*FileInfo{
   795  				strings.ToUpper(GetTestFilePath("NEWTABLE.CSV")): {
   796  					Path:      GetTestFilePath("newtable.csv"),
   797  					Delimiter: ',',
   798  					NoHeader:  false,
   799  					Encoding:  text.UTF8,
   800  					LineBreak: text.LF,
   801  					ForUpdate: true,
   802  				},
   803  			},
   804  			Updated: map[string]*FileInfo{},
   805  		},
   806  		Logs: fmt.Sprintf("file %q is created.\n", GetTestFilePath("newtable.csv")),
   807  	},
   808  	{
   809  		Input: parser.CreateTable{
   810  			Table: parser.Identifier{Literal: "table1.csv"},
   811  			Fields: []parser.QueryExpression{
   812  				parser.Identifier{Literal: "column1"},
   813  				parser.Identifier{Literal: "column2"},
   814  			},
   815  		},
   816  		Error:      fmt.Sprintf("file %s already exists", GetTestFilePath("table1.csv")),
   817  		ReturnCode: ReturnCodeIOError,
   818  	},
   819  	{
   820  		Input: parser.CreateTable{
   821  			Table: parser.Identifier{Literal: "table1.csv"},
   822  			Fields: []parser.QueryExpression{
   823  				parser.Identifier{Literal: "column1"},
   824  				parser.Identifier{Literal: "column2"},
   825  			},
   826  			IfNotExists: true,
   827  		},
   828  		Logs: fmt.Sprintf("file %q already exists.\n", GetTestFilePath("table1.csv")),
   829  	},
   830  	{
   831  		Input: parser.CreateTable{
   832  			Table: parser.Identifier{Literal: "table1.csv"},
   833  			Fields: []parser.QueryExpression{
   834  				parser.Identifier{Literal: "column1"},
   835  				parser.Identifier{Literal: "column2"},
   836  				parser.Identifier{Literal: "column3"},
   837  			},
   838  			IfNotExists: true,
   839  		},
   840  		Error:      "field length does not match",
   841  		ReturnCode: ReturnCodeApplicationError,
   842  	},
   843  	{
   844  		Input: parser.CreateTable{
   845  			Table: parser.Identifier{Literal: "table1.csv"},
   846  			Fields: []parser.QueryExpression{
   847  				parser.Identifier{Literal: "column1"},
   848  				parser.Identifier{Literal: "col"},
   849  			},
   850  			IfNotExists: true,
   851  		},
   852  		Error:      "field col does not exist",
   853  		ReturnCode: ReturnCodeApplicationError,
   854  	},
   855  	{
   856  		Input: parser.AddColumns{
   857  			Table: parser.Identifier{Literal: "table1.csv"},
   858  			Columns: []parser.ColumnDefault{
   859  				{
   860  					Column: parser.Identifier{Literal: "column3"},
   861  				},
   862  			},
   863  		},
   864  		UncommittedViews: UncommittedViews{
   865  			mtx:     &sync.RWMutex{},
   866  			Created: map[string]*FileInfo{},
   867  			Updated: map[string]*FileInfo{
   868  				strings.ToUpper(GetTestFilePath("TABLE1.CSV")): {
   869  					Path:      GetTestFilePath("table1.csv"),
   870  					Delimiter: ',',
   871  					NoHeader:  false,
   872  					Encoding:  text.UTF8,
   873  					LineBreak: text.LF,
   874  					ForUpdate: true,
   875  				},
   876  			},
   877  		},
   878  		Logs: fmt.Sprintf("1 field added on %q.\n", GetTestFilePath("table1.csv")),
   879  	},
   880  	{
   881  		Input: parser.DropColumns{
   882  			Table: parser.Identifier{Literal: "table1"},
   883  			Columns: []parser.QueryExpression{
   884  				parser.FieldReference{Column: parser.Identifier{Literal: "column1"}},
   885  			},
   886  		},
   887  		UncommittedViews: UncommittedViews{
   888  			mtx:     &sync.RWMutex{},
   889  			Created: map[string]*FileInfo{},
   890  			Updated: map[string]*FileInfo{
   891  				strings.ToUpper(GetTestFilePath("TABLE1.CSV")): {
   892  					Path:      GetTestFilePath("table1.csv"),
   893  					Delimiter: ',',
   894  					NoHeader:  false,
   895  					Encoding:  text.UTF8,
   896  					LineBreak: text.LF,
   897  					ForUpdate: true,
   898  				},
   899  			},
   900  		},
   901  		Logs: fmt.Sprintf("1 field dropped on %q.\n", GetTestFilePath("table1.csv")),
   902  	},
   903  	{
   904  		Input: parser.RenameColumn{
   905  			Table: parser.Identifier{Literal: "table1"},
   906  			Old:   parser.FieldReference{Column: parser.Identifier{Literal: "column1"}},
   907  			New:   parser.Identifier{Literal: "newcolumn"},
   908  		},
   909  		UncommittedViews: UncommittedViews{
   910  			mtx:     &sync.RWMutex{},
   911  			Created: map[string]*FileInfo{},
   912  			Updated: map[string]*FileInfo{
   913  				strings.ToUpper(GetTestFilePath("TABLE1.CSV")): {
   914  					Path:      GetTestFilePath("table1.csv"),
   915  					Delimiter: ',',
   916  					NoHeader:  false,
   917  					Encoding:  text.UTF8,
   918  					LineBreak: text.LF,
   919  					ForUpdate: true,
   920  				},
   921  			},
   922  		},
   923  		Logs: fmt.Sprintf("1 field renamed on %q.\n", GetTestFilePath("table1.csv")),
   924  	},
   925  	{
   926  		Input: parser.SetTableAttribute{
   927  			Table:     parser.Identifier{Literal: "table1.csv"},
   928  			Attribute: parser.Identifier{Literal: "delimiter"},
   929  			Value:     parser.NewStringValue(","),
   930  		},
   931  		Logs: "Table attributes of " + GetTestFilePath("table1.csv") + " remain unchanged.\n",
   932  	},
   933  	{
   934  		Input: parser.SetTableAttribute{
   935  			Table:     parser.Identifier{Literal: "table1.csv"},
   936  			Attribute: parser.Identifier{Literal: "delimiter"},
   937  			Value:     parser.NewStringValue("\t"),
   938  		},
   939  		UncommittedViews: UncommittedViews{
   940  			mtx:     &sync.RWMutex{},
   941  			Created: map[string]*FileInfo{},
   942  			Updated: map[string]*FileInfo{
   943  				strings.ToUpper(GetTestFilePath("TABLE1.CSV")): {
   944  					Path:      GetTestFilePath("table1.csv"),
   945  					Delimiter: '\t',
   946  					NoHeader:  false,
   947  					Encoding:  text.UTF8,
   948  					LineBreak: text.LF,
   949  					Format:    option.TSV,
   950  					ForUpdate: true,
   951  				},
   952  			},
   953  		},
   954  		Logs: "\n" +
   955  			strings.Repeat(" ", (calcShowFieldsWidth("table1.csv", "table1.csv", 22)-(22+len("table1.csv")))/2) + "Attributes Updated in table1.csv\n" +
   956  			strings.Repeat("-", calcShowFieldsWidth("table1.csv", "table1.csv", 22)) + "\n" +
   957  			" Path: " + GetTestFilePath("table1.csv") + "\n" +
   958  			" Format: TSV     Delimiter: '\\t'  Enclose All: false\n" +
   959  			" Encoding: UTF8  LineBreak: LF    Header: true\n" +
   960  			"\n",
   961  	},
   962  	{
   963  		Input: parser.Case{
   964  			When: []parser.CaseWhen{
   965  				{
   966  					Condition: parser.NewTernaryValue(ternary.FALSE),
   967  					Statements: []parser.Statement{
   968  						parser.Print{Value: parser.NewStringValue("1")},
   969  					},
   970  				},
   971  				{
   972  					Condition: parser.NewTernaryValue(ternary.TRUE),
   973  					Statements: []parser.Statement{
   974  						parser.Print{Value: parser.NewStringValue("2")},
   975  					},
   976  				},
   977  			},
   978  		},
   979  		Logs: "'2'\n",
   980  	},
   981  	{
   982  		Input: parser.While{
   983  			Condition: parser.Comparison{
   984  				LHS:      parser.Variable{Name: "while_test"},
   985  				RHS:      parser.NewIntegerValueFromString("3"),
   986  				Operator: parser.Token{Token: '<', Literal: "<"},
   987  			},
   988  			Statements: []parser.Statement{
   989  				parser.VariableSubstitution{
   990  					Variable: parser.Variable{Name: "while_test"},
   991  					Value: parser.Arithmetic{
   992  						LHS:      parser.Variable{Name: "while_test"},
   993  						RHS:      parser.NewIntegerValueFromString("1"),
   994  						Operator: parser.Token{Token: '+', Literal: "+"},
   995  					},
   996  				},
   997  				parser.Print{Value: parser.Variable{Name: "while_test"}},
   998  			},
   999  		},
  1000  		Logs: "1\n2\n3\n",
  1001  	},
  1002  	{
  1003  		Input: parser.Exit{
  1004  			Code: value.NewInteger(1),
  1005  		},
  1006  		Error:      ExitMessage,
  1007  		ReturnCode: 1,
  1008  	},
  1009  	{
  1010  		Input: parser.Print{
  1011  			Value: parser.NewIntegerValue(12345),
  1012  		},
  1013  		Logs: "12345\n",
  1014  	},
  1015  	{
  1016  		Input: parser.Printf{
  1017  			Format: parser.NewStringValue("value: %s"),
  1018  			Values: []parser.QueryExpression{
  1019  				parser.NewIntegerValue(12345),
  1020  			},
  1021  		},
  1022  		Logs: "value: 12345\n",
  1023  	},
  1024  	{
  1025  		Input: parser.Source{
  1026  			FilePath: parser.NewStringValue(GetTestFilePath("source.sql")),
  1027  		},
  1028  		Logs: "'external executable file'\n",
  1029  	},
  1030  	{
  1031  		Input: parser.Execute{
  1032  			BaseExpr:   parser.NewBaseExpr(parser.Token{}),
  1033  			Statements: parser.NewStringValue("print 'execute';"),
  1034  		},
  1035  		Logs: "'execute'\n",
  1036  	},
  1037  	{
  1038  		Input: parser.Trigger{
  1039  			Event:   parser.Identifier{Literal: "error"},
  1040  			Message: parser.NewStringValue("user error"),
  1041  			Code:    value.NewInteger(200),
  1042  		},
  1043  		Error:      "user error",
  1044  		ReturnCode: 200,
  1045  	},
  1046  	{
  1047  		Input: parser.Trigger{
  1048  			Event:   parser.Identifier{Literal: "error"},
  1049  			Message: parser.NewIntegerValue(200),
  1050  		},
  1051  		Error:      DefaultUserTriggeredErrorMessage,
  1052  		ReturnCode: 200,
  1053  	},
  1054  	{
  1055  		Input: parser.Trigger{
  1056  			Event:   parser.Identifier{Literal: "invalid"},
  1057  			Message: parser.NewIntegerValue(200),
  1058  		},
  1059  		Error:      "invalid is an unknown event",
  1060  		ReturnCode: ReturnCodeApplicationError,
  1061  	},
  1062  	{
  1063  		Input: parser.ShowObjects{
  1064  			Type: parser.Identifier{Literal: "cursors"},
  1065  		},
  1066  		Logs: "No cursor is declared\n",
  1067  	},
  1068  	{
  1069  		Input: parser.ShowFields{
  1070  			Type:  parser.Identifier{Literal: "fields"},
  1071  			Table: parser.Identifier{Literal: "table1"},
  1072  		},
  1073  		Logs: "\n" +
  1074  			strings.Repeat(" ", (calcShowFieldsWidth("table1.csv", "table1", 10)-(10+len("table1")))/2) + "Fields in table1\n" +
  1075  			strings.Repeat("-", calcShowFieldsWidth("table1.csv", "table1", 10)) + "\n" +
  1076  			" Type: File\n" +
  1077  			" Path: " + GetTestFilePath("table1.csv") + "\n" +
  1078  			" Format: CSV     Delimiter: ','   Enclose All: false\n" +
  1079  			" Encoding: UTF8  LineBreak: LF    Header: true\n" +
  1080  			" Status: Fixed\n" +
  1081  			" Fields:\n" +
  1082  			"   1. column1\n" +
  1083  			"   2. column2\n" +
  1084  			"\n",
  1085  	},
  1086  }
  1087  
  1088  func TestProcessor_ExecuteStatement(t *testing.T) {
  1089  	defer func() {
  1090  		_ = TestTx.ReleaseResources()
  1091  		TestTx.UncommittedViews.Clean()
  1092  		TestTx.Session.SetStdout(NewDiscard())
  1093  		initFlag(TestTx.Flags)
  1094  	}()
  1095  
  1096  	TestTx.Flags.Repository = TestDir
  1097  	TestTx.Flags.ExportOptions.Format = option.CSV
  1098  
  1099  	tx := TestTx
  1100  	proc := NewProcessor(tx)
  1101  	_ = proc.ReferenceScope.DeclareVariableDirectly(parser.Variable{Name: "while_test"}, value.NewInteger(0))
  1102  	ctx := context.Background()
  1103  
  1104  	for _, v := range processorExecuteStatementTests {
  1105  		_ = TestTx.ReleaseResources()
  1106  		TestTx.UncommittedViews = NewUncommittedViews()
  1107  
  1108  		out := NewOutput()
  1109  		tx.Session.SetStdout(out)
  1110  		_, err := proc.ExecuteStatement(ctx, v.Input)
  1111  		log := out.String()
  1112  
  1113  		if err != nil {
  1114  			var code int
  1115  			if apperr, ok := err.(Error); ok {
  1116  				if len(v.Error) < 1 {
  1117  					t.Errorf("unexpected error %q for %q", err, v.Input)
  1118  				} else if err.Error() != v.Error {
  1119  					t.Errorf("error %q, want error %q for %q", err, v.Error, v.Input)
  1120  				}
  1121  
  1122  				code = apperr.Code()
  1123  			}
  1124  			if code != v.ReturnCode {
  1125  				t.Errorf("error code %d, want error code %d for %q", code, v.ReturnCode, v.Input)
  1126  			}
  1127  			continue
  1128  		}
  1129  		if 0 < len(v.Error) {
  1130  			t.Errorf("no error, want error %q for %q", v.Error, v.Input)
  1131  			continue
  1132  		}
  1133  
  1134  		if v.UncommittedViews.mtx != nil {
  1135  			for _, r := range TestTx.UncommittedViews.Created {
  1136  				if r.Handler != nil {
  1137  					if r.Path != r.Handler.Path() {
  1138  						t.Errorf("file pointer = %q, want %q for %q", r.Handler.Path(), r.Path, v.Input)
  1139  					}
  1140  					_ = TestTx.FileContainer.Close(r.Handler)
  1141  					r.Handler = nil
  1142  				}
  1143  			}
  1144  			for _, r := range TestTx.UncommittedViews.Updated {
  1145  				if r.Handler != nil {
  1146  					if r.Path != r.Handler.Path() {
  1147  						t.Errorf("file pointer = %q, want %q for %q", r.Handler.Path(), r.Path, v.Input)
  1148  					}
  1149  					_ = TestTx.FileContainer.Close(r.Handler)
  1150  					r.Handler = nil
  1151  				}
  1152  			}
  1153  
  1154  			if !reflect.DeepEqual(TestTx.UncommittedViews, v.UncommittedViews) {
  1155  				t.Errorf("uncomitted views = %v, want %v for %q", TestTx.UncommittedViews, v.UncommittedViews, v.Input)
  1156  			}
  1157  		}
  1158  		if 0 < len(v.Logs) {
  1159  			if log != v.Logs {
  1160  				t.Errorf("logs = %s, want %s for %q", log, v.Logs, v.Input)
  1161  			}
  1162  		}
  1163  		if v.SelectLogs != nil {
  1164  			selectLog := log
  1165  			if !reflect.DeepEqual(selectLog, v.SelectLogs) {
  1166  				t.Errorf("select logs = %s, want %s for %q", selectLog, v.SelectLogs, v.Input)
  1167  			}
  1168  		}
  1169  	}
  1170  }
  1171  
  1172  var processorIfStmtTests = []struct {
  1173  	Name        string
  1174  	Stmt        parser.If
  1175  	ResultFlow  StatementFlow
  1176  	ReturnValue value.Primary
  1177  	Result      string
  1178  	Error       string
  1179  }{
  1180  	{
  1181  		Name: "If Statement",
  1182  		Stmt: parser.If{
  1183  			Condition: parser.NewTernaryValue(ternary.TRUE),
  1184  			Statements: []parser.Statement{
  1185  				parser.Print{Value: parser.NewStringValue("1")},
  1186  			},
  1187  		},
  1188  		ResultFlow: Terminate,
  1189  		Result:     "'1'\n",
  1190  	},
  1191  	{
  1192  		Name: "If Statement Execute Nothing",
  1193  		Stmt: parser.If{
  1194  			Condition: parser.NewTernaryValue(ternary.FALSE),
  1195  			Statements: []parser.Statement{
  1196  				parser.Print{Value: parser.NewStringValue("1")},
  1197  			},
  1198  		},
  1199  		ResultFlow: Terminate,
  1200  		Result:     "",
  1201  	},
  1202  	{
  1203  		Name: "If Statement Execute ElseIf",
  1204  		Stmt: parser.If{
  1205  			Condition: parser.NewTernaryValue(ternary.FALSE),
  1206  			Statements: []parser.Statement{
  1207  				parser.Print{Value: parser.NewStringValue("1")},
  1208  			},
  1209  			ElseIf: []parser.ElseIf{
  1210  				{
  1211  					Condition: parser.NewTernaryValue(ternary.TRUE),
  1212  					Statements: []parser.Statement{
  1213  						parser.Print{Value: parser.NewStringValue("2")},
  1214  					},
  1215  				},
  1216  				{
  1217  					Condition: parser.NewTernaryValue(ternary.FALSE),
  1218  					Statements: []parser.Statement{
  1219  						parser.Print{Value: parser.NewStringValue("3")},
  1220  					},
  1221  				},
  1222  			},
  1223  			Else: parser.Else{
  1224  				Statements: []parser.Statement{
  1225  					parser.Print{Value: parser.NewStringValue("4")},
  1226  				},
  1227  			},
  1228  		},
  1229  		ResultFlow: Terminate,
  1230  		Result:     "'2'\n",
  1231  	},
  1232  	{
  1233  		Name: "If Statement Execute Else",
  1234  		Stmt: parser.If{
  1235  			Condition: parser.NewTernaryValue(ternary.FALSE),
  1236  			Statements: []parser.Statement{
  1237  				parser.Print{Value: parser.NewStringValue("1")},
  1238  			},
  1239  			ElseIf: []parser.ElseIf{
  1240  				{
  1241  					Condition: parser.NewTernaryValue(ternary.FALSE),
  1242  					Statements: []parser.Statement{
  1243  						parser.Print{Value: parser.NewStringValue("2")},
  1244  					},
  1245  				},
  1246  				{
  1247  					Condition: parser.NewTernaryValue(ternary.FALSE),
  1248  					Statements: []parser.Statement{
  1249  						parser.Print{Value: parser.NewStringValue("3")},
  1250  					},
  1251  				},
  1252  			},
  1253  			Else: parser.Else{
  1254  				Statements: []parser.Statement{
  1255  					parser.Print{Value: parser.NewStringValue("4")},
  1256  				},
  1257  			},
  1258  		},
  1259  		ResultFlow: Terminate,
  1260  		Result:     "'4'\n",
  1261  	},
  1262  	{
  1263  		Name: "If Statement Filter Error",
  1264  		Stmt: parser.If{
  1265  			Condition: parser.FieldReference{Column: parser.Identifier{Literal: "notexist"}},
  1266  			Statements: []parser.Statement{
  1267  				parser.Print{Value: parser.NewStringValue("1")},
  1268  			},
  1269  		},
  1270  		Error: "field notexist does not exist",
  1271  	},
  1272  	{
  1273  		Name: "If Statement Return Value",
  1274  		Stmt: parser.If{
  1275  			Condition: parser.NewTernaryValue(ternary.TRUE),
  1276  			Statements: []parser.Statement{
  1277  				parser.Return{Value: parser.NewStringValue("1")},
  1278  			},
  1279  		},
  1280  		ResultFlow:  Return,
  1281  		ReturnValue: value.NewString("1"),
  1282  		Result:      "",
  1283  	},
  1284  }
  1285  
  1286  func TestProcessor_IfStmt(t *testing.T) {
  1287  	defer func() {
  1288  		TestTx.Session.SetStdout(NewDiscard())
  1289  		initFlag(TestTx.Flags)
  1290  	}()
  1291  
  1292  	TestTx.Flags.SetQuiet(true)
  1293  	tx := TestTx
  1294  	proc := NewProcessor(tx)
  1295  
  1296  	for _, v := range processorIfStmtTests {
  1297  		out := NewOutput()
  1298  		tx.Session.SetStdout(out)
  1299  
  1300  		proc.returnVal = nil
  1301  		flow, err := proc.IfStmt(context.Background(), v.Stmt)
  1302  		log := out.String()
  1303  
  1304  		if err != nil {
  1305  			if len(v.Error) < 1 {
  1306  				t.Errorf("%s: unexpected error %q", v.Name, err)
  1307  			} else if err.Error() != v.Error {
  1308  				t.Errorf("%s: error %q, want error %q", v.Name, err.Error(), v.Error)
  1309  			}
  1310  			continue
  1311  		}
  1312  		if 0 < len(v.Error) {
  1313  			t.Errorf("%s: no error, want error %q", v.Name, v.Error)
  1314  			continue
  1315  		}
  1316  		if flow != v.ResultFlow {
  1317  			t.Errorf("%s: result flow = %q, want %q", v.Name, flow, v.ResultFlow)
  1318  		}
  1319  		if !reflect.DeepEqual(proc.returnVal, v.ReturnValue) {
  1320  			t.Errorf("%s: return = %t, want %t", v.Name, proc.returnVal, v.ReturnValue)
  1321  		}
  1322  		if log != v.Result {
  1323  			t.Errorf("%s: result = %q, want %q", v.Name, log, v.Result)
  1324  		}
  1325  	}
  1326  }
  1327  
  1328  var processorCaseStmtTests = []struct {
  1329  	Name       string
  1330  	Stmt       parser.Case
  1331  	ResultFlow StatementFlow
  1332  	Result     string
  1333  	Error      string
  1334  }{
  1335  	{
  1336  		Name: "Case",
  1337  		Stmt: parser.Case{
  1338  			When: []parser.CaseWhen{
  1339  				{
  1340  					Condition: parser.NewTernaryValue(ternary.FALSE),
  1341  					Statements: []parser.Statement{
  1342  						parser.Print{Value: parser.NewStringValue("1")},
  1343  					},
  1344  				},
  1345  				{
  1346  					Condition: parser.NewTernaryValue(ternary.TRUE),
  1347  					Statements: []parser.Statement{
  1348  						parser.Print{Value: parser.NewStringValue("2")},
  1349  					},
  1350  				},
  1351  			},
  1352  		},
  1353  		ResultFlow: Terminate,
  1354  		Result:     "'2'\n",
  1355  	},
  1356  	{
  1357  		Name: "Case Comparison",
  1358  		Stmt: parser.Case{
  1359  			Value: parser.NewIntegerValue(2),
  1360  			When: []parser.CaseWhen{
  1361  				{
  1362  					Condition: parser.NewIntegerValue(1),
  1363  					Statements: []parser.Statement{
  1364  						parser.Print{Value: parser.NewStringValue("1")},
  1365  					},
  1366  				},
  1367  				{
  1368  					Condition: parser.NewIntegerValue(2),
  1369  					Statements: []parser.Statement{
  1370  						parser.Print{Value: parser.NewStringValue("2")},
  1371  					},
  1372  				},
  1373  			},
  1374  		},
  1375  		ResultFlow: Terminate,
  1376  		Result:     "'2'\n",
  1377  	},
  1378  	{
  1379  		Name: "Case Else",
  1380  		Stmt: parser.Case{
  1381  			When: []parser.CaseWhen{
  1382  				{
  1383  					Condition: parser.NewTernaryValue(ternary.FALSE),
  1384  					Statements: []parser.Statement{
  1385  						parser.Print{Value: parser.NewStringValue("1")},
  1386  					},
  1387  				},
  1388  				{
  1389  					Condition: parser.NewTernaryValue(ternary.FALSE),
  1390  					Statements: []parser.Statement{
  1391  						parser.Print{Value: parser.NewStringValue("2")},
  1392  					},
  1393  				},
  1394  			},
  1395  			Else: parser.CaseElse{
  1396  				Statements: []parser.Statement{
  1397  					parser.Print{Value: parser.NewStringValue("3")},
  1398  				},
  1399  			},
  1400  		},
  1401  		ResultFlow: Terminate,
  1402  		Result:     "'3'\n",
  1403  	},
  1404  	{
  1405  		Name: "Case No Match",
  1406  		Stmt: parser.Case{
  1407  			When: []parser.CaseWhen{
  1408  				{
  1409  					Condition: parser.NewTernaryValue(ternary.FALSE),
  1410  					Statements: []parser.Statement{
  1411  						parser.Print{Value: parser.NewStringValue("1")},
  1412  					},
  1413  				},
  1414  				{
  1415  					Condition: parser.NewTernaryValue(ternary.FALSE),
  1416  					Statements: []parser.Statement{
  1417  						parser.Print{Value: parser.NewStringValue("2")},
  1418  					},
  1419  				},
  1420  			},
  1421  		},
  1422  		ResultFlow: Terminate,
  1423  		Result:     "",
  1424  	},
  1425  	{
  1426  		Name: "Case Comparison Value Error",
  1427  		Stmt: parser.Case{
  1428  			Value: parser.FieldReference{Column: parser.Identifier{Literal: "notexist"}},
  1429  			When: []parser.CaseWhen{
  1430  				{
  1431  					Condition: parser.NewIntegerValue(1),
  1432  					Statements: []parser.Statement{
  1433  						parser.Print{Value: parser.NewStringValue("1")},
  1434  					},
  1435  				},
  1436  				{
  1437  					Condition: parser.NewIntegerValue(2),
  1438  					Statements: []parser.Statement{
  1439  						parser.Print{Value: parser.NewStringValue("2")},
  1440  					},
  1441  				},
  1442  			},
  1443  		},
  1444  		ResultFlow: TerminateWithError,
  1445  		Error:      "field notexist does not exist",
  1446  	},
  1447  	{
  1448  		Name: "Case Condition Error",
  1449  		Stmt: parser.Case{
  1450  			When: []parser.CaseWhen{
  1451  				{
  1452  					Condition: parser.FieldReference{Column: parser.Identifier{Literal: "notexist"}},
  1453  					Statements: []parser.Statement{
  1454  						parser.Print{Value: parser.NewStringValue("1")},
  1455  					},
  1456  				},
  1457  			},
  1458  		},
  1459  		ResultFlow: TerminateWithError,
  1460  		Error:      "field notexist does not exist",
  1461  	},
  1462  }
  1463  
  1464  func TestProcessor_Case(t *testing.T) {
  1465  	defer func() {
  1466  		TestTx.Session.SetStdout(NewDiscard())
  1467  		initFlag(TestTx.Flags)
  1468  	}()
  1469  
  1470  	TestTx.Flags.SetQuiet(true)
  1471  	tx := TestTx
  1472  	proc := NewProcessor(tx)
  1473  
  1474  	for _, v := range processorCaseStmtTests {
  1475  		out := NewOutput()
  1476  		tx.Session.SetStdout(out)
  1477  		flow, err := proc.Case(context.Background(), v.Stmt)
  1478  		log := out.String()
  1479  
  1480  		if err != nil {
  1481  			if len(v.Error) < 1 {
  1482  				t.Errorf("%s: unexpected error %q", v.Name, err)
  1483  			} else if err.Error() != v.Error {
  1484  				t.Errorf("%s: error %q, want error %q", v.Name, err.Error(), v.Error)
  1485  			}
  1486  			continue
  1487  		}
  1488  		if 0 < len(v.Error) {
  1489  			t.Errorf("%s: no error, want error %q", v.Name, v.Error)
  1490  			continue
  1491  		}
  1492  		if flow != v.ResultFlow {
  1493  			t.Errorf("%s: result flow = %q, want %q", v.Name, flow, v.ResultFlow)
  1494  		}
  1495  		if log != v.Result {
  1496  			t.Errorf("%s: result = %q, want %q", v.Name, log, v.Result)
  1497  		}
  1498  	}
  1499  }
  1500  
  1501  var processorWhileTests = []struct {
  1502  	Name        string
  1503  	Stmt        parser.While
  1504  	ResultFlow  StatementFlow
  1505  	ReturnValue value.Primary
  1506  	Result      string
  1507  	Error       string
  1508  }{
  1509  	{
  1510  		Name: "While Statement",
  1511  		Stmt: parser.While{
  1512  			Condition: parser.Comparison{
  1513  				LHS:      parser.Variable{Name: "while_test"},
  1514  				RHS:      parser.NewIntegerValueFromString("3"),
  1515  				Operator: parser.Token{Token: parser.COMPARISON_OP, Literal: "<"},
  1516  			},
  1517  			Statements: []parser.Statement{
  1518  				parser.VariableSubstitution{
  1519  					Variable: parser.Variable{Name: "while_test"},
  1520  					Value: parser.Arithmetic{
  1521  						LHS:      parser.Variable{Name: "while_test"},
  1522  						RHS:      parser.NewIntegerValueFromString("1"),
  1523  						Operator: parser.Token{Token: '+', Literal: "+"},
  1524  					},
  1525  				},
  1526  				parser.Print{Value: parser.Variable{Name: "while_test"}},
  1527  				parser.TransactionControl{Token: parser.COMMIT},
  1528  			},
  1529  		},
  1530  		ResultFlow: Terminate,
  1531  		Result:     "1\n2\n3\n",
  1532  	},
  1533  	{
  1534  		Name: "While Statement Continue",
  1535  		Stmt: parser.While{
  1536  			Condition: parser.Comparison{
  1537  				LHS:      parser.Variable{Name: "while_test_count"},
  1538  				RHS:      parser.NewIntegerValueFromString("3"),
  1539  				Operator: parser.Token{Token: parser.COMPARISON_OP, Literal: "<"},
  1540  			},
  1541  			Statements: []parser.Statement{
  1542  				parser.VariableSubstitution{
  1543  					Variable: parser.Variable{Name: "while_test_count"},
  1544  					Value: parser.Arithmetic{
  1545  						LHS:      parser.Variable{Name: "while_test_count"},
  1546  						RHS:      parser.NewIntegerValueFromString("1"),
  1547  						Operator: parser.Token{Token: '+', Literal: "+"},
  1548  					},
  1549  				},
  1550  				parser.VariableSubstitution{
  1551  					Variable: parser.Variable{Name: "while_test"},
  1552  					Value: parser.Arithmetic{
  1553  						LHS:      parser.Variable{Name: "while_test"},
  1554  						RHS:      parser.NewIntegerValueFromString("1"),
  1555  						Operator: parser.Token{Token: '+', Literal: "+"},
  1556  					},
  1557  				},
  1558  				parser.If{
  1559  					Condition: parser.Comparison{
  1560  						LHS:      parser.Variable{Name: "while_test_count"},
  1561  						RHS:      parser.NewIntegerValueFromString("2"),
  1562  						Operator: parser.Token{Token: '=', Literal: "="},
  1563  					},
  1564  					Statements: []parser.Statement{
  1565  						parser.FlowControl{Token: parser.CONTINUE},
  1566  					},
  1567  				},
  1568  				parser.Print{Value: parser.Variable{Name: "while_test"}},
  1569  				parser.TransactionControl{Token: parser.COMMIT},
  1570  			},
  1571  		},
  1572  		ResultFlow: Terminate,
  1573  		Result:     "1\n3\n",
  1574  	},
  1575  	{
  1576  		Name: "While Statement Break",
  1577  		Stmt: parser.While{
  1578  			Condition: parser.Comparison{
  1579  				LHS:      parser.Variable{Name: "while_test_count"},
  1580  				RHS:      parser.NewIntegerValueFromString("3"),
  1581  				Operator: parser.Token{Token: parser.COMPARISON_OP, Literal: "<"},
  1582  			},
  1583  			Statements: []parser.Statement{
  1584  				parser.VariableSubstitution{
  1585  					Variable: parser.Variable{Name: "while_test_count"},
  1586  					Value: parser.Arithmetic{
  1587  						LHS:      parser.Variable{Name: "while_test_count"},
  1588  						RHS:      parser.NewIntegerValueFromString("1"),
  1589  						Operator: parser.Token{Token: '+', Literal: "+"},
  1590  					},
  1591  				},
  1592  				parser.VariableSubstitution{
  1593  					Variable: parser.Variable{Name: "while_test"},
  1594  					Value: parser.Arithmetic{
  1595  						LHS:      parser.Variable{Name: "while_test"},
  1596  						RHS:      parser.NewIntegerValueFromString("1"),
  1597  						Operator: parser.Token{Token: '+', Literal: "+"},
  1598  					},
  1599  				},
  1600  				parser.If{
  1601  					Condition: parser.Comparison{
  1602  						LHS:      parser.Variable{Name: "while_test_count"},
  1603  						RHS:      parser.NewIntegerValueFromString("2"),
  1604  						Operator: parser.Token{Token: '=', Literal: "="},
  1605  					},
  1606  					Statements: []parser.Statement{
  1607  						parser.FlowControl{Token: parser.BREAK},
  1608  					},
  1609  				},
  1610  				parser.Print{Value: parser.Variable{Name: "while_test"}},
  1611  				parser.TransactionControl{Token: parser.COMMIT},
  1612  			},
  1613  		},
  1614  		ResultFlow: Terminate,
  1615  		Result:     "1\n",
  1616  	},
  1617  	{
  1618  		Name: "While Statement Exit",
  1619  		Stmt: parser.While{
  1620  			Condition: parser.Comparison{
  1621  				LHS:      parser.Variable{Name: "while_test_count"},
  1622  				RHS:      parser.NewIntegerValueFromString("3"),
  1623  				Operator: parser.Token{Token: parser.COMPARISON_OP, Literal: "<"},
  1624  			},
  1625  			Statements: []parser.Statement{
  1626  				parser.VariableSubstitution{
  1627  					Variable: parser.Variable{Name: "while_test_count"},
  1628  					Value: parser.Arithmetic{
  1629  						LHS:      parser.Variable{Name: "while_test_count"},
  1630  						RHS:      parser.NewIntegerValueFromString("1"),
  1631  						Operator: parser.Token{Token: '+', Literal: "+"},
  1632  					},
  1633  				},
  1634  				parser.VariableSubstitution{
  1635  					Variable: parser.Variable{Name: "while_test"},
  1636  					Value: parser.Arithmetic{
  1637  						LHS:      parser.Variable{Name: "while_test"},
  1638  						RHS:      parser.NewIntegerValueFromString("1"),
  1639  						Operator: parser.Token{Token: '+', Literal: "+"},
  1640  					},
  1641  				},
  1642  				parser.If{
  1643  					Condition: parser.Comparison{
  1644  						LHS:      parser.Variable{Name: "while_test_count"},
  1645  						RHS:      parser.NewIntegerValueFromString("2"),
  1646  						Operator: parser.Token{Token: '=', Literal: "="},
  1647  					},
  1648  					Statements: []parser.Statement{
  1649  						parser.Exit{},
  1650  					},
  1651  				},
  1652  				parser.Print{Value: parser.Variable{Name: "while_test"}},
  1653  				parser.TransactionControl{Token: parser.COMMIT},
  1654  			},
  1655  		},
  1656  		ResultFlow: Exit,
  1657  		Result:     "1\n",
  1658  	},
  1659  	{
  1660  		Name: "While Statement Filter Error",
  1661  		Stmt: parser.While{
  1662  			Condition: parser.Comparison{
  1663  				LHS:      parser.Variable{Name: "while_test"},
  1664  				RHS:      parser.FieldReference{Column: parser.Identifier{Literal: "notexist"}},
  1665  				Operator: parser.Token{Token: parser.COMPARISON_OP, Literal: "<"},
  1666  			},
  1667  			Statements: []parser.Statement{
  1668  				parser.VariableSubstitution{
  1669  					Variable: parser.Variable{Name: "while_test"},
  1670  					Value: parser.Arithmetic{
  1671  						LHS:      parser.Variable{Name: "while_test"},
  1672  						RHS:      parser.NewIntegerValueFromString("1"),
  1673  						Operator: parser.Token{Token: '+', Literal: "+"},
  1674  					},
  1675  				},
  1676  				parser.Print{Value: parser.Variable{Name: "while_test"}},
  1677  				parser.TransactionControl{Token: parser.COMMIT},
  1678  			},
  1679  		},
  1680  		Error: "field notexist does not exist",
  1681  	},
  1682  	{
  1683  		Name: "While Statement Execution Error",
  1684  		Stmt: parser.While{
  1685  			Condition: parser.Comparison{
  1686  				LHS:      parser.Variable{Name: "while_test"},
  1687  				RHS:      parser.NewIntegerValueFromString("3"),
  1688  				Operator: parser.Token{Token: parser.COMPARISON_OP, Literal: "<"},
  1689  			},
  1690  			Statements: []parser.Statement{
  1691  				parser.VariableSubstitution{
  1692  					Variable: parser.Variable{Name: "while_test"},
  1693  					Value: parser.Arithmetic{
  1694  						LHS:      parser.Variable{Name: "while_test"},
  1695  						RHS:      parser.FieldReference{Column: parser.Identifier{Literal: "notexist"}},
  1696  						Operator: parser.Token{Token: '+', Literal: "+"},
  1697  					},
  1698  				},
  1699  				parser.Print{Value: parser.Variable{Name: "while_test"}},
  1700  				parser.TransactionControl{Token: parser.COMMIT},
  1701  			},
  1702  		},
  1703  		Error: "field notexist does not exist",
  1704  	},
  1705  	{
  1706  		Name: "While Statement Return Value",
  1707  		Stmt: parser.While{
  1708  			Condition: parser.Comparison{
  1709  				LHS:      parser.Variable{Name: "while_test"},
  1710  				RHS:      parser.NewIntegerValueFromString("3"),
  1711  				Operator: parser.Token{Token: parser.COMPARISON_OP, Literal: "<"},
  1712  			},
  1713  			Statements: []parser.Statement{
  1714  				parser.Return{Value: parser.NewStringValue("1")},
  1715  				parser.VariableSubstitution{
  1716  					Variable: parser.Variable{Name: "while_test"},
  1717  					Value: parser.Arithmetic{
  1718  						LHS:      parser.Variable{Name: "while_test"},
  1719  						RHS:      parser.NewIntegerValueFromString("1"),
  1720  						Operator: parser.Token{Token: '+', Literal: "+"},
  1721  					},
  1722  				},
  1723  				parser.Print{Value: parser.Variable{Name: "while_test"}},
  1724  				parser.TransactionControl{Token: parser.COMMIT},
  1725  			},
  1726  		},
  1727  		ResultFlow:  Return,
  1728  		ReturnValue: value.NewString("1"),
  1729  	},
  1730  }
  1731  
  1732  func TestProcessor_While(t *testing.T) {
  1733  	defer func() {
  1734  		TestTx.Session.SetStdout(NewDiscard())
  1735  		initFlag(TestTx.Flags)
  1736  	}()
  1737  
  1738  	TestTx.Flags.SetQuiet(true)
  1739  	tx := TestTx
  1740  	proc := NewProcessor(tx)
  1741  
  1742  	for _, v := range processorWhileTests {
  1743  		proc.returnVal = nil
  1744  		if _, ok := proc.ReferenceScope.CurrentBlock().Variables.Get(parser.Variable{Name: "while_test"}); !ok {
  1745  			_ = proc.ReferenceScope.DeclareVariableDirectly(parser.Variable{Name: "while_test"}, value.NewInteger(0))
  1746  		}
  1747  		_ = proc.ReferenceScope.CurrentBlock().Variables.Set(parser.Variable{Name: "while_test"}, value.NewInteger(0))
  1748  
  1749  		if _, ok := proc.ReferenceScope.CurrentBlock().Variables.Get(parser.Variable{Name: "while_test_count"}); !ok {
  1750  			_ = proc.ReferenceScope.DeclareVariableDirectly(parser.Variable{Name: "while_test_count"}, value.NewInteger(0))
  1751  		}
  1752  		_ = proc.ReferenceScope.CurrentBlock().Variables.Set(parser.Variable{Name: "while_test_count"}, value.NewInteger(0))
  1753  
  1754  		out := NewOutput()
  1755  		tx.Session.SetStdout(out)
  1756  		flow, err := proc.While(context.Background(), v.Stmt)
  1757  		log := out.String()
  1758  
  1759  		if err != nil {
  1760  			if len(v.Error) < 1 {
  1761  				t.Errorf("%s: unexpected error %q", v.Name, err)
  1762  			} else if err.Error() != v.Error {
  1763  				t.Errorf("%s: error %q, want error %q", v.Name, err.Error(), v.Error)
  1764  			}
  1765  			continue
  1766  		}
  1767  		if 0 < len(v.Error) {
  1768  			t.Errorf("%s: no error, want error %q", v.Name, v.Error)
  1769  			continue
  1770  		}
  1771  		if flow != v.ResultFlow {
  1772  			t.Errorf("%s: result flow = %q, want %q", v.Name, flow, v.ResultFlow)
  1773  		}
  1774  		if !reflect.DeepEqual(proc.returnVal, v.ReturnValue) {
  1775  			t.Errorf("%s: return = %t, want %t", v.Name, proc.returnVal, v.ReturnValue)
  1776  		}
  1777  		if log != v.Result {
  1778  			t.Errorf("%s: result = %q, want %q", v.Name, log, v.Result)
  1779  		}
  1780  	}
  1781  }
  1782  
  1783  var processorWhileInCursorTests = []struct {
  1784  	Name        string
  1785  	Stmt        parser.WhileInCursor
  1786  	ResultFlow  StatementFlow
  1787  	ReturnValue value.Primary
  1788  	Result      string
  1789  	Error       string
  1790  }{
  1791  	{
  1792  		Name: "While In Cursor",
  1793  		Stmt: parser.WhileInCursor{
  1794  			Variables: []parser.Variable{
  1795  				{Name: "var1"},
  1796  				{Name: "var2"},
  1797  			},
  1798  			Cursor: parser.Identifier{Literal: "cur"},
  1799  			Statements: []parser.Statement{
  1800  				parser.Print{Value: parser.Variable{Name: "var1"}},
  1801  				parser.TransactionControl{Token: parser.COMMIT},
  1802  			},
  1803  		},
  1804  		ResultFlow: Terminate,
  1805  		Result:     "'1'\n'2'\n'3'\n",
  1806  	},
  1807  	{
  1808  		Name: "While In Cursor With Declaration",
  1809  		Stmt: parser.WhileInCursor{
  1810  			WithDeclaration: true,
  1811  			Variables: []parser.Variable{
  1812  				{Name: "declvar1"},
  1813  				{Name: "declvar2"},
  1814  			},
  1815  			Cursor: parser.Identifier{Literal: "cur"},
  1816  			Statements: []parser.Statement{
  1817  				parser.Print{Value: parser.Variable{Name: "declvar1"}},
  1818  				parser.TransactionControl{Token: parser.COMMIT},
  1819  			},
  1820  		},
  1821  		ResultFlow: Terminate,
  1822  		Result:     "'1'\n'2'\n'3'\n",
  1823  	},
  1824  	{
  1825  		Name: "While In Cursor Continue",
  1826  		Stmt: parser.WhileInCursor{
  1827  			Variables: []parser.Variable{
  1828  				{Name: "var1"},
  1829  				{Name: "var2"},
  1830  			},
  1831  			Cursor: parser.Identifier{Literal: "cur"},
  1832  			Statements: []parser.Statement{
  1833  				parser.If{
  1834  					Condition: parser.Comparison{
  1835  						LHS:      parser.Variable{Name: "var1"},
  1836  						RHS:      parser.NewIntegerValueFromString("2"),
  1837  						Operator: parser.Token{Token: '=', Literal: "="},
  1838  					},
  1839  					Statements: []parser.Statement{
  1840  						parser.FlowControl{Token: parser.CONTINUE},
  1841  					},
  1842  				},
  1843  				parser.Print{Value: parser.Variable{Name: "var1"}},
  1844  				parser.TransactionControl{Token: parser.COMMIT},
  1845  			},
  1846  		},
  1847  		ResultFlow: Terminate,
  1848  		Result:     "'1'\n'3'\n",
  1849  	},
  1850  	{
  1851  		Name: "While In Cursor Break",
  1852  		Stmt: parser.WhileInCursor{
  1853  			Variables: []parser.Variable{
  1854  				{Name: "var1"},
  1855  				{Name: "var2"},
  1856  			},
  1857  			Cursor: parser.Identifier{Literal: "cur"},
  1858  			Statements: []parser.Statement{
  1859  				parser.If{
  1860  					Condition: parser.Comparison{
  1861  						LHS:      parser.Variable{Name: "var1"},
  1862  						RHS:      parser.NewIntegerValueFromString("2"),
  1863  						Operator: parser.Token{Token: '=', Literal: "="},
  1864  					},
  1865  					Statements: []parser.Statement{
  1866  						parser.FlowControl{Token: parser.BREAK},
  1867  					},
  1868  				},
  1869  				parser.Print{Value: parser.Variable{Name: "var1"}},
  1870  				parser.TransactionControl{Token: parser.COMMIT},
  1871  			},
  1872  		},
  1873  		ResultFlow: Terminate,
  1874  		Result:     "'1'\n",
  1875  	},
  1876  	{
  1877  		Name: "While In Cursor Exit With Code",
  1878  		Stmt: parser.WhileInCursor{
  1879  			Variables: []parser.Variable{
  1880  				{Name: "var1"},
  1881  				{Name: "var2"},
  1882  			},
  1883  			Cursor: parser.Identifier{Literal: "cur"},
  1884  			Statements: []parser.Statement{
  1885  				parser.If{
  1886  					Condition: parser.Comparison{
  1887  						LHS:      parser.Variable{Name: "var1"},
  1888  						RHS:      parser.NewIntegerValueFromString("2"),
  1889  						Operator: parser.Token{Token: '=', Literal: "="},
  1890  					},
  1891  					Statements: []parser.Statement{
  1892  						parser.Exit{},
  1893  					},
  1894  				},
  1895  				parser.Print{Value: parser.Variable{Name: "var1"}},
  1896  				parser.TransactionControl{Token: parser.COMMIT},
  1897  			},
  1898  		},
  1899  		ResultFlow: Exit,
  1900  		Result:     "'1'\n",
  1901  	},
  1902  	{
  1903  		Name: "While In Cursor Fetch Error",
  1904  		Stmt: parser.WhileInCursor{
  1905  			Variables: []parser.Variable{
  1906  				{Name: "var1"},
  1907  				{Name: "var3"},
  1908  			},
  1909  			Cursor: parser.Identifier{Literal: "cur"},
  1910  			Statements: []parser.Statement{
  1911  				parser.Print{Value: parser.Variable{Name: "var1"}},
  1912  				parser.TransactionControl{Token: parser.COMMIT},
  1913  			},
  1914  		},
  1915  		Error: "variable @var3 is undeclared",
  1916  	},
  1917  	{
  1918  		Name: "While In Cursor Statement Execution Error",
  1919  		Stmt: parser.WhileInCursor{
  1920  			Variables: []parser.Variable{
  1921  				{Name: "var1"},
  1922  				{Name: "var2"},
  1923  			},
  1924  			Cursor: parser.Identifier{Literal: "cur"},
  1925  			Statements: []parser.Statement{
  1926  				parser.If{
  1927  					Condition: parser.Comparison{
  1928  						LHS:      parser.Variable{Name: "var1"},
  1929  						RHS:      parser.FieldReference{Column: parser.Identifier{Literal: "notexist"}},
  1930  						Operator: parser.Token{Token: '=', Literal: "="},
  1931  					},
  1932  					Statements: []parser.Statement{
  1933  						parser.FlowControl{Token: parser.BREAK},
  1934  					},
  1935  				},
  1936  				parser.Print{Value: parser.Variable{Name: "var1"}},
  1937  				parser.TransactionControl{Token: parser.COMMIT},
  1938  			},
  1939  		},
  1940  		Error: "field notexist does not exist",
  1941  	},
  1942  	{
  1943  		Name: "While In Cursor Return Value",
  1944  		Stmt: parser.WhileInCursor{
  1945  			Variables: []parser.Variable{
  1946  				{Name: "var1"},
  1947  				{Name: "var2"},
  1948  			},
  1949  			Cursor: parser.Identifier{Literal: "cur"},
  1950  			Statements: []parser.Statement{
  1951  				parser.Return{Value: parser.NewStringValue("1")},
  1952  				parser.Print{Value: parser.Variable{Name: "var1"}},
  1953  				parser.TransactionControl{Token: parser.COMMIT},
  1954  			},
  1955  		},
  1956  		ResultFlow:  Return,
  1957  		ReturnValue: value.NewString("1"),
  1958  	},
  1959  }
  1960  
  1961  func TestProcessor_WhileInCursor(t *testing.T) {
  1962  	defer func() {
  1963  		_ = TestTx.CachedViews.Clean(TestTx.FileContainer)
  1964  		TestTx.Session.SetStdout(NewDiscard())
  1965  		initFlag(TestTx.Flags)
  1966  	}()
  1967  
  1968  	TestTx.Flags.Repository = TestDir
  1969  
  1970  	tx := TestTx
  1971  	proc := NewProcessor(tx)
  1972  	ctx := context.Background()
  1973  
  1974  	for _, v := range processorWhileInCursorTests {
  1975  		proc.ReferenceScope = GenerateReferenceScope([]map[string]map[string]interface{}{
  1976  			{
  1977  				scopeNameVariables: {
  1978  					"var1": value.NewNull(),
  1979  					"var2": value.NewNull(),
  1980  				},
  1981  				scopeNameCursors: {
  1982  					"CUR": &Cursor{
  1983  						query: selectQueryForCursorTest,
  1984  						mtx:   &sync.Mutex{},
  1985  					},
  1986  				},
  1987  			},
  1988  		}, nil, time.Time{}, nil)
  1989  		_ = TestTx.CachedViews.Clean(TestTx.FileContainer)
  1990  		_ = proc.ReferenceScope.OpenCursor(ctx, parser.Identifier{Literal: "cur"}, nil)
  1991  
  1992  		out := NewOutput()
  1993  		tx.Session.SetStdout(out)
  1994  		flow, err := proc.WhileInCursor(context.Background(), v.Stmt)
  1995  		log := out.String()
  1996  
  1997  		if err != nil {
  1998  			if len(v.Error) < 1 {
  1999  				t.Errorf("%s: unexpected error %q", v.Name, err)
  2000  			} else if err.Error() != v.Error {
  2001  				t.Errorf("%s: error %q, want error %q", v.Name, err.Error(), v.Error)
  2002  			}
  2003  			continue
  2004  		}
  2005  		if 0 < len(v.Error) {
  2006  			t.Errorf("%s: no error, want error %q", v.Name, v.Error)
  2007  			continue
  2008  		}
  2009  		if flow != v.ResultFlow {
  2010  			t.Errorf("%s: result flow = %q, want %q", v.Name, flow, v.ResultFlow)
  2011  		}
  2012  		if !reflect.DeepEqual(proc.returnVal, v.ReturnValue) {
  2013  			t.Errorf("%s: return = %t, want %t", v.Name, proc.returnVal, v.ReturnValue)
  2014  		}
  2015  		if log != v.Result {
  2016  			t.Errorf("%s: result = %q, want %q", v.Name, log, v.Result)
  2017  		}
  2018  	}
  2019  }
  2020  
  2021  var processorExecExternalCommand = []struct {
  2022  	Name  string
  2023  	Stmt  parser.ExternalCommand
  2024  	Error string
  2025  }{
  2026  	{
  2027  		Name: "Error in Splitting Arguments",
  2028  		Stmt: parser.ExternalCommand{
  2029  			Command: "cmd arg 'arg",
  2030  		},
  2031  		Error: "external command: string not terminated",
  2032  	},
  2033  	{
  2034  		Name: "Error in Scanning Argument",
  2035  		Stmt: parser.ExternalCommand{
  2036  			Command: "cmd 'arg arg@'",
  2037  		},
  2038  		Error: "external command: invalid variable symbol",
  2039  	},
  2040  	{
  2041  		Name: "Error in Evaluation of Variable",
  2042  		Stmt: parser.ExternalCommand{
  2043  			Command: "cmd @__not_exist__",
  2044  		},
  2045  		Error: "external command: variable @__not_exist__ is undeclared",
  2046  	},
  2047  }
  2048  
  2049  func TestProcessor_ExecExternalCommand(t *testing.T) {
  2050  	proc := NewProcessor(TestTx)
  2051  
  2052  	for _, v := range processorExecExternalCommand {
  2053  		err := proc.ExecExternalCommand(context.Background(), v.Stmt)
  2054  
  2055  		if err != nil {
  2056  			if len(v.Error) < 1 {
  2057  				t.Errorf("%s: unexpected error %q", v.Name, err)
  2058  			} else if err.Error() != v.Error {
  2059  				t.Errorf("%s: error %q, want error %q", v.Name, err.Error(), v.Error)
  2060  			}
  2061  			continue
  2062  		}
  2063  		if 0 < len(v.Error) {
  2064  			t.Errorf("%s: no error, want error %q", v.Name, v.Error)
  2065  			continue
  2066  		}
  2067  	}
  2068  }