[#2992] fixed zero-default value not being used if the field is not explicitly set when manually creating records
This commit is contained in:
		
							parent
							
								
									34fe55686d
								
							
						
					
					
						commit
						1330e2e1e7
					
				| 
						 | 
				
			
			@ -110,6 +110,10 @@
 | 
			
		|||
 | 
			
		||||
- Added new utility `github.com/pocketbase/pocketbase/tools/template` package to assist with rendering HTML templates using the standard Go `html/template` and `text/template` syntax.
 | 
			
		||||
 | 
			
		||||
- Fixed zero-default value not being used if the field is not explicitly set when manually creating records ([#2992](https://github.com/pocketbase/pocketbase/issues/2992)).
 | 
			
		||||
  Additionally, `record.Get(field)` will now always return normalized value (the same as in the json serialization) for consistency and to avoid ambiguities what is stored in the related DB table.
 | 
			
		||||
  The schema fields columns `DEFAULT` definition was also updated for new collections to ensure that `NULL` values can't be accidentally inserted.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## v0.16.10
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -173,6 +173,15 @@ func (dao *Dao) normalizeSingleVsMultipleFieldChanges(newCollection, oldCollecti
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	return dao.RunInTransaction(func(txDao *Dao) error {
 | 
			
		||||
		// temporary disable the schema error checks to prevent view and trigger errors
 | 
			
		||||
		// when "altering" (aka. deleting and recreating) the non-normalized columns
 | 
			
		||||
		if _, err := txDao.DB().NewQuery("PRAGMA writable_schema = ON").Execute(); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// executed with defer to make sure that the pragma is always reverted
 | 
			
		||||
		// in case of an error and when nested transactions are used
 | 
			
		||||
		defer txDao.DB().NewQuery("PRAGMA writable_schema = RESET").Execute()
 | 
			
		||||
 | 
			
		||||
		for _, newField := range newCollection.Schema.Fields() {
 | 
			
		||||
			// allow to continue even if there is no old field for the cases
 | 
			
		||||
			// when a new field is added and there are already inserted data
 | 
			
		||||
| 
						 | 
				
			
			@ -192,11 +201,26 @@ func (dao *Dao) normalizeSingleVsMultipleFieldChanges(newCollection, oldCollecti
 | 
			
		|||
				continue // no change
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var updateQuery *dbx.Query
 | 
			
		||||
			// update the column definition by:
 | 
			
		||||
			// 1. inserting a new column with the new definition
 | 
			
		||||
			// 2. copy normalized values from the original column to the new one
 | 
			
		||||
			// 3. drop the original column
 | 
			
		||||
			// 4. rename the new column to the original column
 | 
			
		||||
			// -------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
			originalName := newField.Name
 | 
			
		||||
			tempName := "_" + newField.Name + security.PseudorandomString(5)
 | 
			
		||||
 | 
			
		||||
			_, err := txDao.DB().AddColumn(newCollection.Name, tempName, newField.ColDefinition()).Execute()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var copyQuery *dbx.Query
 | 
			
		||||
 | 
			
		||||
			if !isOldMultiple && isNewMultiple {
 | 
			
		||||
				// single -> multiple (convert to array)
 | 
			
		||||
				updateQuery = txDao.DB().NewQuery(fmt.Sprintf(
 | 
			
		||||
				copyQuery = txDao.DB().NewQuery(fmt.Sprintf(
 | 
			
		||||
					`UPDATE {{%s}} set [[%s]] = (
 | 
			
		||||
							CASE
 | 
			
		||||
								WHEN COALESCE([[%s]], '') = ''
 | 
			
		||||
| 
						 | 
				
			
			@ -211,19 +235,19 @@ func (dao *Dao) normalizeSingleVsMultipleFieldChanges(newCollection, oldCollecti
 | 
			
		|||
							END
 | 
			
		||||
						)`,
 | 
			
		||||
					newCollection.Name,
 | 
			
		||||
					newField.Name,
 | 
			
		||||
					newField.Name,
 | 
			
		||||
					newField.Name,
 | 
			
		||||
					newField.Name,
 | 
			
		||||
					newField.Name,
 | 
			
		||||
					newField.Name,
 | 
			
		||||
					tempName,
 | 
			
		||||
					originalName,
 | 
			
		||||
					originalName,
 | 
			
		||||
					originalName,
 | 
			
		||||
					originalName,
 | 
			
		||||
					originalName,
 | 
			
		||||
				))
 | 
			
		||||
			} else {
 | 
			
		||||
				// multiple -> single (keep only the last element)
 | 
			
		||||
				//
 | 
			
		||||
				// note: for file fields the actual files are not deleted
 | 
			
		||||
				// allowing additional custom handling via migration.
 | 
			
		||||
				updateQuery = txDao.DB().NewQuery(fmt.Sprintf(
 | 
			
		||||
				// note: for file fields the actual file objects are not
 | 
			
		||||
				// deleted allowing additional custom handling via migration
 | 
			
		||||
				copyQuery = txDao.DB().NewQuery(fmt.Sprintf(
 | 
			
		||||
					`UPDATE {{%s}} set [[%s]] = (
 | 
			
		||||
						CASE
 | 
			
		||||
							WHEN COALESCE([[%s]], '[]') = '[]'
 | 
			
		||||
| 
						 | 
				
			
			@ -238,21 +262,35 @@ func (dao *Dao) normalizeSingleVsMultipleFieldChanges(newCollection, oldCollecti
 | 
			
		|||
						END
 | 
			
		||||
					)`,
 | 
			
		||||
					newCollection.Name,
 | 
			
		||||
					newField.Name,
 | 
			
		||||
					newField.Name,
 | 
			
		||||
					newField.Name,
 | 
			
		||||
					newField.Name,
 | 
			
		||||
					newField.Name,
 | 
			
		||||
					newField.Name,
 | 
			
		||||
					tempName,
 | 
			
		||||
					originalName,
 | 
			
		||||
					originalName,
 | 
			
		||||
					originalName,
 | 
			
		||||
					originalName,
 | 
			
		||||
					originalName,
 | 
			
		||||
				))
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if _, err := updateQuery.Execute(); err != nil {
 | 
			
		||||
			// copy the normalized values
 | 
			
		||||
			if _, err := copyQuery.Execute(); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// drop the original column
 | 
			
		||||
			if _, err := txDao.DB().DropColumn(newCollection.Name, originalName).Execute(); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// rename the new column back to the original
 | 
			
		||||
			if _, err := txDao.DB().RenameColumn(newCollection.Name, tempName, originalName).Execute(); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
		// revert the pragma and reload the schema
 | 
			
		||||
		_, revertErr := txDao.DB().NewQuery("PRAGMA writable_schema = RESET").Execute()
 | 
			
		||||
 | 
			
		||||
		return revertErr
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -188,7 +188,51 @@ func TestSingleVsMultipleValuesNormalization(t *testing.T) {
 | 
			
		|||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type expectation struct {
 | 
			
		||||
	// ensures that the writable schema was reverted to its expected default
 | 
			
		||||
	var writableSchema bool
 | 
			
		||||
	app.Dao().DB().NewQuery("PRAGMA writable_schema").Row(&writableSchema)
 | 
			
		||||
	if writableSchema == true {
 | 
			
		||||
		t.Fatalf("Expected writable_schema to be OFF, got %v", writableSchema)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// check whether the columns DEFAULT definition was updated
 | 
			
		||||
	// ---------------------------------------------------------------
 | 
			
		||||
	tableInfo, err := app.Dao().TableInfo(collection.Name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tableInfoExpectations := map[string]string{
 | 
			
		||||
		"select_one":   `'[]'`,
 | 
			
		||||
		"select_many":  `''`,
 | 
			
		||||
		"file_one":     `'[]'`,
 | 
			
		||||
		"file_many":    `''`,
 | 
			
		||||
		"rel_one":      `'[]'`,
 | 
			
		||||
		"rel_many":     `''`,
 | 
			
		||||
		"new_multiple": `'[]'`,
 | 
			
		||||
	}
 | 
			
		||||
	for col, dflt := range tableInfoExpectations {
 | 
			
		||||
		t.Run("check default for "+col, func(t *testing.T) {
 | 
			
		||||
			var row *models.TableInfoRow
 | 
			
		||||
			for _, r := range tableInfo {
 | 
			
		||||
				if r.Name == col {
 | 
			
		||||
					row = r
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if row == nil {
 | 
			
		||||
				t.Fatalf("Missing info for column %q", col)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if v := row.DefaultValue.String(); v != dflt {
 | 
			
		||||
				t.Fatalf("Expected default value %q, got %q", dflt, v)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// check whether the values were normalized
 | 
			
		||||
	// ---------------------------------------------------------------
 | 
			
		||||
	type fieldsExpectation struct {
 | 
			
		||||
		SelectOne   string `db:"select_one"`
 | 
			
		||||
		SelectMany  string `db:"select_many"`
 | 
			
		||||
		FileOne     string `db:"file_one"`
 | 
			
		||||
| 
						 | 
				
			
			@ -198,13 +242,13 @@ func TestSingleVsMultipleValuesNormalization(t *testing.T) {
 | 
			
		|||
		NewMultiple string `db:"new_multiple"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scenarios := []struct {
 | 
			
		||||
	fieldsScenarios := []struct {
 | 
			
		||||
		recordId string
 | 
			
		||||
		expected expectation
 | 
			
		||||
		expected fieldsExpectation
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			"imy661ixudk5izi",
 | 
			
		||||
			expectation{
 | 
			
		||||
			fieldsExpectation{
 | 
			
		||||
				SelectOne:   `[]`,
 | 
			
		||||
				SelectMany:  ``,
 | 
			
		||||
				FileOne:     `[]`,
 | 
			
		||||
| 
						 | 
				
			
			@ -216,7 +260,7 @@ func TestSingleVsMultipleValuesNormalization(t *testing.T) {
 | 
			
		|||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"al1h9ijdeojtsjy",
 | 
			
		||||
			expectation{
 | 
			
		||||
			fieldsExpectation{
 | 
			
		||||
				SelectOne:   `["optionB"]`,
 | 
			
		||||
				SelectMany:  `optionB`,
 | 
			
		||||
				FileOne:     `["300_Jsjq7RdBgA.png"]`,
 | 
			
		||||
| 
						 | 
				
			
			@ -228,7 +272,7 @@ func TestSingleVsMultipleValuesNormalization(t *testing.T) {
 | 
			
		|||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"84nmscqy84lsi1t",
 | 
			
		||||
			expectation{
 | 
			
		||||
			fieldsExpectation{
 | 
			
		||||
				SelectOne:   `["optionB"]`,
 | 
			
		||||
				SelectMany:  `optionC`,
 | 
			
		||||
				FileOne:     `["test_d61b33QdDU.txt"]`,
 | 
			
		||||
| 
						 | 
				
			
			@ -240,8 +284,9 @@ func TestSingleVsMultipleValuesNormalization(t *testing.T) {
 | 
			
		|||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, s := range scenarios {
 | 
			
		||||
		result := new(expectation)
 | 
			
		||||
	for _, s := range fieldsScenarios {
 | 
			
		||||
		t.Run("check fields for record "+s.recordId, func(t *testing.T) {
 | 
			
		||||
			result := new(fieldsExpectation)
 | 
			
		||||
 | 
			
		||||
			err := app.Dao().DB().Select(
 | 
			
		||||
				"select_one",
 | 
			
		||||
| 
						 | 
				
			
			@ -253,24 +298,22 @@ func TestSingleVsMultipleValuesNormalization(t *testing.T) {
 | 
			
		|||
				"new_multiple",
 | 
			
		||||
			).From(collection.Name).Where(dbx.HashExp{"id": s.recordId}).One(result)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
			t.Errorf("[%s] Failed to load record: %v", s.recordId, err)
 | 
			
		||||
			continue
 | 
			
		||||
				t.Fatalf("Failed to load record: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			encodedResult, err := json.Marshal(result)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
			t.Errorf("[%s] Failed to encode result: %v", s.recordId, err)
 | 
			
		||||
			continue
 | 
			
		||||
				t.Fatalf("Failed to encode result: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			encodedExpectation, err := json.Marshal(s.expected)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
			t.Errorf("[%s] Failed to encode expectation: %v", s.recordId, err)
 | 
			
		||||
			continue
 | 
			
		||||
				t.Fatalf("Failed to encode expectation: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if !bytes.EqualFold(encodedExpectation, encodedResult) {
 | 
			
		||||
			t.Errorf("[%s] Expected \n%s, \ngot \n%s", s.recordId, encodedExpectation, encodedResult)
 | 
			
		||||
		}
 | 
			
		||||
				t.Fatalf("Expected \n%s, \ngot \n%s", encodedExpectation, encodedResult)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										5
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										5
									
								
								go.sum
								
								
								
								
							| 
						 | 
				
			
			@ -12,8 +12,6 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDe
 | 
			
		|||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
 | 
			
		||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
 | 
			
		||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
 | 
			
		||||
github.com/aws/aws-sdk-go v1.44.306 h1:H487V/1N09BDxeGR7oR+LloC2uUpmf4atmqJaBgQOIs=
 | 
			
		||||
github.com/aws/aws-sdk-go v1.44.306/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
 | 
			
		||||
github.com/aws/aws-sdk-go v1.44.307 h1:2R0/EPgpZcFSUwZhYImq/srjaOrOfLv5MNRzrFyAM38=
 | 
			
		||||
github.com/aws/aws-sdk-go v1.44.307/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
 | 
			
		||||
github.com/aws/aws-sdk-go-v2 v1.19.0 h1:klAT+y3pGFBU/qVf1uzwttpBbiuozJYWzNLHioyDJ+k=
 | 
			
		||||
| 
						 | 
				
			
			@ -168,8 +166,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
			
		|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
			
		||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 | 
			
		||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 | 
			
		||||
github.com/labstack/echo/v5 v5.0.0-20220201181537-ed2888cfa198 h1:lFz33AOOXwTpqOiHvrN8nmTdkxSfuNLHLPjgQ1muPpU=
 | 
			
		||||
github.com/labstack/echo/v5 v5.0.0-20220201181537-ed2888cfa198/go.mod h1:uh3YlzsEJj7OG57rDWj6c3WEkOF1ZHGBQkDuUZw3rE8=
 | 
			
		||||
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 h1:FwuzbVh87iLiUQj1+uQUsuw9x5t9m5n5g7rG7o4svW4=
 | 
			
		||||
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61/go.mod h1:paQfF1YtHe+GrGg5fOgjsjoCX/UKDr9bc1DoWpZfns8=
 | 
			
		||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 | 
			
		||||
| 
						 | 
				
			
			@ -210,7 +206,6 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
 | 
			
		|||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 | 
			
		||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
 | 
			
		||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 | 
			
		||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
 | 
			
		||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -324,7 +324,7 @@ func (m *Record) Set(key string, value any) {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get returns a single record model data value for "key".
 | 
			
		||||
// Get returns a normalized single record model data value for "key".
 | 
			
		||||
func (m *Record) Get(key string) any {
 | 
			
		||||
	switch key {
 | 
			
		||||
	case schema.FieldNameId:
 | 
			
		||||
| 
						 | 
				
			
			@ -334,11 +334,27 @@ func (m *Record) Get(key string) any {
 | 
			
		|||
	case schema.FieldNameUpdated:
 | 
			
		||||
		return m.Updated
 | 
			
		||||
	default:
 | 
			
		||||
		if m.data == nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		var v any
 | 
			
		||||
		if m.data != nil {
 | 
			
		||||
			v = m.data.Get(key)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return m.data.Get(key)
 | 
			
		||||
		// normalize the field value in case it is missing or an incorrect type was set
 | 
			
		||||
		// to ensure that the DB will always have normalized columns value.
 | 
			
		||||
		if field := m.Collection().Schema.GetFieldByName(key); field != nil {
 | 
			
		||||
			v = field.PrepareValue(v)
 | 
			
		||||
		} else if m.collection.IsAuth() {
 | 
			
		||||
			switch key {
 | 
			
		||||
			case schema.FieldNameEmailVisibility, schema.FieldNameVerified:
 | 
			
		||||
				v = cast.ToBool(v)
 | 
			
		||||
			case schema.FieldNameLastResetSentAt, schema.FieldNameLastVerificationSentAt:
 | 
			
		||||
				v, _ = types.ParseDateTime(v)
 | 
			
		||||
			case schema.FieldNameUsername, schema.FieldNameEmail, schema.FieldNameTokenKey, schema.FieldNamePasswordHash:
 | 
			
		||||
				v = cast.ToString(v)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return v
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -729,6 +729,22 @@ func TestRecordSetAndGet(t *testing.T) {
 | 
			
		|||
				Name: "field2",
 | 
			
		||||
				Type: schema.FieldTypeNumber,
 | 
			
		||||
			},
 | 
			
		||||
			// fields that are not explicitly set to check
 | 
			
		||||
			// the default retrieval value (single and multiple)
 | 
			
		||||
			&schema.SchemaField{
 | 
			
		||||
				Name: "field3",
 | 
			
		||||
				Type: schema.FieldTypeBool,
 | 
			
		||||
			},
 | 
			
		||||
			&schema.SchemaField{
 | 
			
		||||
				Name:    "field4",
 | 
			
		||||
				Type:    schema.FieldTypeSelect,
 | 
			
		||||
				Options: &schema.SelectOptions{MaxSelect: 2},
 | 
			
		||||
			},
 | 
			
		||||
			&schema.SchemaField{
 | 
			
		||||
				Name:    "field5",
 | 
			
		||||
				Type:    schema.FieldTypeRelation,
 | 
			
		||||
				Options: &schema.RelationOptions{MaxSelect: types.Pointer(1)},
 | 
			
		||||
			},
 | 
			
		||||
		),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -741,28 +757,40 @@ func TestRecordSetAndGet(t *testing.T) {
 | 
			
		|||
	m.Set("unknown", 456)                        // undefined fields are allowed but not exported by default
 | 
			
		||||
	m.Set("expand", map[string]any{"test": 123}) // should store the value in m.expand
 | 
			
		||||
 | 
			
		||||
	if m.Get("id") != "test_id" {
 | 
			
		||||
		t.Fatalf("Expected id %q, got %q", "test_id", m.Get("id"))
 | 
			
		||||
	if v := m.Get("id"); v != "test_id" {
 | 
			
		||||
		t.Fatalf("Expected id %q, got %q", "test_id", v)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if m.GetString("created") != "2022-09-15 00:00:00.123Z" {
 | 
			
		||||
		t.Fatalf("Expected created %q, got %q", "2022-09-15 00:00:00.123Z", m.GetString("created"))
 | 
			
		||||
	if v := m.GetString("created"); v != "2022-09-15 00:00:00.123Z" {
 | 
			
		||||
		t.Fatalf("Expected created %q, got %q", "2022-09-15 00:00:00.123Z", v)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if m.GetString("updated") != "" {
 | 
			
		||||
		t.Fatalf("Expected updated to be empty, got %q", m.GetString("updated"))
 | 
			
		||||
	if v := m.GetString("updated"); v != "" {
 | 
			
		||||
		t.Fatalf("Expected updated to be empty, got %q", v)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if m.Get("field1") != "123" {
 | 
			
		||||
		t.Fatalf("Expected field1 %q, got %v", "123", m.Get("field1"))
 | 
			
		||||
	if v, ok := m.Get("field1").(string); !ok || v != "123" {
 | 
			
		||||
		t.Fatalf("Expected field1 %#v, got %#v", "123", m.Get("field1"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if m.Get("field2") != 0.0 {
 | 
			
		||||
		t.Fatalf("Expected field2 %v, got %v", 0.0, m.Get("field2"))
 | 
			
		||||
	if v, ok := m.Get("field2").(float64); !ok || v != 0.0 {
 | 
			
		||||
		t.Fatalf("Expected field2 %#v, got %#v", 0.0, m.Get("field2"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if m.Get("unknown") != 456 {
 | 
			
		||||
		t.Fatalf("Expected unknown %v, got %v", 456, m.Get("unknown"))
 | 
			
		||||
	if v, ok := m.Get("field3").(bool); !ok || v != false {
 | 
			
		||||
		t.Fatalf("Expected field3 %#v, got %#v", false, m.Get("field3"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if v, ok := m.Get("field4").([]string); !ok || len(v) != 0 {
 | 
			
		||||
		t.Fatalf("Expected field4 %#v, got %#v", "[]", m.Get("field4"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if v, ok := m.Get("field5").(string); !ok || len(v) != 0 {
 | 
			
		||||
		t.Fatalf("Expected field5 %#v, got %#v", "", m.Get("field5"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if v := m.Get("unknown"); v != 456 {
 | 
			
		||||
		t.Fatalf("Expected unknown %v, got %v", 456, v)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if m.Expand()["test"] != 123 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -143,13 +143,17 @@ type SchemaField struct {
 | 
			
		|||
func (f *SchemaField) ColDefinition() string {
 | 
			
		||||
	switch f.Type {
 | 
			
		||||
	case FieldTypeNumber:
 | 
			
		||||
		return "NUMERIC DEFAULT 0"
 | 
			
		||||
		return "NUMERIC DEFAULT 0 NOT NULL"
 | 
			
		||||
	case FieldTypeBool:
 | 
			
		||||
		return "BOOLEAN DEFAULT FALSE"
 | 
			
		||||
		return "BOOLEAN DEFAULT FALSE NOT NULL"
 | 
			
		||||
	case FieldTypeJson:
 | 
			
		||||
		return "JSON DEFAULT NULL"
 | 
			
		||||
	default:
 | 
			
		||||
		return "TEXT DEFAULT ''"
 | 
			
		||||
		if opt, ok := f.Options.(MultiValuer); ok && opt.IsMultiple() {
 | 
			
		||||
			return "JSON DEFAULT '[]' NOT NULL"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return "TEXT DEFAULT '' NOT NULL"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -63,47 +63,59 @@ func TestSchemaFieldColDefinition(t *testing.T) {
 | 
			
		|||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			schema.SchemaField{Type: schema.FieldTypeText, Name: "test"},
 | 
			
		||||
			"TEXT DEFAULT ''",
 | 
			
		||||
			"TEXT DEFAULT '' NOT NULL",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			schema.SchemaField{Type: schema.FieldTypeNumber, Name: "test"},
 | 
			
		||||
			"NUMERIC DEFAULT 0",
 | 
			
		||||
			"NUMERIC DEFAULT 0 NOT NULL",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			schema.SchemaField{Type: schema.FieldTypeBool, Name: "test"},
 | 
			
		||||
			"BOOLEAN DEFAULT FALSE",
 | 
			
		||||
			"BOOLEAN DEFAULT FALSE NOT NULL",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			schema.SchemaField{Type: schema.FieldTypeEmail, Name: "test"},
 | 
			
		||||
			"TEXT DEFAULT ''",
 | 
			
		||||
			"TEXT DEFAULT '' NOT NULL",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			schema.SchemaField{Type: schema.FieldTypeUrl, Name: "test"},
 | 
			
		||||
			"TEXT DEFAULT ''",
 | 
			
		||||
			"TEXT DEFAULT '' NOT NULL",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			schema.SchemaField{Type: schema.FieldTypeEditor, Name: "test"},
 | 
			
		||||
			"TEXT DEFAULT ''",
 | 
			
		||||
			"TEXT DEFAULT '' NOT NULL",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			schema.SchemaField{Type: schema.FieldTypeDate, Name: "test"},
 | 
			
		||||
			"TEXT DEFAULT ''",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			schema.SchemaField{Type: schema.FieldTypeSelect, Name: "test"},
 | 
			
		||||
			"TEXT DEFAULT ''",
 | 
			
		||||
			"TEXT DEFAULT '' NOT NULL",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			schema.SchemaField{Type: schema.FieldTypeJson, Name: "test"},
 | 
			
		||||
			"JSON DEFAULT NULL",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			schema.SchemaField{Type: schema.FieldTypeFile, Name: "test"},
 | 
			
		||||
			"TEXT DEFAULT ''",
 | 
			
		||||
			schema.SchemaField{Type: schema.FieldTypeSelect, Name: "test"},
 | 
			
		||||
			"TEXT DEFAULT '' NOT NULL",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			schema.SchemaField{Type: schema.FieldTypeRelation, Name: "test"},
 | 
			
		||||
			"TEXT DEFAULT ''",
 | 
			
		||||
			schema.SchemaField{Type: schema.FieldTypeSelect, Name: "test_multiple", Options: &schema.SelectOptions{MaxSelect: 2}},
 | 
			
		||||
			"JSON DEFAULT '[]' NOT NULL",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			schema.SchemaField{Type: schema.FieldTypeFile, Name: "test"},
 | 
			
		||||
			"TEXT DEFAULT '' NOT NULL",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			schema.SchemaField{Type: schema.FieldTypeFile, Name: "test_multiple", Options: &schema.FileOptions{MaxSelect: 2}},
 | 
			
		||||
			"JSON DEFAULT '[]' NOT NULL",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			schema.SchemaField{Type: schema.FieldTypeRelation, Name: "test", Options: &schema.RelationOptions{MaxSelect: types.Pointer(1)}},
 | 
			
		||||
			"TEXT DEFAULT '' NOT NULL",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			schema.SchemaField{Type: schema.FieldTypeRelation, Name: "test_multiple", Options: &schema.RelationOptions{MaxSelect: nil}},
 | 
			
		||||
			"JSON DEFAULT '[]' NOT NULL",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -141,6 +141,13 @@ declare function routerPre(...middlewares: Array<string|echo.MiddlewareFunc>): v
 | 
			
		|||
// baseBinds
 | 
			
		||||
// -------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Global helper variable that contains the absolute path to the app pb_hooks directory.
 | 
			
		||||
 *
 | 
			
		||||
 * @group PocketBase
 | 
			
		||||
 */
 | 
			
		||||
declare var __hooks: string
 | 
			
		||||
 | 
			
		||||
// skip on* hook methods as they are registered via the global on* method
 | 
			
		||||
type appWithoutHooks = Omit<pocketbase.PocketBase, ` + "`on${string}`" + `>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -148,6 +155,8 @@ type appWithoutHooks = Omit<pocketbase.PocketBase, ` + "`on${string}`" + `>
 | 
			
		|||
 * ` + "`$app`" + ` is the current running PocketBase instance that is globally
 | 
			
		||||
 * available in each .pb.js file.
 | 
			
		||||
 *
 | 
			
		||||
 * _Note that this variable is available only in pb_hooks context._
 | 
			
		||||
 *
 | 
			
		||||
 * @namespace
 | 
			
		||||
 * @group PocketBase
 | 
			
		||||
 */
 | 
			
		||||
| 
						 | 
				
			
			@ -162,14 +171,10 @@ declare var $app: appWithoutHooks
 | 
			
		|||
 * Example:
 | 
			
		||||
 *
 | 
			
		||||
 * ` + "```" + `js
 | 
			
		||||
 * routerAdd("get", "/hello", (c) => {
 | 
			
		||||
 * const html = $template.loadFiles(
 | 
			
		||||
 *     "views/layout.html",
 | 
			
		||||
 *     "views/content.html",
 | 
			
		||||
 * ).render({"name": "John"})
 | 
			
		||||
 *
 | 
			
		||||
 *     return c.html(200, html)
 | 
			
		||||
 * })
 | 
			
		||||
 * ` + "```" + `
 | 
			
		||||
 *
 | 
			
		||||
 * _Note that this method is available only in pb_hooks context._
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -201,6 +201,11 @@ func (p *plugin) registerHooks() error {
 | 
			
		|||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	absHooksDir, err := filepath.Abs(p.config.HooksDir)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	p.app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
 | 
			
		||||
		e.Router.HTTPErrorHandler = p.normalizeServeExceptions(e.Router.HTTPErrorHandler)
 | 
			
		||||
		return nil
 | 
			
		||||
| 
						 | 
				
			
			@ -225,6 +230,7 @@ func (p *plugin) registerHooks() error {
 | 
			
		|||
		apisBinds(vm)
 | 
			
		||||
		vm.Set("$app", p.app)
 | 
			
		||||
		vm.Set("$template", templateRegistry)
 | 
			
		||||
		vm.Set("__hooks", absHooksDir)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// initiliaze the executor vms
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue