| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | package core_test | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"encoding/json" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	"slices" | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 	"testing" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-19 22:18:33 +08:00
										 |  |  | 	"github.com/pocketbase/dbx" | 
					
						
							|  |  |  | 	"github.com/pocketbase/pocketbase/core" | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 	"github.com/pocketbase/pocketbase/tests" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-19 22:18:33 +08:00
										 |  |  | func ensureNoTempViews(app core.App, t *testing.T) { | 
					
						
							|  |  |  | 	var total int | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	err := app.DB().Select("count(*)"). | 
					
						
							| 
									
										
										
										
											2023-03-19 22:18:33 +08:00
										 |  |  | 		From("sqlite_schema"). | 
					
						
							|  |  |  | 		AndWhere(dbx.HashExp{"type": "view"}). | 
					
						
							|  |  |  | 		AndWhere(dbx.NewExp(`[[name]] LIKE '%\_temp\_%' ESCAPE '\'`)). | 
					
						
							|  |  |  | 		Limit(1). | 
					
						
							|  |  |  | 		Row(&total) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatalf("Failed to check for temp views: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if total > 0 { | 
					
						
							|  |  |  | 		t.Fatalf("Expected all temp views to be deleted, got %d", total) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | func TestDeleteView(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-01-03 10:30:20 +08:00
										 |  |  | 	t.Parallel() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 	app, _ := tests.NewTestApp() | 
					
						
							|  |  |  | 	defer app.Cleanup() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	scenarios := []struct { | 
					
						
							|  |  |  | 		viewName    string | 
					
						
							|  |  |  | 		expectError bool | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{"", true}, | 
					
						
							|  |  |  | 		{"demo1", true},    // not a view table
 | 
					
						
							|  |  |  | 		{"missing", false}, // missing or already deleted
 | 
					
						
							|  |  |  | 		{"view1", false},   // existing
 | 
					
						
							|  |  |  | 		{"VieW1", false},   // view names are case insensitives
 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i, s := range scenarios { | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 		err := app.DeleteView(s.viewName) | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		hasErr := err != nil | 
					
						
							|  |  |  | 		if hasErr != s.expectError { | 
					
						
							|  |  |  | 			t.Errorf("[%d - %q] Expected hasErr %v, got %v (%v)", i, s.viewName, s.expectError, hasErr, err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-03-19 22:18:33 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	ensureNoTempViews(app, t) | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestSaveView(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-01-03 10:30:20 +08:00
										 |  |  | 	t.Parallel() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 	app, _ := tests.NewTestApp() | 
					
						
							|  |  |  | 	defer app.Cleanup() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	scenarios := []struct { | 
					
						
							|  |  |  | 		scenarioName  string | 
					
						
							|  |  |  | 		viewName      string | 
					
						
							|  |  |  | 		query         string | 
					
						
							|  |  |  | 		expectError   bool | 
					
						
							|  |  |  | 		expectColumns []string | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"empty name and query", | 
					
						
							|  |  |  | 			"", | 
					
						
							|  |  |  | 			"", | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 			nil, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"empty name", | 
					
						
							|  |  |  | 			"", | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			"select * from " + core.CollectionNameSuperusers, | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			true, | 
					
						
							|  |  |  | 			nil, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"empty query", | 
					
						
							|  |  |  | 			"123Test", | 
					
						
							|  |  |  | 			"", | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 			nil, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"invalid query", | 
					
						
							|  |  |  | 			"123Test", | 
					
						
							|  |  |  | 			"123 456", | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 			nil, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"missing table", | 
					
						
							|  |  |  | 			"123Test", | 
					
						
							| 
									
										
										
										
											2023-03-19 22:18:33 +08:00
										 |  |  | 			"select id from missing", | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			true, | 
					
						
							|  |  |  | 			nil, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"non select query", | 
					
						
							|  |  |  | 			"123Test", | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			"drop table " + core.CollectionNameSuperusers, | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			true, | 
					
						
							|  |  |  | 			nil, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"multiple select queries", | 
					
						
							|  |  |  | 			"123Test", | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			"select *, count(id) as c  from " + core.CollectionNameSuperusers + "; select * from demo1;", | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			true, | 
					
						
							|  |  |  | 			nil, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"try to break the parent parenthesis", | 
					
						
							|  |  |  | 			"123Test", | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			"select *, count(id) as c  from `" + core.CollectionNameSuperusers + "`)", | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			true, | 
					
						
							|  |  |  | 			nil, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"simple select query (+ trimmed semicolon)", | 
					
						
							|  |  |  | 			"123Test", | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			";select *, count(id) as c  from " + core.CollectionNameSuperusers + ";", | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			false, | 
					
						
							|  |  |  | 			[]string{ | 
					
						
							|  |  |  | 				"id", "created", "updated", | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 				"password", "tokenKey", "email", | 
					
						
							|  |  |  | 				"emailVisibility", "verified", | 
					
						
							|  |  |  | 				"c", | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"update old view with new query", | 
					
						
							|  |  |  | 			"123Test", | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			"select 1 as test from " + core.CollectionNameSuperusers, | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			false, | 
					
						
							|  |  |  | 			[]string{"test"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, s := range scenarios { | 
					
						
							| 
									
										
										
										
											2023-10-05 14:31:24 +08:00
										 |  |  | 		t.Run(s.scenarioName, func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			err := app.SaveView(s.viewName, s.query) | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-05 14:31:24 +08:00
										 |  |  | 			hasErr := err != nil | 
					
						
							|  |  |  | 			if hasErr != s.expectError { | 
					
						
							|  |  |  | 				t.Fatalf("Expected hasErr %v, got %v (%v)", s.expectError, hasErr, err) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-05 14:31:24 +08:00
										 |  |  | 			if hasErr { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			infoRows, err := app.TableInfo(s.viewName) | 
					
						
							| 
									
										
										
										
											2023-10-05 14:31:24 +08:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				t.Fatalf("Failed to fetch table info for %s: %v", s.viewName, err) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-05 14:31:24 +08:00
										 |  |  | 			if len(s.expectColumns) != len(infoRows) { | 
					
						
							|  |  |  | 				t.Fatalf("Expected %d columns, got %d", len(s.expectColumns), len(infoRows)) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-05 14:31:24 +08:00
										 |  |  | 			for _, row := range infoRows { | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 				if !slices.Contains(s.expectColumns, row.Name) { | 
					
						
							| 
									
										
										
										
											2023-10-05 14:31:24 +08:00
										 |  |  | 					t.Fatalf("Missing %q column in %v", row.Name, s.expectColumns) | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-10-05 14:31:24 +08:00
										 |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-03-19 22:18:33 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	ensureNoTempViews(app, t) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | func TestCreateViewFieldsWithDiscardedNestedTransaction(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-01-03 10:30:20 +08:00
										 |  |  | 	t.Parallel() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-19 22:18:33 +08:00
										 |  |  | 	app, _ := tests.NewTestApp() | 
					
						
							|  |  |  | 	defer app.Cleanup() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	app.RunInTransaction(func(txApp core.App) error { | 
					
						
							|  |  |  | 		_, err := txApp.CreateViewFields("select id from missing") | 
					
						
							| 
									
										
										
										
											2023-03-19 22:18:33 +08:00
										 |  |  | 		if err == nil { | 
					
						
							|  |  |  | 			t.Fatal("Expected error, got nil") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ensureNoTempViews(app, t) | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | func TestCreateViewFields(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-01-03 10:30:20 +08:00
										 |  |  | 	t.Parallel() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 	app, _ := tests.NewTestApp() | 
					
						
							|  |  |  | 	defer app.Cleanup() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	scenarios := []struct { | 
					
						
							|  |  |  | 		name         string | 
					
						
							|  |  |  | 		query        string | 
					
						
							|  |  |  | 		expectError  bool | 
					
						
							|  |  |  | 		expectFields map[string]string // name-type pairs
 | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"empty query", | 
					
						
							|  |  |  | 			"", | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 			nil, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"invalid query", | 
					
						
							|  |  |  | 			"test 123456", | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 			nil, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"missing table", | 
					
						
							| 
									
										
										
										
											2023-03-19 22:18:33 +08:00
										 |  |  | 			"select id from missing", | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			true, | 
					
						
							|  |  |  | 			nil, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"query with wildcard column", | 
					
						
							|  |  |  | 			"select a.id, a.* from demo1 a", | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 			nil, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"query without id", | 
					
						
							|  |  |  | 			"select text, url, created, updated from demo1", | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 			nil, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"query with comments", | 
					
						
							|  |  |  | 			` | 
					
						
							|  |  |  | 				select | 
					
						
							|  |  |  | 				-- test single line | 
					
						
							|  |  |  | 				id, | 
					
						
							|  |  |  | 				text, | 
					
						
							|  |  |  | 				/* multi | 
					
						
							|  |  |  | 					line comment */ | 
					
						
							|  |  |  | 				url, created, updated from demo1 | 
					
						
							|  |  |  | 			`, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 			map[string]string{ | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 				"id":      core.FieldTypeText, | 
					
						
							|  |  |  | 				"text":    core.FieldTypeText, | 
					
						
							|  |  |  | 				"url":     core.FieldTypeURL, | 
					
						
							|  |  |  | 				"created": core.FieldTypeAutodate, | 
					
						
							|  |  |  | 				"updated": core.FieldTypeAutodate, | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"query with all fields and quoted identifiers", | 
					
						
							|  |  |  | 			` | 
					
						
							|  |  |  | 				select | 
					
						
							|  |  |  | 					"id", | 
					
						
							|  |  |  | 					"created", | 
					
						
							|  |  |  | 					"updated", | 
					
						
							|  |  |  | 					[text], | 
					
						
							|  |  |  | 					` + "`bool`" + `, | 
					
						
							|  |  |  | 					"url", | 
					
						
							|  |  |  | 					"select_one", | 
					
						
							|  |  |  | 					"select_many", | 
					
						
							|  |  |  | 					"file_one", | 
					
						
							|  |  |  | 					"demo1"."file_many", | 
					
						
							|  |  |  | 					` + "`demo1`." + "`number`" + ` number_alias, | 
					
						
							|  |  |  | 					"email", | 
					
						
							|  |  |  | 					"datetime", | 
					
						
							|  |  |  | 					"json", | 
					
						
							|  |  |  | 					"rel_one", | 
					
						
							| 
									
										
										
										
											2023-10-05 14:31:24 +08:00
										 |  |  | 					"rel_many", | 
					
						
							|  |  |  | 					'single_quoted_custom_literal' as 'single_quoted_column' | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 				from demo1 | 
					
						
							|  |  |  | 			`, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 			map[string]string{ | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 				"id":                   core.FieldTypeText, | 
					
						
							|  |  |  | 				"created":              core.FieldTypeAutodate, | 
					
						
							|  |  |  | 				"updated":              core.FieldTypeAutodate, | 
					
						
							|  |  |  | 				"text":                 core.FieldTypeText, | 
					
						
							|  |  |  | 				"bool":                 core.FieldTypeBool, | 
					
						
							|  |  |  | 				"url":                  core.FieldTypeURL, | 
					
						
							|  |  |  | 				"select_one":           core.FieldTypeSelect, | 
					
						
							|  |  |  | 				"select_many":          core.FieldTypeSelect, | 
					
						
							|  |  |  | 				"file_one":             core.FieldTypeFile, | 
					
						
							|  |  |  | 				"file_many":            core.FieldTypeFile, | 
					
						
							|  |  |  | 				"number_alias":         core.FieldTypeNumber, | 
					
						
							|  |  |  | 				"email":                core.FieldTypeEmail, | 
					
						
							|  |  |  | 				"datetime":             core.FieldTypeDate, | 
					
						
							|  |  |  | 				"json":                 core.FieldTypeJSON, | 
					
						
							|  |  |  | 				"rel_one":              core.FieldTypeRelation, | 
					
						
							|  |  |  | 				"rel_many":             core.FieldTypeRelation, | 
					
						
							|  |  |  | 				"single_quoted_column": core.FieldTypeJSON, | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"query with indirect relations fields", | 
					
						
							|  |  |  | 			"select a.id, b.id as bid, b.created from demo1 as a left join demo2 b", | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 			map[string]string{ | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 				"id":      core.FieldTypeText, | 
					
						
							|  |  |  | 				"bid":     core.FieldTypeRelation, | 
					
						
							|  |  |  | 				"created": core.FieldTypeAutodate, | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"query with multiple froms, joins and style of aliasses", | 
					
						
							|  |  |  | 			` | 
					
						
							|  |  |  | 				select | 
					
						
							|  |  |  | 					a.id as id, | 
					
						
							|  |  |  | 					b.id as bid, | 
					
						
							|  |  |  | 					lj.id cid, | 
					
						
							|  |  |  | 					ij.id as did, | 
					
						
							|  |  |  | 					a.bool, | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 					` + core.CollectionNameSuperusers + `.id as eid, | 
					
						
							|  |  |  | 					` + core.CollectionNameSuperusers + `.email | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 				from demo1 a, demo2 as b | 
					
						
							|  |  |  | 				left join demo3 lj on lj.id = 123 | 
					
						
							|  |  |  | 				inner join demo4 as ij on ij.id = 123 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 				join ` + core.CollectionNameSuperusers + ` | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 				where 1=1 | 
					
						
							|  |  |  | 				group by a.id | 
					
						
							|  |  |  | 				limit 10 | 
					
						
							|  |  |  | 			`, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 			map[string]string{ | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 				"id":    core.FieldTypeText, | 
					
						
							|  |  |  | 				"bid":   core.FieldTypeRelation, | 
					
						
							|  |  |  | 				"cid":   core.FieldTypeRelation, | 
					
						
							|  |  |  | 				"did":   core.FieldTypeRelation, | 
					
						
							|  |  |  | 				"bool":  core.FieldTypeBool, | 
					
						
							|  |  |  | 				"eid":   core.FieldTypeRelation, | 
					
						
							|  |  |  | 				"email": core.FieldTypeEmail, | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							| 
									
										
										
										
											2023-08-11 19:29:18 +08:00
										 |  |  | 			"query with casts", | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			`select | 
					
						
							|  |  |  | 				a.id, | 
					
						
							|  |  |  | 				count(a.id) count, | 
					
						
							|  |  |  | 				cast(a.id as int) cast_int, | 
					
						
							|  |  |  | 				cast(a.id as integer) cast_integer, | 
					
						
							|  |  |  | 				cast(a.id as real) cast_real, | 
					
						
							|  |  |  | 				cast(a.id as decimal) cast_decimal, | 
					
						
							|  |  |  | 				cast(a.id as numeric) cast_numeric, | 
					
						
							| 
									
										
										
										
											2023-08-11 19:29:18 +08:00
										 |  |  | 				cast(a.id as text) cast_text, | 
					
						
							|  |  |  | 				cast(a.id as bool) cast_bool, | 
					
						
							|  |  |  | 				cast(a.id as boolean) cast_boolean, | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 				avg(a.id) avg, | 
					
						
							|  |  |  | 				sum(a.id) sum, | 
					
						
							|  |  |  | 				total(a.id) total, | 
					
						
							|  |  |  | 				min(a.id) min, | 
					
						
							|  |  |  | 				max(a.id) max | 
					
						
							|  |  |  | 			from demo1 a`, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 			map[string]string{ | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 				"id":           core.FieldTypeText, | 
					
						
							|  |  |  | 				"count":        core.FieldTypeNumber, | 
					
						
							|  |  |  | 				"total":        core.FieldTypeNumber, | 
					
						
							|  |  |  | 				"cast_int":     core.FieldTypeNumber, | 
					
						
							|  |  |  | 				"cast_integer": core.FieldTypeNumber, | 
					
						
							|  |  |  | 				"cast_real":    core.FieldTypeNumber, | 
					
						
							|  |  |  | 				"cast_decimal": core.FieldTypeNumber, | 
					
						
							|  |  |  | 				"cast_numeric": core.FieldTypeNumber, | 
					
						
							|  |  |  | 				"cast_text":    core.FieldTypeText, | 
					
						
							|  |  |  | 				"cast_bool":    core.FieldTypeBool, | 
					
						
							|  |  |  | 				"cast_boolean": core.FieldTypeBool, | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 				// json because they are nullable
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 				"sum": core.FieldTypeJSON, | 
					
						
							|  |  |  | 				"avg": core.FieldTypeJSON, | 
					
						
							|  |  |  | 				"min": core.FieldTypeJSON, | 
					
						
							|  |  |  | 				"max": core.FieldTypeJSON, | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"query with reserved auth collection fields", | 
					
						
							|  |  |  | 			` | 
					
						
							|  |  |  | 				select | 
					
						
							|  |  |  | 					a.id, | 
					
						
							|  |  |  | 					a.username, | 
					
						
							|  |  |  | 					a.email, | 
					
						
							|  |  |  | 					a.emailVisibility, | 
					
						
							|  |  |  | 					a.verified, | 
					
						
							|  |  |  | 					demo1.id relid | 
					
						
							|  |  |  | 				from users a | 
					
						
							|  |  |  | 				left join demo1 | 
					
						
							|  |  |  | 			`, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 			map[string]string{ | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 				"id":              core.FieldTypeText, | 
					
						
							|  |  |  | 				"username":        core.FieldTypeText, | 
					
						
							|  |  |  | 				"email":           core.FieldTypeEmail, | 
					
						
							|  |  |  | 				"emailVisibility": core.FieldTypeBool, | 
					
						
							|  |  |  | 				"verified":        core.FieldTypeBool, | 
					
						
							|  |  |  | 				"relid":           core.FieldTypeRelation, | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"query with unknown fields and aliases", | 
					
						
							|  |  |  | 			`select | 
					
						
							|  |  |  | 				id, | 
					
						
							|  |  |  | 				id as id2, | 
					
						
							|  |  |  | 				text as text_alias, | 
					
						
							|  |  |  | 				url as url_alias, | 
					
						
							|  |  |  | 				"demo1"."bool" as bool_alias, | 
					
						
							|  |  |  | 				number as number_alias, | 
					
						
							|  |  |  | 				created created_alias, | 
					
						
							|  |  |  | 				updated updated_alias, | 
					
						
							|  |  |  | 				123 as custom | 
					
						
							| 
									
										
										
										
											2023-04-24 20:43:23 +08:00
										 |  |  | 			from demo1`, | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			false, | 
					
						
							|  |  |  | 			map[string]string{ | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 				"id":            core.FieldTypeText, | 
					
						
							|  |  |  | 				"id2":           core.FieldTypeRelation, | 
					
						
							|  |  |  | 				"text_alias":    core.FieldTypeText, | 
					
						
							|  |  |  | 				"url_alias":     core.FieldTypeURL, | 
					
						
							|  |  |  | 				"bool_alias":    core.FieldTypeBool, | 
					
						
							|  |  |  | 				"number_alias":  core.FieldTypeNumber, | 
					
						
							|  |  |  | 				"created_alias": core.FieldTypeAutodate, | 
					
						
							|  |  |  | 				"updated_alias": core.FieldTypeAutodate, | 
					
						
							|  |  |  | 				"custom":        core.FieldTypeJSON, | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2023-04-24 17:53:27 +08:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			"query with distinct and reordered id column", | 
					
						
							|  |  |  | 			`select distinct | 
					
						
							|  |  |  | 				id as id2, | 
					
						
							|  |  |  | 				id, | 
					
						
							|  |  |  | 				123 as custom | 
					
						
							| 
									
										
										
										
											2023-04-24 20:43:23 +08:00
										 |  |  | 			from demo1`, | 
					
						
							| 
									
										
										
										
											2023-04-24 17:53:27 +08:00
										 |  |  | 			false, | 
					
						
							|  |  |  | 			map[string]string{ | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 				"id2":    core.FieldTypeRelation, | 
					
						
							|  |  |  | 				"id":     core.FieldTypeText, | 
					
						
							|  |  |  | 				"custom": core.FieldTypeJSON, | 
					
						
							| 
									
										
										
										
											2023-04-24 17:53:27 +08:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2023-04-24 20:43:23 +08:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			"query with aliasing the same field multiple times", | 
					
						
							|  |  |  | 			`select | 
					
						
							|  |  |  | 				a.id as id, | 
					
						
							|  |  |  | 				a.text as alias1, | 
					
						
							|  |  |  | 				a.text as alias2, | 
					
						
							|  |  |  | 				b.text as alias3, | 
					
						
							|  |  |  | 				b.text as alias4 | 
					
						
							|  |  |  | 			from demo1 a | 
					
						
							|  |  |  | 			left join demo1 as b`, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 			map[string]string{ | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 				"id":     core.FieldTypeText, | 
					
						
							|  |  |  | 				"alias1": core.FieldTypeText, | 
					
						
							|  |  |  | 				"alias2": core.FieldTypeText, | 
					
						
							|  |  |  | 				"alias3": core.FieldTypeText, | 
					
						
							|  |  |  | 				"alias4": core.FieldTypeText, | 
					
						
							| 
									
										
										
										
											2023-04-24 20:43:23 +08:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, s := range scenarios { | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 		t.Run(s.name, func(t *testing.T) { | 
					
						
							|  |  |  | 			result, err := app.CreateViewFields(s.query) | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			hasErr := err != nil | 
					
						
							|  |  |  | 			if hasErr != s.expectError { | 
					
						
							|  |  |  | 				t.Fatalf("Expected hasErr %v, got %v (%v)", s.expectError, hasErr, err) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			if hasErr { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			if len(s.expectFields) != len(result) { | 
					
						
							|  |  |  | 				serialized, _ := json.Marshal(result) | 
					
						
							|  |  |  | 				t.Fatalf("Expected %d fields, got %d: \n%s", len(s.expectFields), len(result), serialized) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			for name, typ := range s.expectFields { | 
					
						
							|  |  |  | 				field := result.GetByName(name) | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 				if field == nil { | 
					
						
							|  |  |  | 					t.Fatalf("Expected to find field %s, got nil", name) | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 				if field.Type() != typ { | 
					
						
							|  |  |  | 					t.Fatalf("Expected field %s to be %q, got %q", name, typ, field.Type()) | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-03-19 22:18:33 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	ensureNoTempViews(app, t) | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestFindRecordByViewFile(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-01-03 10:30:20 +08:00
										 |  |  | 	t.Parallel() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 	app, _ := tests.NewTestApp() | 
					
						
							|  |  |  | 	defer app.Cleanup() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	prevCollection, err := app.FindCollectionByNameOrId("demo1") | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		t.Fatal(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	totalLevels := 6 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// create collection view mocks
 | 
					
						
							|  |  |  | 	fileOneAlias := "file_one one0" | 
					
						
							|  |  |  | 	fileManyAlias := "file_many many0" | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 	mockCollections := make([]*core.Collection, 0, totalLevels) | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 	for i := 0; i <= totalLevels; i++ { | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 		view := new(core.Collection) | 
					
						
							|  |  |  | 		view.Type = core.CollectionTypeView | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 		view.Name = fmt.Sprintf("_test_view%d", i) | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 		view.ViewQuery = fmt.Sprintf( | 
					
						
							|  |  |  | 			"select id, %s, %s from %s", | 
					
						
							|  |  |  | 			fileOneAlias, | 
					
						
							|  |  |  | 			fileManyAlias, | 
					
						
							|  |  |  | 			prevCollection.Name, | 
					
						
							|  |  |  | 		) | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// save view
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 		if err := app.Save(view); err != nil { | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 			t.Fatalf("Failed to save view%d: %v", i, err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		mockCollections = append(mockCollections, view) | 
					
						
							|  |  |  | 		prevCollection = view | 
					
						
							|  |  |  | 		fileOneAlias = fmt.Sprintf("one%d one%d", i, i+1) | 
					
						
							|  |  |  | 		fileManyAlias = fmt.Sprintf("many%d many%d", i, i+1) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	fileOneName := "test_d61b33QdDU.txt" | 
					
						
							|  |  |  | 	fileManyName := "test_QZFjKjXchk.txt" | 
					
						
							|  |  |  | 	expectedRecordId := "84nmscqy84lsi1t" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	scenarios := []struct { | 
					
						
							|  |  |  | 		name               string | 
					
						
							|  |  |  | 		collectionNameOrId string | 
					
						
							|  |  |  | 		fileFieldName      string | 
					
						
							|  |  |  | 		filename           string | 
					
						
							|  |  |  | 		expectError        bool | 
					
						
							|  |  |  | 		expectRecordId     string | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"missing collection", | 
					
						
							|  |  |  | 			"missing", | 
					
						
							|  |  |  | 			"a", | 
					
						
							|  |  |  | 			fileOneName, | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 			"", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"non-view collection", | 
					
						
							|  |  |  | 			"demo1", | 
					
						
							|  |  |  | 			"file_one", | 
					
						
							|  |  |  | 			fileOneName, | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 			"", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"view collection after the max recursion limit", | 
					
						
							|  |  |  | 			mockCollections[totalLevels-1].Name, | 
					
						
							|  |  |  | 			fmt.Sprintf("one%d", totalLevels-1), | 
					
						
							|  |  |  | 			fileOneName, | 
					
						
							|  |  |  | 			true, | 
					
						
							|  |  |  | 			"", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"first view collection (single file)", | 
					
						
							|  |  |  | 			mockCollections[0].Name, | 
					
						
							|  |  |  | 			"one0", | 
					
						
							|  |  |  | 			fileOneName, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 			expectedRecordId, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"first view collection (many files)", | 
					
						
							|  |  |  | 			mockCollections[0].Name, | 
					
						
							|  |  |  | 			"many0", | 
					
						
							|  |  |  | 			fileManyName, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 			expectedRecordId, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"last view collection before the recursion limit (single file)", | 
					
						
							|  |  |  | 			mockCollections[totalLevels-2].Name, | 
					
						
							|  |  |  | 			fmt.Sprintf("one%d", totalLevels-2), | 
					
						
							|  |  |  | 			fileOneName, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 			expectedRecordId, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			"last view collection before the recursion limit (many files)", | 
					
						
							|  |  |  | 			mockCollections[totalLevels-2].Name, | 
					
						
							|  |  |  | 			fmt.Sprintf("many%d", totalLevels-2), | 
					
						
							|  |  |  | 			fileManyName, | 
					
						
							|  |  |  | 			false, | 
					
						
							|  |  |  | 			expectedRecordId, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, s := range scenarios { | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 		t.Run(s.name, func(t *testing.T) { | 
					
						
							|  |  |  | 			record, err := app.FindRecordByViewFile( | 
					
						
							|  |  |  | 				s.collectionNameOrId, | 
					
						
							|  |  |  | 				s.fileFieldName, | 
					
						
							|  |  |  | 				s.filename, | 
					
						
							|  |  |  | 			) | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			hasErr := err != nil | 
					
						
							|  |  |  | 			if hasErr != s.expectError { | 
					
						
							|  |  |  | 				t.Fatalf("Expected hasErr %v, got %v (%v)", s.expectError, hasErr, err) | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			if hasErr { | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 00:23:19 +08:00
										 |  |  | 			if record.Id != s.expectRecordId { | 
					
						
							|  |  |  | 				t.Fatalf("Expected recordId %q, got %q", s.expectRecordId, record.Id) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2023-02-19 01:33:42 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } |