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

     1  package query
     2  
     3  import (
     4  	"context"
     5  	"reflect"
     6  	"sync"
     7  	"testing"
     8  
     9  	"github.com/mithrandie/go-text"
    10  
    11  	"github.com/mithrandie/csvq/lib/parser"
    12  	"github.com/mithrandie/csvq/lib/value"
    13  
    14  	"github.com/mithrandie/ternary"
    15  )
    16  
    17  var selectQueryForCursorTest = parser.SelectQuery{
    18  	SelectEntity: parser.SelectEntity{
    19  		SelectClause: parser.SelectClause{
    20  			Fields: []parser.QueryExpression{
    21  				parser.Field{Object: parser.FieldReference{Column: parser.Identifier{Literal: "column1"}}},
    22  				parser.Field{Object: parser.FieldReference{Column: parser.Identifier{Literal: "column2"}}},
    23  			},
    24  		},
    25  		FromClause: parser.FromClause{
    26  			Tables: []parser.QueryExpression{
    27  				parser.Table{Object: parser.Identifier{Literal: "table1"}},
    28  			},
    29  		},
    30  	},
    31  }
    32  
    33  var selectQueryForCursorQueryErrorTest = parser.SelectQuery{
    34  	SelectEntity: parser.SelectEntity{
    35  		SelectClause: parser.SelectClause{
    36  			Fields: []parser.QueryExpression{
    37  				parser.Field{Object: parser.FieldReference{Column: parser.Identifier{Literal: "notexist"}}},
    38  				parser.Field{Object: parser.FieldReference{Column: parser.Identifier{Literal: "column2"}}},
    39  			},
    40  		},
    41  		FromClause: parser.FromClause{
    42  			Tables: []parser.QueryExpression{
    43  				parser.Table{Object: parser.Identifier{Literal: "table1"}},
    44  			},
    45  		},
    46  	},
    47  }
    48  
    49  var cursorMapDeclareTests = []struct {
    50  	Name   string
    51  	Expr   parser.CursorDeclaration
    52  	Result CursorMap
    53  	Error  string
    54  }{
    55  	{
    56  		Name: "CursorMap Declare",
    57  		Expr: parser.CursorDeclaration{
    58  			Cursor: parser.Identifier{Literal: "cur"},
    59  			Query:  selectQueryForCursorTest,
    60  		},
    61  		Result: GenerateCursorMap([]*Cursor{
    62  			{
    63  				Name:  "cur",
    64  				query: selectQueryForCursorTest,
    65  				mtx:   &sync.Mutex{},
    66  			},
    67  		}),
    68  	},
    69  	{
    70  		Name: "CursorMap Declare for Statement",
    71  		Expr: parser.CursorDeclaration{
    72  			Cursor:    parser.Identifier{Literal: "stmtcur"},
    73  			Statement: parser.Identifier{Literal: "stmt"},
    74  		},
    75  		Result: GenerateCursorMap([]*Cursor{
    76  			{
    77  				Name:  "cur",
    78  				query: selectQueryForCursorTest,
    79  				mtx:   &sync.Mutex{},
    80  			},
    81  			{
    82  				Name:      "stmtcur",
    83  				statement: parser.Identifier{Literal: "stmt"},
    84  				mtx:       &sync.Mutex{},
    85  			},
    86  		}),
    87  	},
    88  	{
    89  		Name: "CursorMap Declare Redeclaration Error",
    90  		Expr: parser.CursorDeclaration{
    91  			Cursor: parser.Identifier{Literal: "cur"},
    92  			Query:  parser.SelectQuery{},
    93  		},
    94  		Error: "cursor cur is redeclared",
    95  	},
    96  }
    97  
    98  func TestCursorMap_Declare(t *testing.T) {
    99  	cursors := NewCursorMap()
   100  
   101  	for _, v := range cursorMapDeclareTests {
   102  		err := cursors.Declare(v.Expr)
   103  		if err != nil {
   104  			if len(v.Error) < 1 {
   105  				t.Errorf("%s: unexpected error %q", v.Name, err)
   106  			} else if err.Error() != v.Error {
   107  				t.Errorf("%s: error %q, want error %q", v.Name, err.Error(), v.Error)
   108  			}
   109  			continue
   110  		}
   111  		if 0 < len(v.Error) {
   112  			t.Errorf("%s: no error, want error %q", v.Name, v.Error)
   113  			continue
   114  		}
   115  		if !SyncMapEqual(cursors, v.Result) {
   116  			t.Errorf("%s: result = %v, want %v", v.Name, cursors, v.Result)
   117  		}
   118  	}
   119  }
   120  
   121  var cursorMapAddPseudoCursorTests = []struct {
   122  	Name   string
   123  	Cursor parser.Identifier
   124  	Values []value.Primary
   125  	Result CursorMap
   126  	Error  string
   127  }{
   128  	{
   129  		Name:   "CursorMap AddPseudoCursor",
   130  		Cursor: parser.Identifier{Literal: "pcur"},
   131  		Values: []value.Primary{
   132  			value.NewInteger(1),
   133  			value.NewInteger(2),
   134  		},
   135  		Result: GenerateCursorMap([]*Cursor{
   136  			{
   137  				Name: "pcur",
   138  				view: &View{
   139  					Header: NewHeader("", []string{"c1"}),
   140  					RecordSet: RecordSet{
   141  						NewRecord([]value.Primary{value.NewInteger(1)}),
   142  						NewRecord([]value.Primary{value.NewInteger(2)}),
   143  					},
   144  				},
   145  				index:    -1,
   146  				isPseudo: true,
   147  				mtx:      &sync.Mutex{},
   148  			},
   149  		}),
   150  	},
   151  	{
   152  		Name:   "CursorMap AddPseudoCursor Redeclaration Error",
   153  		Cursor: parser.Identifier{Literal: "pcur"},
   154  		Values: []value.Primary{},
   155  		Error:  "cursor pcur is redeclared",
   156  	},
   157  }
   158  
   159  func TestCursorMap_AddPseudoCursor(t *testing.T) {
   160  	cursors := NewCursorMap()
   161  	for _, v := range cursorMapAddPseudoCursorTests {
   162  		err := cursors.AddPseudoCursor(v.Cursor, v.Values)
   163  		if err != nil {
   164  			if len(v.Error) < 1 {
   165  				t.Errorf("%s: unexpected error %q", v.Name, err)
   166  			} else if err.Error() != v.Error {
   167  				t.Errorf("%s: error %q, want error %q", v.Name, err.Error(), v.Error)
   168  			}
   169  			continue
   170  		}
   171  		if 0 < len(v.Error) {
   172  			t.Errorf("%s: no error, want error %q", v.Name, v.Error)
   173  			continue
   174  		}
   175  		if !SyncMapEqual(cursors, v.Result) {
   176  			t.Errorf("%s: result = %v, want %v", v.Name, cursors, v.Result)
   177  		}
   178  	}
   179  }
   180  
   181  var cursorMapDisposeTests = []struct {
   182  	Name    string
   183  	CurName parser.Identifier
   184  	Result  CursorMap
   185  	Error   string
   186  }{
   187  	{
   188  		Name:    "CursorMap Dispose",
   189  		CurName: parser.Identifier{Literal: "cur"},
   190  		Result: GenerateCursorMap([]*Cursor{
   191  			{
   192  				Name: "pcur",
   193  				view: &View{
   194  					Header: NewHeader("", []string{"c1"}),
   195  					RecordSet: RecordSet{
   196  						NewRecord([]value.Primary{value.NewInteger(1)}),
   197  						NewRecord([]value.Primary{value.NewInteger(2)}),
   198  					},
   199  				},
   200  				index:    -1,
   201  				isPseudo: true,
   202  				mtx:      &sync.Mutex{},
   203  			},
   204  		}),
   205  	},
   206  	{
   207  		Name:    "CursorMap Dispose Undeclared Error",
   208  		CurName: parser.Identifier{Literal: "notexist"},
   209  		Error:   "undeclared cursor",
   210  	},
   211  	{
   212  		Name:    "CursorMap Dispose Rseudo Cursor Error",
   213  		CurName: parser.Identifier{Literal: "pcur"},
   214  		Error:   "unpermmitted pseudo cursor usage",
   215  	},
   216  }
   217  
   218  func TestCursorMap_Dispose(t *testing.T) {
   219  	cursors := GenerateCursorMap([]*Cursor{
   220  		{
   221  			Name:  "cur",
   222  			query: selectQueryForCursorTest,
   223  			mtx:   &sync.Mutex{},
   224  		},
   225  	})
   226  	_ = cursors.AddPseudoCursor(
   227  		parser.Identifier{Literal: "pcur"},
   228  		[]value.Primary{
   229  			value.NewInteger(1),
   230  			value.NewInteger(2),
   231  		},
   232  	)
   233  
   234  	for _, v := range cursorMapDisposeTests {
   235  		err := cursors.Dispose(v.CurName)
   236  		if err != nil {
   237  			if len(v.Error) < 1 {
   238  				t.Errorf("%s: unexpected error %q", v.Name, err)
   239  			} else if err.Error() != v.Error {
   240  				t.Errorf("%s: error %q, want error %q", v.Name, err.Error(), v.Error)
   241  			}
   242  			continue
   243  		}
   244  		if 0 < len(v.Error) {
   245  			t.Errorf("%s: no error, want error %q", v.Name, v.Error)
   246  			continue
   247  		}
   248  		if !SyncMapEqual(cursors, v.Result) {
   249  			t.Errorf("%s: result = %v, want %v", v.Name, cursors, v.Result)
   250  		}
   251  	}
   252  }
   253  
   254  var cursorMapOpenTests = []struct {
   255  	Name      string
   256  	CurName   parser.Identifier
   257  	CurValues []parser.ReplaceValue
   258  	Result    CursorMap
   259  	Error     string
   260  }{
   261  	{
   262  		Name:    "CursorMap Open",
   263  		CurName: parser.Identifier{Literal: "cur"},
   264  		Result: GenerateCursorMap([]*Cursor{
   265  			{
   266  				Name:  "cur",
   267  				query: selectQueryForCursorTest,
   268  				view: &View{
   269  					Header: NewHeader("table1", []string{"column1", "column2"}),
   270  					RecordSet: []Record{
   271  						NewRecord([]value.Primary{
   272  							value.NewString("1"),
   273  							value.NewString("str1"),
   274  						}),
   275  						NewRecord([]value.Primary{
   276  							value.NewString("2"),
   277  							value.NewString("str2"),
   278  						}),
   279  						NewRecord([]value.Primary{
   280  							value.NewString("3"),
   281  							value.NewString("str3"),
   282  						}),
   283  					},
   284  					FileInfo: &FileInfo{
   285  						Path:      GetTestFilePath("table1.csv"),
   286  						Delimiter: ',',
   287  						NoHeader:  false,
   288  						Encoding:  text.UTF8,
   289  						LineBreak: text.LF,
   290  					},
   291  				},
   292  				index: -1,
   293  				mtx:   &sync.Mutex{},
   294  			},
   295  			{
   296  				Name:  "cur2",
   297  				query: selectQueryForCursorQueryErrorTest,
   298  				mtx:   &sync.Mutex{},
   299  			},
   300  			{
   301  				Name: "pcur",
   302  				view: &View{
   303  					Header: NewHeader("", []string{"c1"}),
   304  					RecordSet: RecordSet{
   305  						NewRecord([]value.Primary{value.NewInteger(1)}),
   306  						NewRecord([]value.Primary{value.NewInteger(2)}),
   307  					},
   308  				},
   309  				index:    -1,
   310  				isPseudo: true,
   311  				mtx:      &sync.Mutex{},
   312  			},
   313  			{
   314  				Name:      "stmt",
   315  				statement: parser.Identifier{Literal: "stmt"},
   316  				mtx:       &sync.Mutex{},
   317  			},
   318  			{
   319  				Name:      "not_exist_stmt",
   320  				statement: parser.Identifier{Literal: "not_exist_stmt"},
   321  				mtx:       &sync.Mutex{},
   322  			},
   323  			{
   324  				Name:      "invalid_stmt",
   325  				statement: parser.Identifier{Literal: "invalid_stmt"},
   326  				mtx:       &sync.Mutex{},
   327  			},
   328  			{
   329  				Name:      "invalid_stmt2",
   330  				statement: parser.Identifier{Literal: "invalid_stmt2"},
   331  				mtx:       &sync.Mutex{},
   332  			},
   333  		}),
   334  	},
   335  	{
   336  		Name:    "CursorMap Open Statement",
   337  		CurName: parser.Identifier{Literal: "stmt"},
   338  		CurValues: []parser.ReplaceValue{
   339  			{Value: parser.NewIntegerValueFromString("2")},
   340  		},
   341  		Result: GenerateCursorMap([]*Cursor{
   342  			{
   343  				Name:      "stmt",
   344  				statement: parser.Identifier{Literal: "stmt"},
   345  				view: &View{
   346  					Header: NewHeader("table1", []string{"column1", "column2"}),
   347  					RecordSet: []Record{
   348  						NewRecord([]value.Primary{
   349  							value.NewString("2"),
   350  							value.NewString("str2"),
   351  						}),
   352  					},
   353  					FileInfo: &FileInfo{
   354  						Path:      GetTestFilePath("table1.csv"),
   355  						Delimiter: ',',
   356  						NoHeader:  false,
   357  						Encoding:  text.UTF8,
   358  						LineBreak: text.LF,
   359  					},
   360  				},
   361  				index: -1,
   362  				mtx:   &sync.Mutex{},
   363  			},
   364  			{
   365  				Name:  "cur",
   366  				query: selectQueryForCursorTest,
   367  				view: &View{
   368  					Header: NewHeader("table1", []string{"column1", "column2"}),
   369  					RecordSet: []Record{
   370  						NewRecord([]value.Primary{
   371  							value.NewString("1"),
   372  							value.NewString("str1"),
   373  						}),
   374  						NewRecord([]value.Primary{
   375  							value.NewString("2"),
   376  							value.NewString("str2"),
   377  						}),
   378  						NewRecord([]value.Primary{
   379  							value.NewString("3"),
   380  							value.NewString("str3"),
   381  						}),
   382  					},
   383  					FileInfo: &FileInfo{
   384  						Path:      GetTestFilePath("table1.csv"),
   385  						Delimiter: ',',
   386  						NoHeader:  false,
   387  						Encoding:  text.UTF8,
   388  						LineBreak: text.LF,
   389  					},
   390  				},
   391  				index: -1,
   392  				mtx:   &sync.Mutex{},
   393  			},
   394  			{
   395  				Name:  "cur2",
   396  				query: selectQueryForCursorQueryErrorTest,
   397  				mtx:   &sync.Mutex{},
   398  			},
   399  			{
   400  				Name: "pcur",
   401  				view: &View{
   402  					Header: NewHeader("", []string{"c1"}),
   403  					RecordSet: RecordSet{
   404  						NewRecord([]value.Primary{value.NewInteger(1)}),
   405  						NewRecord([]value.Primary{value.NewInteger(2)}),
   406  					},
   407  				},
   408  				index:    -1,
   409  				isPseudo: true,
   410  				mtx:      &sync.Mutex{},
   411  			},
   412  			{
   413  				Name:      "not_exist_stmt",
   414  				statement: parser.Identifier{Literal: "not_exist_stmt"},
   415  				mtx:       &sync.Mutex{},
   416  			},
   417  			{
   418  				Name:      "invalid_stmt",
   419  				statement: parser.Identifier{Literal: "invalid_stmt"},
   420  				mtx:       &sync.Mutex{},
   421  			},
   422  			{
   423  				Name:      "invalid_stmt2",
   424  				statement: parser.Identifier{Literal: "invalid_stmt2"},
   425  				mtx:       &sync.Mutex{},
   426  			},
   427  		}),
   428  	},
   429  	{
   430  		Name:    "CursorMap Open Undeclared Error",
   431  		CurName: parser.Identifier{Literal: "notexist"},
   432  		Error:   "undeclared cursor",
   433  	},
   434  	{
   435  		Name:    "CursorMap Open Open Error",
   436  		CurName: parser.Identifier{Literal: "cur"},
   437  		Error:   "cursor cur is already open",
   438  	},
   439  	{
   440  		Name:    "CursorMap Open Query Error",
   441  		CurName: parser.Identifier{Literal: "cur2"},
   442  		Error:   "field notexist does not exist",
   443  	},
   444  	{
   445  		Name:    "CursorMap Open Rseudo Cursor Error",
   446  		CurName: parser.Identifier{Literal: "pcur"},
   447  		Error:   "cursor pcur is a pseudo cursor",
   448  	},
   449  	{
   450  		Name:    "CursorMap Open Unprepared Statement",
   451  		CurName: parser.Identifier{Literal: "not_exist_stmt"},
   452  		Error:   "statement not_exist_stmt does not exist",
   453  	},
   454  	{
   455  		Name:    "CursorMap Open Not Select Query Error",
   456  		CurName: parser.Identifier{Literal: "invalid_stmt"},
   457  		Error:   "invalid cursor statement: invalid_stmt",
   458  	},
   459  	{
   460  		Name:    "CursorMap Open Multiple Statements Error",
   461  		CurName: parser.Identifier{Literal: "invalid_stmt2"},
   462  		Error:   "invalid cursor statement: invalid_stmt2",
   463  	},
   464  }
   465  
   466  func TestCursorMap_Open(t *testing.T) {
   467  	defer func() {
   468  		TestTx.PreparedStatements = NewPreparedStatementMap()
   469  		_ = TestTx.CachedViews.Clean(TestTx.FileContainer)
   470  		initFlag(TestTx.Flags)
   471  	}()
   472  
   473  	scope := NewReferenceScope(TestTx)
   474  	TestTx.Flags.Repository = TestDir
   475  	_ = TestTx.PreparedStatements.Prepare(scope.Tx.Flags, parser.StatementPreparation{
   476  		Name:      parser.Identifier{Literal: "stmt"},
   477  		Statement: value.NewString("select * from table1 where column1 = ?"),
   478  	})
   479  	_ = TestTx.PreparedStatements.Prepare(scope.Tx.Flags, parser.StatementPreparation{
   480  		Name:      parser.Identifier{Literal: "invalid_stmt"},
   481  		Statement: value.NewString("insert into table1 values (?, ?)"),
   482  	})
   483  	_ = TestTx.PreparedStatements.Prepare(scope.Tx.Flags, parser.StatementPreparation{
   484  		Name:      parser.Identifier{Literal: "invalid_stmt2"},
   485  		Statement: value.NewString("select 1; insert into table1 values (?, ?);"),
   486  	})
   487  
   488  	scope.Blocks[0].Cursors = GenerateCursorMap([]*Cursor{
   489  		{
   490  			Name:  "cur",
   491  			query: selectQueryForCursorTest,
   492  			mtx:   &sync.Mutex{},
   493  		},
   494  		{
   495  			Name:  "cur2",
   496  			query: selectQueryForCursorQueryErrorTest,
   497  			mtx:   &sync.Mutex{},
   498  		},
   499  		{
   500  			Name:      "stmt",
   501  			statement: parser.Identifier{Literal: "stmt"},
   502  			mtx:       &sync.Mutex{},
   503  		},
   504  		{
   505  			Name:      "not_exist_stmt",
   506  			statement: parser.Identifier{Literal: "not_exist_stmt"},
   507  			mtx:       &sync.Mutex{},
   508  		},
   509  		{
   510  			Name:      "invalid_stmt",
   511  			statement: parser.Identifier{Literal: "invalid_stmt"},
   512  			mtx:       &sync.Mutex{},
   513  		},
   514  		{
   515  			Name:      "invalid_stmt2",
   516  			statement: parser.Identifier{Literal: "invalid_stmt2"},
   517  			mtx:       &sync.Mutex{},
   518  		},
   519  	})
   520  	_ = scope.Blocks[0].Cursors.AddPseudoCursor(
   521  		parser.Identifier{Literal: "pcur"},
   522  		[]value.Primary{
   523  			value.NewInteger(1),
   524  			value.NewInteger(2),
   525  		},
   526  	)
   527  
   528  	ctx := context.Background()
   529  	for _, v := range cursorMapOpenTests {
   530  		_ = TestTx.CachedViews.Clean(TestTx.FileContainer)
   531  		err := scope.Blocks[0].Cursors.Open(ctx, scope, v.CurName, v.CurValues)
   532  		if err != nil {
   533  			if len(v.Error) < 1 {
   534  				t.Errorf("%s: unexpected error %q", v.Name, err)
   535  			} else if err.Error() != v.Error {
   536  				t.Errorf("%s: error %q, want error %q", v.Name, err.Error(), v.Error)
   537  			}
   538  			continue
   539  		}
   540  		if 0 < len(v.Error) {
   541  			t.Errorf("%s: no error, want error %q", v.Name, v.Error)
   542  			continue
   543  		}
   544  		if !SyncMapEqual(scope.Blocks[0].Cursors, v.Result) {
   545  			t.Errorf("%s: result = %v, want %v", v.Name, scope.Blocks[0].Cursors, v.Result)
   546  		}
   547  	}
   548  }
   549  
   550  var cursorMapCloseTests = []struct {
   551  	Name    string
   552  	CurName parser.Identifier
   553  	Result  CursorMap
   554  	Error   string
   555  }{
   556  	{
   557  		Name:    "CursorMap Close",
   558  		CurName: parser.Identifier{Literal: "cur"},
   559  		Result: GenerateCursorMap([]*Cursor{
   560  			{
   561  				Name:  "cur",
   562  				query: selectQueryForCursorTest,
   563  				mtx:   &sync.Mutex{},
   564  			},
   565  			{
   566  				Name: "pcur",
   567  				view: &View{
   568  					Header: NewHeader("", []string{"c1"}),
   569  					RecordSet: RecordSet{
   570  						NewRecord([]value.Primary{value.NewInteger(1)}),
   571  						NewRecord([]value.Primary{value.NewInteger(2)}),
   572  					},
   573  				},
   574  				index:    -1,
   575  				isPseudo: true,
   576  				mtx:      &sync.Mutex{},
   577  			},
   578  		}),
   579  	},
   580  	{
   581  		Name:    "CursorMap Close Rseudo Cursor Error",
   582  		CurName: parser.Identifier{Literal: "pcur"},
   583  		Error:   "cursor pcur is a pseudo cursor",
   584  	},
   585  	{
   586  		Name:    "CursorMap Close Undeclared Error",
   587  		CurName: parser.Identifier{Literal: "notexist"},
   588  		Error:   "undeclared cursor",
   589  	},
   590  }
   591  
   592  func TestCursorMap_Close(t *testing.T) {
   593  	defer func() {
   594  		_ = TestTx.CachedViews.Clean(TestTx.FileContainer)
   595  		initFlag(TestTx.Flags)
   596  	}()
   597  
   598  	TestTx.Flags.Repository = TestDir
   599  
   600  	cursors := GenerateCursorMap([]*Cursor{
   601  		{
   602  			Name:  "cur",
   603  			query: selectQueryForCursorTest,
   604  			mtx:   &sync.Mutex{},
   605  		},
   606  	})
   607  	_ = cursors.AddPseudoCursor(
   608  		parser.Identifier{Literal: "pcur"},
   609  		[]value.Primary{
   610  			value.NewInteger(1),
   611  			value.NewInteger(2),
   612  		},
   613  	)
   614  	scope := NewReferenceScope(TestTx)
   615  	ctx := context.Background()
   616  	_ = cursors.Open(ctx, scope, parser.Identifier{Literal: "cur"}, nil)
   617  
   618  	for _, v := range cursorMapCloseTests {
   619  		err := cursors.Close(v.CurName)
   620  		if err != nil {
   621  			if len(v.Error) < 1 {
   622  				t.Errorf("%s: unexpected error %q", v.Name, err)
   623  			} else if err.Error() != v.Error {
   624  				t.Errorf("%s: error %q, want error %q", v.Name, err.Error(), v.Error)
   625  			}
   626  			continue
   627  		}
   628  		if 0 < len(v.Error) {
   629  			t.Errorf("%s: no error, want error %q", v.Name, v.Error)
   630  			continue
   631  		}
   632  		if !SyncMapEqual(cursors, v.Result) {
   633  			t.Errorf("%s: result = %v, want %v", v.Name, cursors, v.Result)
   634  		}
   635  	}
   636  }
   637  
   638  var cursorMapFetchTests = []struct {
   639  	Name     string
   640  	CurName  parser.Identifier
   641  	Position int
   642  	Number   int
   643  	Result   []value.Primary
   644  	Error    string
   645  }{
   646  	{
   647  		Name:     "CursorMap Fetch First Time",
   648  		CurName:  parser.Identifier{Literal: "cur"},
   649  		Position: parser.NEXT,
   650  		Result: []value.Primary{
   651  			value.NewString("1"),
   652  			value.NewString("str1"),
   653  		},
   654  	},
   655  	{
   656  		Name:     "CursorMap Fetch Second Time",
   657  		CurName:  parser.Identifier{Literal: "cur"},
   658  		Position: parser.NEXT,
   659  		Result: []value.Primary{
   660  			value.NewString("2"),
   661  			value.NewString("str2"),
   662  		},
   663  	},
   664  	{
   665  		Name:     "CursorMap Fetch Third Time",
   666  		CurName:  parser.Identifier{Literal: "cur"},
   667  		Position: parser.NEXT,
   668  		Result: []value.Primary{
   669  			value.NewString("3"),
   670  			value.NewString("str3"),
   671  		},
   672  	},
   673  	{
   674  		Name:     "CursorMap Fetch Fourth Time",
   675  		CurName:  parser.Identifier{Literal: "cur"},
   676  		Position: parser.NEXT,
   677  		Result:   nil,
   678  	},
   679  	{
   680  		Name:     "CursorMap Fetch First",
   681  		CurName:  parser.Identifier{Literal: "cur"},
   682  		Position: parser.FIRST,
   683  		Result: []value.Primary{
   684  			value.NewString("1"),
   685  			value.NewString("str1"),
   686  		},
   687  	},
   688  	{
   689  		Name:     "CursorMap Fetch Last",
   690  		CurName:  parser.Identifier{Literal: "cur"},
   691  		Position: parser.LAST,
   692  		Result: []value.Primary{
   693  			value.NewString("3"),
   694  			value.NewString("str3"),
   695  		},
   696  	},
   697  	{
   698  		Name:     "CursorMap Fetch Prior",
   699  		CurName:  parser.Identifier{Literal: "cur"},
   700  		Position: parser.PRIOR,
   701  		Result: []value.Primary{
   702  			value.NewString("2"),
   703  			value.NewString("str2"),
   704  		},
   705  	},
   706  	{
   707  		Name:     "CursorMap Fetch Absolute",
   708  		CurName:  parser.Identifier{Literal: "cur"},
   709  		Position: parser.ABSOLUTE,
   710  		Number:   1,
   711  		Result: []value.Primary{
   712  			value.NewString("2"),
   713  			value.NewString("str2"),
   714  		},
   715  	},
   716  	{
   717  		Name:     "CursorMap Fetch Relative",
   718  		CurName:  parser.Identifier{Literal: "cur"},
   719  		Position: parser.RELATIVE,
   720  		Number:   -1,
   721  		Result: []value.Primary{
   722  			value.NewString("1"),
   723  			value.NewString("str1"),
   724  		},
   725  	},
   726  	{
   727  		Name:     "CursorMap Fetch Prior to Last",
   728  		CurName:  parser.Identifier{Literal: "cur"},
   729  		Position: parser.ABSOLUTE,
   730  		Number:   -2,
   731  		Result:   nil,
   732  	},
   733  	{
   734  		Name:     "CursorMap Fetch Later than Last",
   735  		CurName:  parser.Identifier{Literal: "cur"},
   736  		Position: parser.ABSOLUTE,
   737  		Number:   100,
   738  		Result:   nil,
   739  	},
   740  	{
   741  		Name:     "CursorMap Fetch Undeclared Error",
   742  		CurName:  parser.Identifier{Literal: "notexist"},
   743  		Position: parser.NEXT,
   744  		Error:    "undeclared cursor",
   745  	},
   746  	{
   747  		Name:     "CursorMap Fetch Closed Error",
   748  		CurName:  parser.Identifier{Literal: "cur2"},
   749  		Position: parser.NEXT,
   750  		Error:    "cursor cur2 is closed",
   751  	},
   752  }
   753  
   754  func TestCursorMap_Fetch(t *testing.T) {
   755  	defer func() {
   756  		_ = TestTx.CachedViews.Clean(TestTx.FileContainer)
   757  		initFlag(TestTx.Flags)
   758  	}()
   759  
   760  	TestTx.Flags.Repository = TestDir
   761  
   762  	cursors := GenerateCursorMap([]*Cursor{
   763  		{
   764  			Name:  "cur",
   765  			query: selectQueryForCursorTest,
   766  			mtx:   &sync.Mutex{},
   767  		},
   768  		{
   769  			Name:  "cur2",
   770  			query: selectQueryForCursorTest,
   771  			mtx:   &sync.Mutex{},
   772  		},
   773  	})
   774  	scope := NewReferenceScope(TestTx)
   775  	ctx := context.Background()
   776  	_ = cursors.Open(ctx, scope, parser.Identifier{Literal: "cur"}, nil)
   777  
   778  	for _, v := range cursorMapFetchTests {
   779  		result, err := cursors.Fetch(v.CurName, v.Position, v.Number)
   780  		if err != nil {
   781  			if len(v.Error) < 1 {
   782  				t.Errorf("%s: unexpected error %q", v.Name, err)
   783  			} else if err.Error() != v.Error {
   784  				t.Errorf("%s: error %q, want error %q", v.Name, err.Error(), v.Error)
   785  			}
   786  			continue
   787  		}
   788  		if 0 < len(v.Error) {
   789  			t.Errorf("%s: no error, want error %q", v.Name, v.Error)
   790  			continue
   791  		}
   792  		if !reflect.DeepEqual(result, v.Result) {
   793  			t.Errorf("%s: result = %s, want %s", v.Name, result, v.Result)
   794  		}
   795  	}
   796  }
   797  
   798  var cursorMapIsOpenTests = []struct {
   799  	Name    string
   800  	CurName parser.Identifier
   801  	Result  ternary.Value
   802  	Error   string
   803  }{
   804  	{
   805  		Name:    "CursorMap IsOpen TRUE",
   806  		CurName: parser.Identifier{Literal: "cur"},
   807  		Result:  ternary.TRUE,
   808  	},
   809  	{
   810  		Name:    "CursorMap IsOpen FALSE",
   811  		CurName: parser.Identifier{Literal: "cur2"},
   812  		Result:  ternary.FALSE,
   813  	},
   814  	{
   815  		Name:    "CursorMap IsOpen Undeclared Error",
   816  		CurName: parser.Identifier{Literal: "notexist"},
   817  		Error:   "undeclared cursor",
   818  	},
   819  }
   820  
   821  func TestCursorMap_IsOpen(t *testing.T) {
   822  	defer func() {
   823  		_ = TestTx.CachedViews.Clean(TestTx.FileContainer)
   824  		initFlag(TestTx.Flags)
   825  	}()
   826  
   827  	TestTx.Flags.Repository = TestDir
   828  
   829  	cursors := GenerateCursorMap([]*Cursor{
   830  		{
   831  			Name:  "cur",
   832  			query: selectQueryForCursorTest,
   833  			mtx:   &sync.Mutex{},
   834  		},
   835  		{
   836  			Name:  "cur2",
   837  			query: selectQueryForCursorTest,
   838  			mtx:   &sync.Mutex{},
   839  		},
   840  	})
   841  	scope := NewReferenceScope(TestTx)
   842  	ctx := context.Background()
   843  	_ = cursors.Open(ctx, scope, parser.Identifier{Literal: "cur"}, nil)
   844  
   845  	for _, v := range cursorMapIsOpenTests {
   846  		result, err := cursors.IsOpen(v.CurName)
   847  		if err != nil {
   848  			if len(v.Error) < 1 {
   849  				t.Errorf("%s: unexpected error %q", v.Name, err)
   850  			} else if err.Error() != v.Error {
   851  				t.Errorf("%s: error %q, want error %q", v.Name, err.Error(), v.Error)
   852  			}
   853  			continue
   854  		}
   855  		if 0 < len(v.Error) {
   856  			t.Errorf("%s: no error, want error %q", v.Name, v.Error)
   857  			continue
   858  		}
   859  		if result != v.Result {
   860  			t.Errorf("%s: result = %s, want %s", v.Name, result, v.Result)
   861  		}
   862  	}
   863  }
   864  
   865  var cursorMapIsInRangeTests = []struct {
   866  	Name    string
   867  	CurName parser.Identifier
   868  	Index   int
   869  	Result  ternary.Value
   870  	Error   string
   871  }{
   872  	{
   873  		Name:    "CursorMap Is In Range UNKNOWN",
   874  		CurName: parser.Identifier{Literal: "cur"},
   875  		Result:  ternary.UNKNOWN,
   876  	},
   877  	{
   878  		Name:    "CursorMap Is In Range TRUE",
   879  		CurName: parser.Identifier{Literal: "cur2"},
   880  		Index:   1,
   881  		Result:  ternary.TRUE,
   882  	},
   883  	{
   884  		Name:    "CursorMap Is In Range FALSE",
   885  		CurName: parser.Identifier{Literal: "cur2"},
   886  		Index:   -1,
   887  		Result:  ternary.FALSE,
   888  	},
   889  	{
   890  		Name:    "CursorMap Is In Range Not Open Error",
   891  		CurName: parser.Identifier{Literal: "cur3"},
   892  		Error:   "cursor cur3 is closed",
   893  	},
   894  	{
   895  		Name:    "CursorMap Is In Range Undeclared Error",
   896  		CurName: parser.Identifier{Literal: "notexist"},
   897  		Error:   "undeclared cursor",
   898  	},
   899  }
   900  
   901  func TestCursorMap_IsInRange(t *testing.T) {
   902  	defer func() {
   903  		_ = TestTx.CachedViews.Clean(TestTx.FileContainer)
   904  		initFlag(TestTx.Flags)
   905  	}()
   906  
   907  	TestTx.Flags.Repository = TestDir
   908  
   909  	cursors := GenerateCursorMap([]*Cursor{
   910  		{
   911  			Name:  "cur",
   912  			query: selectQueryForCursorTest,
   913  			mtx:   &sync.Mutex{},
   914  		},
   915  		{
   916  			Name:  "cur2",
   917  			query: selectQueryForCursorTest,
   918  			mtx:   &sync.Mutex{},
   919  		},
   920  		{
   921  			Name:  "cur3",
   922  			query: selectQueryForCursorTest,
   923  			mtx:   &sync.Mutex{},
   924  		},
   925  	})
   926  	scope := NewReferenceScope(TestTx)
   927  	ctx := context.Background()
   928  	_ = TestTx.CachedViews.Clean(TestTx.FileContainer)
   929  	_ = cursors.Open(ctx, scope, parser.Identifier{Literal: "cur"}, nil)
   930  	_ = TestTx.CachedViews.Clean(TestTx.FileContainer)
   931  	_ = cursors.Open(ctx, scope, parser.Identifier{Literal: "cur2"}, nil)
   932  	_, _ = cursors.Fetch(parser.Identifier{Literal: "cur2"}, parser.NEXT, 0)
   933  
   934  	for _, v := range cursorMapIsInRangeTests {
   935  		if 0 != v.Index {
   936  			c, _ := cursors.Load("CUR2")
   937  			c.index = v.Index
   938  		}
   939  		result, err := cursors.IsInRange(v.CurName)
   940  		if err != nil {
   941  			if len(v.Error) < 1 {
   942  				t.Errorf("%s: unexpected error %q", v.Name, err)
   943  			} else if err.Error() != v.Error {
   944  				t.Errorf("%s: error %q, want error %q", v.Name, err.Error(), v.Error)
   945  			}
   946  			continue
   947  		}
   948  		if 0 < len(v.Error) {
   949  			t.Errorf("%s: no error, want error %q", v.Name, v.Error)
   950  			continue
   951  		}
   952  		if result != v.Result {
   953  			t.Errorf("%s: result = %s, want %s", v.Name, result, v.Result)
   954  		}
   955  	}
   956  }
   957  
   958  var cursorMapCountTests = []struct {
   959  	Name    string
   960  	CurName parser.Identifier
   961  	Result  int
   962  	Error   string
   963  }{
   964  	{
   965  		Name:    "CursorMap Count",
   966  		CurName: parser.Identifier{Literal: "cur"},
   967  		Result:  3,
   968  	},
   969  	{
   970  		Name:    "CursorMap Count Not Open Error",
   971  		CurName: parser.Identifier{Literal: "cur2"},
   972  		Error:   "cursor cur2 is closed",
   973  	},
   974  	{
   975  		Name:    "CursorMap Count Undeclared Error",
   976  		CurName: parser.Identifier{Literal: "notexist"},
   977  		Error:   "undeclared cursor",
   978  	},
   979  }
   980  
   981  func TestCursorMap_Count(t *testing.T) {
   982  	defer func() {
   983  		_ = TestTx.CachedViews.Clean(TestTx.FileContainer)
   984  		initFlag(TestTx.Flags)
   985  	}()
   986  
   987  	TestTx.Flags.Repository = TestDir
   988  
   989  	cursors := GenerateCursorMap([]*Cursor{
   990  		{
   991  			Name:  "cur",
   992  			query: selectQueryForCursorTest,
   993  			mtx:   &sync.Mutex{},
   994  		},
   995  		{
   996  			Name:  "cur2",
   997  			query: selectQueryForCursorTest,
   998  			mtx:   &sync.Mutex{},
   999  		},
  1000  	})
  1001  	_ = TestTx.CachedViews.Clean(TestTx.FileContainer)
  1002  	scope := NewReferenceScope(TestTx)
  1003  	ctx := context.Background()
  1004  	_ = cursors.Open(ctx, scope, parser.Identifier{Literal: "cur"}, nil)
  1005  
  1006  	for _, v := range cursorMapCountTests {
  1007  		result, err := cursors.Count(v.CurName)
  1008  		if err != nil {
  1009  			if len(v.Error) < 1 {
  1010  				t.Errorf("%s: unexpected error %q", v.Name, err)
  1011  			} else if err.Error() != v.Error {
  1012  				t.Errorf("%s: error %q, want error %q", v.Name, err.Error(), v.Error)
  1013  			}
  1014  			continue
  1015  		}
  1016  		if 0 < len(v.Error) {
  1017  			t.Errorf("%s: no error, want error %q", v.Name, v.Error)
  1018  			continue
  1019  		}
  1020  		if result != v.Result {
  1021  			t.Errorf("%s: result = %v, want %v", v.Name, result, v.Result)
  1022  		}
  1023  	}
  1024  }