// Copyright 2017 The Xorm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tests

import (
	"database/sql"
	"database/sql/driver"
	"encoding/json"
	"errors"
	"fmt"
	"math"
	"math/big"
	"reflect"
	"strconv"
	"testing"

	"code.forgejo.org/xorm/xorm"
	"code.forgejo.org/xorm/xorm/convert"
	"code.forgejo.org/xorm/xorm/schemas"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestArrayField(t *testing.T) {
	assert.NoError(t, PrepareEngine())

	type ArrayStruct struct {
		Id   int64
		Name [20]byte `xorm:"char(80)"`
	}

	assert.NoError(t, testEngine.Sync(new(ArrayStruct)))

	as := ArrayStruct{
		Name: [20]byte{
			96, 96, 96, 96, 96,
			96, 96, 96, 96, 96,
			96, 96, 96, 96, 96,
			96, 96, 96, 96, 96,
		},
	}
	cnt, err := testEngine.Insert(&as)
	assert.NoError(t, err)
	assert.EqualValues(t, 1, cnt)

	var arr ArrayStruct
	has, err := testEngine.ID(1).Get(&arr)
	assert.NoError(t, err)
	assert.Equal(t, true, has)
	assert.Equal(t, as.Name, arr.Name)

	var arrs []ArrayStruct
	err = testEngine.Find(&arrs)
	assert.NoError(t, err)
	assert.EqualValues(t, 1, len(arrs))
	assert.Equal(t, as.Name, arrs[0].Name)

	newName := [20]byte{
		90, 96, 96, 96, 96,
		96, 96, 96, 96, 96,
		96, 96, 96, 96, 96,
		96, 96, 96, 96, 96,
	}

	cnt, err = testEngine.ID(1).Update(&ArrayStruct{
		Name: newName,
	})
	assert.NoError(t, err)
	assert.EqualValues(t, 1, cnt)

	var newArr ArrayStruct
	has, err = testEngine.ID(1).Get(&newArr)
	assert.NoError(t, err)
	assert.Equal(t, true, has)
	assert.Equal(t, newName, newArr.Name)

	cnt, err = testEngine.ID(1).Delete(new(ArrayStruct))
	assert.NoError(t, err)
	assert.EqualValues(t, 1, cnt)

	var cfgArr ArrayStruct
	has, err = testEngine.ID(1).Get(&cfgArr)
	assert.NoError(t, err)
	assert.Equal(t, false, has)
}

func TestGetBytes(t *testing.T) {
	assert.NoError(t, PrepareEngine())

	type Varbinary struct {
		Data []byte `xorm:"VARBINARY(250)"`
	}

	err := testEngine.Sync(new(Varbinary))
	assert.NoError(t, err)

	cnt, err := testEngine.Insert(&Varbinary{
		Data: []byte("test"),
	})
	assert.NoError(t, err)
	assert.EqualValues(t, 1, cnt)

	var b Varbinary
	has, err := testEngine.Get(&b)
	assert.NoError(t, err)
	assert.Equal(t, true, has)
	assert.Equal(t, "test", string(b.Data))
}

type ConvString string

func (s *ConvString) FromDB(data []byte) error {
	*s = ConvString("prefix---" + string(data))
	return nil
}

func (s *ConvString) ToDB() ([]byte, error) {
	return []byte(string(*s)), nil
}

type ConvConfig struct {
	Name string
	Id   int64
}

func (s *ConvConfig) FromDB(data []byte) error {
	if data == nil {
		return nil
	}
	return json.Unmarshal(data, s)
}

func (s *ConvConfig) ToDB() ([]byte, error) {
	if s == nil {
		return nil, nil
	}
	return json.Marshal(s)
}

type SliceType []*ConvConfig

func (s *SliceType) FromDB(data []byte) error {
	return json.Unmarshal(data, s)
}

func (s *SliceType) ToDB() ([]byte, error) {
	return json.Marshal(s)
}

type Nullable struct {
	Data string
}

func (s *Nullable) FromDB(data []byte) error {
	if data == nil {
		return nil
	}

	*s = Nullable{
		Data: string(data),
	}

	return nil
}

func (s *Nullable) ToDB() ([]byte, error) {
	if s == nil {
		return nil, nil
	}

	return []byte(s.Data), nil
}

type ConvStruct struct {
	Conv      ConvString
	Conv2     *ConvString
	Cfg1      ConvConfig
	Cfg2      *ConvConfig        `xorm:"TEXT"`
	Cfg3      convert.Conversion `xorm:"BLOB"`
	Slice     SliceType
	Nullable1 *Nullable `xorm:"null"`
	Nullable2 *Nullable `xorm:"null"`
}

func (c *ConvStruct) BeforeSet(name string, cell xorm.Cell) {
	if name == "cfg3" || name == "Cfg3" {
		c.Cfg3 = new(ConvConfig)
	}
}

func TestConversion(t *testing.T) {
	assert.NoError(t, PrepareEngine())

	c := new(ConvStruct)
	assert.NoError(t, testEngine.DropTables(c))
	assert.NoError(t, testEngine.Sync(c))

	var s ConvString = "sssss"
	c.Conv = "tttt"
	c.Conv2 = &s
	c.Cfg1 = ConvConfig{"mm", 1}
	c.Cfg2 = &ConvConfig{"xx", 2}
	c.Cfg3 = &ConvConfig{"zz", 3}
	c.Slice = []*ConvConfig{{"yy", 4}, {"ff", 5}}
	c.Nullable1 = &Nullable{Data: "test"}
	c.Nullable2 = nil

	_, err := testEngine.Nullable("nullable2").Insert(c)
	assert.NoError(t, err)

	c1 := new(ConvStruct)
	has, err := testEngine.Get(c1)
	assert.NoError(t, err)
	assert.True(t, has)
	assert.EqualValues(t, "prefix---tttt", string(c1.Conv))
	assert.NotNil(t, c1.Conv2)
	assert.EqualValues(t, "prefix---"+s, *c1.Conv2)
	assert.EqualValues(t, c.Cfg1, c1.Cfg1)
	assert.NotNil(t, c1.Cfg2)
	assert.EqualValues(t, *c.Cfg2, *c1.Cfg2)
	assert.NotNil(t, c1.Cfg3)
	assert.EqualValues(t, *c.Cfg3.(*ConvConfig), *c1.Cfg3.(*ConvConfig))
	assert.EqualValues(t, 2, len(c1.Slice))
	assert.EqualValues(t, *c.Slice[0], *c1.Slice[0])
	assert.EqualValues(t, *c.Slice[1], *c1.Slice[1])

	cnt, err := testEngine.Where("1=1").Delete(new(ConvStruct))
	assert.NoError(t, err)
	assert.EqualValues(t, 1, cnt)

	c.Cfg2 = nil

	_, err = testEngine.Insert(c)
	assert.NoError(t, err)

	c2 := new(ConvStruct)
	has, err = testEngine.Get(c2)
	assert.NoError(t, err)
	assert.True(t, has)
	assert.EqualValues(t, "prefix---tttt", string(c2.Conv))
	assert.NotNil(t, c2.Conv2)
	assert.EqualValues(t, "prefix---"+s, *c2.Conv2)
	assert.EqualValues(t, c.Cfg1, c2.Cfg1)
	assert.Nil(t, c2.Cfg2)
	assert.NotNil(t, c2.Cfg3)
	assert.EqualValues(t, *c.Cfg3.(*ConvConfig), *c2.Cfg3.(*ConvConfig))
	assert.EqualValues(t, 2, len(c2.Slice))
	assert.EqualValues(t, *c.Slice[0], *c2.Slice[0])
	assert.EqualValues(t, *c.Slice[1], *c2.Slice[1])
	assert.NotNil(t, c1.Nullable1)
	assert.Equal(t, c1.Nullable1.Data, "test")
	assert.Nil(t, c1.Nullable2)
}

type (
	MyInt   int
	MyUInt  uint
	MyFloat float64
)

type MyStruct struct {
	Type      MyInt
	U         MyUInt
	F         MyFloat
	S         MyString
	IA        []MyInt
	UA        []MyUInt
	FA        []MyFloat
	SA        []MyString
	NameArray []string
	Name      string
	UIA       []uint
	UIA8      []uint8
	UIA16     []uint16
	UIA32     []uint32
	UIA64     []uint64
	UI        uint
	// C64       complex64
	MSS map[string]string
}

func TestCustomType1(t *testing.T) {
	assert.NoError(t, PrepareEngine())

	err := testEngine.DropTables(&MyStruct{})
	assert.NoError(t, err)

	err = testEngine.CreateTables(&MyStruct{})
	assert.NoError(t, err)

	i := MyStruct{Name: "Test", Type: MyInt(1)}
	i.U = 23
	i.F = 1.34
	i.S = "fafdsafdsaf"
	i.UI = 2
	i.IA = []MyInt{1, 3, 5}
	i.UIA = []uint{1, 3}
	i.UIA16 = []uint16{2}
	i.UIA32 = []uint32{4, 5}
	i.UIA64 = []uint64{6, 7, 9}
	i.UIA8 = []uint8{1, 2, 3, 4}
	i.NameArray = []string{"ssss", "fsdf", "lllll, ss"}
	i.MSS = map[string]string{"s": "sfds,ss", "x": "lfjljsl"}

	cnt, err := testEngine.Insert(&i)
	assert.NoError(t, err)
	assert.EqualValues(t, 1, cnt)

	fmt.Println(i)
	i.NameArray = []string{}
	i.MSS = map[string]string{}
	i.F = 0
	has, err := testEngine.Get(&i)
	assert.NoError(t, err)
	assert.True(t, has)

	ss := []MyStruct{}
	err = testEngine.Find(&ss)
	assert.NoError(t, err)
	assert.EqualValues(t, 1, len(ss))
	assert.EqualValues(t, i, ss[0])

	sss := MyStruct{}
	has, err = testEngine.Get(&sss)
	assert.NoError(t, err)
	assert.True(t, has)

	sss.NameArray = []string{}
	sss.MSS = map[string]string{}
	cnt, err = testEngine.Delete(&sss)
	assert.NoError(t, err)
	assert.EqualValues(t, 1, cnt)
}

type Status struct {
	Name  string
	Color string
}

var (
	_          convert.Conversion = &Status{}
	Registered                    = Status{"Registered", "white"}
	Approved                      = Status{"Approved", "green"}
	Removed                       = Status{"Removed", "red"}
	Statuses                      = map[string]Status{
		Registered.Name: Registered,
		Approved.Name:   Approved,
		Removed.Name:    Removed,
	}
)

func (s *Status) FromDB(bytes []byte) error {
	if r, ok := Statuses[string(bytes)]; ok {
		*s = r
		return nil
	}
	return errors.New("no this data")
}

func (s *Status) ToDB() ([]byte, error) {
	return []byte(s.Name), nil
}

type UserCus struct {
	Id     int64
	Name   string
	Status Status `xorm:"varchar(40)"`
}

func TestCustomType2(t *testing.T) {
	assert.NoError(t, PrepareEngine())

	var uc UserCus
	err := testEngine.CreateTables(&uc)
	assert.NoError(t, err)

	tableName := testEngine.TableName(&uc, true)
	_, err = testEngine.Exec("delete from " + testEngine.Quote(tableName))
	assert.NoError(t, err)

	session := testEngine.NewSession()
	defer session.Close()

	cnt, err := session.Insert(&UserCus{1, "xlw", Registered})
	assert.NoError(t, err)
	assert.EqualValues(t, 1, cnt)

	user := UserCus{}
	exist, err := testEngine.ID(1).Get(&user)
	assert.NoError(t, err)
	assert.True(t, exist)

	fmt.Println(user)

	users := make([]UserCus, 0)
	err = testEngine.Where("`"+testEngine.GetColumnMapper().Obj2Table("Status")+"` = ?", "Registered").Find(&users)
	assert.NoError(t, err)
	assert.EqualValues(t, 1, len(users))

	fmt.Println(users)
}

func TestUnsignedUint64(t *testing.T) {
	type MyUnsignedStruct struct {
		Id uint64
	}

	assert.NoError(t, PrepareEngine())
	assertSync(t, new(MyUnsignedStruct))

	tables, err := testEngine.DBMetas()
	assert.NoError(t, err)
	assert.EqualValues(t, 1, len(tables))
	assert.EqualValues(t, 1, len(tables[0].Columns()))

	switch testEngine.Dialect().URI().DBType {
	case schemas.SQLITE:
		assert.EqualValues(t, "INTEGER", tables[0].Columns()[0].SQLType.Name)
	case schemas.MYSQL:
		assert.EqualValues(t, "UNSIGNED BIGINT", tables[0].Columns()[0].SQLType.Name)
	case schemas.POSTGRES:
		assert.EqualValues(t, "BIGINT", tables[0].Columns()[0].SQLType.Name)
	default:
		assert.False(t, true, "Unsigned is not implemented")
	}

	// Only MYSQL database supports unsigned bigint
	if testEngine.Dialect().URI().DBType != schemas.MYSQL {
		return
	}

	cnt, err := testEngine.Insert(&MyUnsignedStruct{
		Id: math.MaxUint64,
	})
	assert.NoError(t, err)
	assert.EqualValues(t, 1, cnt)

	var v MyUnsignedStruct
	has, err := testEngine.Get(&v)
	assert.NoError(t, err)
	assert.True(t, has)
	assert.EqualValues(t, uint64(math.MaxUint64), v.Id)
}

func TestUnsignedUint32(t *testing.T) {
	type MyUnsignedInt32Struct struct {
		Id uint32
	}

	assert.NoError(t, PrepareEngine())
	assertSync(t, new(MyUnsignedInt32Struct))

	tables, err := testEngine.DBMetas()
	assert.NoError(t, err)
	assert.EqualValues(t, 1, len(tables))
	assert.EqualValues(t, 1, len(tables[0].Columns()))

	switch testEngine.Dialect().URI().DBType {
	case schemas.SQLITE:
		assert.EqualValues(t, "INTEGER", tables[0].Columns()[0].SQLType.Name)
	case schemas.MYSQL:
		assert.EqualValues(t, "UNSIGNED INT", tables[0].Columns()[0].SQLType.Name)
	case schemas.POSTGRES:
		assert.EqualValues(t, "BIGINT", tables[0].Columns()[0].SQLType.Name)
	default:
		assert.False(t, true, "Unsigned is not implemented")
	}

	cnt, err := testEngine.Insert(&MyUnsignedInt32Struct{
		Id: math.MaxUint32,
	})
	assert.NoError(t, err)
	assert.EqualValues(t, 1, cnt)

	var v MyUnsignedInt32Struct
	has, err := testEngine.Get(&v)
	assert.NoError(t, err)
	assert.True(t, has)
	assert.EqualValues(t, uint64(math.MaxUint32), v.Id)
}

func TestUnsignedTinyInt(t *testing.T) {
	type MyUnsignedTinyIntStruct struct {
		Id uint8 `xorm:"unsigned tinyint"`
	}

	assert.NoError(t, PrepareEngine())
	assertSync(t, new(MyUnsignedTinyIntStruct))

	tables, err := testEngine.DBMetas()
	assert.NoError(t, err)
	assert.EqualValues(t, 1, len(tables))
	assert.EqualValues(t, 1, len(tables[0].Columns()))

	switch testEngine.Dialect().URI().DBType {
	case schemas.SQLITE:
		assert.EqualValues(t, "INTEGER", tables[0].Columns()[0].SQLType.Name)
	case schemas.MYSQL:
		assert.EqualValues(t, "UNSIGNED TINYINT", tables[0].Columns()[0].SQLType.Name)
	case schemas.POSTGRES:
		assert.EqualValues(t, "SMALLINT", tables[0].Columns()[0].SQLType.Name)
	default:
		assert.False(t, true, fmt.Sprintf("Unsigned is not implemented, returned %s", tables[0].Columns()[0].SQLType.Name))
	}

	cnt, err := testEngine.Insert(&MyUnsignedTinyIntStruct{
		Id: math.MaxUint8,
	})
	assert.NoError(t, err)
	assert.EqualValues(t, 1, cnt)

	var v MyUnsignedTinyIntStruct
	has, err := testEngine.Get(&v)
	assert.NoError(t, err)
	assert.True(t, has)
	assert.EqualValues(t, math.MaxUint8, v.Id)
}

type MyDecimal big.Int

func (d *MyDecimal) FromDB(data []byte) error {
	i, _ := strconv.ParseInt(string(data), 10, 64)
	(*big.Int)(d).SetInt64(i)
	return nil
}

func (d *MyDecimal) ToDB() ([]byte, error) {
	return fmt.Appendf(nil, "%d", (*big.Int)(d).Int64()), nil
}

func (d *MyDecimal) AsBigInt() *big.Int {
	return (*big.Int)(d)
}

func (d *MyDecimal) AsInt64() int64 {
	return d.AsBigInt().Int64()
}

func TestDecimal(t *testing.T) {
	type MyMoney struct {
		Id      int64
		Account *MyDecimal
	}

	assert.NoError(t, PrepareEngine())
	assertSync(t, new(MyMoney))

	_, err := testEngine.Insert(&MyMoney{
		Account: (*MyDecimal)(big.NewInt(10000000000000000)),
	})
	assert.NoError(t, err)

	var m MyMoney
	has, err := testEngine.Get(&m)
	assert.NoError(t, err)
	assert.True(t, has)
	assert.NotNil(t, m.Account)
	assert.EqualValues(t, 10000000000000000, m.Account.AsInt64())
}

type MyArray [20]byte

func (d *MyArray) FromDB(data []byte) error {
	copy((*d)[:], data[:20])
	return nil
}

func (d MyArray) ToDB() ([]byte, error) {
	return d[:], nil
}

func TestMyArray(t *testing.T) {
	type MyArrayStruct struct {
		Id      int64
		Content MyArray
	}

	assert.NoError(t, PrepareEngine())
	assertSync(t, new(MyArrayStruct))

	v := [20]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
	_, err := testEngine.Insert(&MyArrayStruct{
		Content: v,
	})
	assert.NoError(t, err)

	var m MyArrayStruct
	has, err := testEngine.Get(&m)
	assert.NoError(t, err)
	assert.True(t, has)
	assert.EqualValues(t, v, m.Content)
}

type ZDecimal struct {
	value *big.Int
}

func (d *ZDecimal) FromDB(data []byte) error {
	i, _ := strconv.ParseInt(string(data), 10, 64)
	*d = ZDecimal{
		value: big.NewInt(i),
	}
	return nil
}

func (d ZDecimal) ToDB() ([]byte, error) {
	return fmt.Appendf(nil, "%d", d.value.Int64()), nil
}

func (d ZDecimal) IsZero() bool {
	if d.value == nil {
		return true
	}
	return d.value.Sign() == 0
}

func (d ZDecimal) String() string {
	if d.value == nil {
		return "0"
	}
	return d.value.String()
}

func TestZDecimal(t *testing.T) {
	type ZMyMoney struct {
		Id      int64
		Account string
		Amount  ZDecimal
	}

	assert.NoError(t, PrepareEngine())
	assertSync(t, new(ZMyMoney))

	_, err := testEngine.Insert(&ZMyMoney{
		Account: "test",
		Amount: ZDecimal{
			value: big.NewInt(10000000000000000),
		},
	})
	assert.NoError(t, err)

	m := ZMyMoney{
		Id: 1,
	}
	has, err := testEngine.Get(&m)
	assert.NoError(t, err)
	assert.True(t, has)

	_, err = testEngine.Update(&ZMyMoney{
		Id:      1,
		Account: "test2",
	})
	assert.NoError(t, err)
	m2 := ZMyMoney{
		Id: 1,
	}
	has, err = testEngine.Get(&m2)

	assert.NoError(t, err)
	assert.True(t, has)
	assert.Equal(t, "test2", "test2")
	assert.False(t, m.Amount.IsZero())
	assert.Equal(t, "10000000000000000", m.Amount.String())
}

// Simplified version of forgejo/modules/optional/Option[T]
type Option[T any] []T

func None[T any]() Option[T] {
	return nil
}

func Some[T any](v T) Option[T] {
	return Option[T]{v}
}

func (o Option[T]) Get() (has bool, value T) {
	if o != nil {
		has = true
		value = o[0]
	}
	return has, value
}

var (
	_ sql.Scanner              = (*Option[bool])(nil)
	_ driver.Valuer            = None[bool]()
	_ schemas.SQLTypeDelegator = None[bool]()
)

func (o *Option[T]) Scan(value any) error {
	var n sql.Null[T]
	if err := n.Scan(value); err != nil {
		return err
	}
	if n.Valid {
		*o = Some(n.V)
	} else {
		*o = None[T]()
	}
	return nil
}

func (o Option[T]) Value() (driver.Value, error) {
	var n sql.Null[T]
	if has, value := o.Get(); has {
		n.V = value
		n.Valid = true
	} else {
		n.Valid = false
	}
	return n.Value()
}

func (o Option[T]) DelegateSQLType() reflect.Type {
	return reflect.TypeFor[T]()
}

func TestOptionFieldInt(t *testing.T) {
	assert.NoError(t, PrepareEngine())

	type OptionInt struct {
		Id     int64 `xorm:"pk autoincr"`
		Number Option[int64]
	}
	assert.NoError(t, testEngine.Sync(new(OptionInt)))

	t.Run("insert null, read back", func(t *testing.T) {
		null := OptionInt{
			Number: None[int64](),
		}
		cnt, err := testEngine.Insert(&null)
		require.NoError(t, err)
		assert.EqualValues(t, 1, cnt)

		{
			var read OptionInt
			has, err := testEngine.ID(null.Id).Get(&read)
			require.NoError(t, err)
			require.Equal(t, true, has)
			hasNumber, _ := read.Number.Get()
			assert.False(t, hasNumber)
		}
	})

	t.Run("insert not null, read back", func(t *testing.T) {
		notNull := OptionInt{
			Number: Some[int64](123),
		}

		cnt, err := testEngine.Insert(&notNull)
		require.NoError(t, err)
		assert.EqualValues(t, 1, cnt)
		{
			var read OptionInt
			has, err := testEngine.ID(notNull.Id).Get(&read)
			require.NoError(t, err)
			require.Equal(t, true, has)
			hasNumber, number := read.Number.Get()
			assert.True(t, hasNumber)
			assert.EqualValues(t, 123, number)
		}
	})

	t.Run("read multiple records without filters", func(t *testing.T) {
		var arr []OptionInt
		err := testEngine.Find(&arr)
		assert.NoError(t, err)
		assert.Len(t, arr, 2)
	})

	t.Run("read multiple records with bean filters", func(t *testing.T) {
		var arr []OptionInt
		cond := &OptionInt{
			Number: Some[int64](123),
		}
		err := testEngine.Find(&arr, cond)
		require.NoError(t, err)
		require.Len(t, arr, 1)
		v := arr[0]
		has, value := v.Number.Get()
		assert.True(t, has)
		assert.EqualValues(t, 123, value)
	})
}

func TestOptionFieldString(t *testing.T) {
	assert.NoError(t, PrepareEngine())

	type OptionString struct {
		Id     int64 `xorm:"pk autoincr"`
		String Option[string]
	}
	assert.NoError(t, testEngine.Sync(new(OptionString)))

	t.Run("insert null, read back", func(t *testing.T) {
		null := OptionString{
			String: None[string](),
		}
		cnt, err := testEngine.Insert(&null)
		require.NoError(t, err)
		assert.EqualValues(t, 1, cnt)

		{
			var read OptionString
			has, err := testEngine.ID(null.Id).Get(&read)
			require.NoError(t, err)
			require.Equal(t, true, has)
			hasString, _ := read.String.Get()
			assert.False(t, hasString)
		}
	})

	t.Run("insert not null, read back", func(t *testing.T) {
		notNull := OptionString{
			String: Some("hello"),
		}

		cnt, err := testEngine.Insert(&notNull)
		require.NoError(t, err)
		assert.EqualValues(t, 1, cnt)
		{
			var read OptionString
			has, err := testEngine.ID(notNull.Id).Get(&read)
			require.NoError(t, err)
			require.Equal(t, true, has)
			hasString, str := read.String.Get()
			assert.True(t, hasString)
			assert.Equal(t, "hello", str)
		}
	})

	t.Run("read multiple records without filters", func(t *testing.T) {
		var arr []OptionString
		err := testEngine.Find(&arr)
		assert.NoError(t, err)
		assert.Len(t, arr, 2)
	})

	t.Run("read multiple records with bean filters", func(t *testing.T) {
		var arr []OptionString
		cond := &OptionString{
			String: Some("hello"),
		}
		err := testEngine.Find(&arr, cond)
		require.NoError(t, err)
		require.Len(t, arr, 1)
		v := arr[0]
		has, value := v.String.Get()
		assert.True(t, has)
		assert.EqualValues(t, "hello", value)
	})
}

func TestOptionFieldIntrospection(t *testing.T) {
	assert.NoError(t, PrepareEngine())

	type OptionIntrospectInt struct {
		Id           int64 `xorm:"pk autoincr"`
		OptionNumber Option[int64]
		NormalNumber int64
	}
	assert.NoError(t, testEngine.Sync(new(OptionIntrospectInt)))

	schema, err := testEngine.TableInfo(&OptionIntrospectInt{})
	require.NoError(t, err)

	var optionColumn, normalColumn *schemas.Column
	for _, c := range schema.Columns() {
		switch c.Name {
		case "option_number":
			optionColumn = c
		case "normal_number":
			normalColumn = c
		}
	}
	require.NotNil(t, optionColumn)
	require.NotNil(t, normalColumn)

	assert.Equal(t, normalColumn.TableName, optionColumn.TableName, "field TableName")
	assert.Equal(t, normalColumn.SQLType, optionColumn.SQLType, "field SQLType")
	assert.Equal(t, normalColumn.IsJSON, optionColumn.IsJSON, "field IsJSON")
	assert.Equal(t, normalColumn.Length, optionColumn.Length, "field Length")
	assert.Equal(t, normalColumn.Length2, optionColumn.Length2, "field Length2")
	assert.Equal(t, normalColumn.Nullable, optionColumn.Nullable, "field Nullable")
	assert.Equal(t, normalColumn.Default, optionColumn.Default, "field Default")
	assert.Equal(t, normalColumn.Indexes, optionColumn.Indexes, "field Indexes")
	assert.Equal(t, normalColumn.IsPrimaryKey, optionColumn.IsPrimaryKey, "field IsPrimaryKey")
	assert.Equal(t, normalColumn.IsAutoIncrement, optionColumn.IsAutoIncrement, "field IsAutoIncrement")
	assert.Equal(t, normalColumn.MapType, optionColumn.MapType, "field MapType")
	assert.Equal(t, normalColumn.IsCreated, optionColumn.IsCreated, "field IsCreated")
	assert.Equal(t, normalColumn.IsUpdated, optionColumn.IsUpdated, "field IsUpdated")
	assert.Equal(t, normalColumn.IsDeleted, optionColumn.IsDeleted, "field IsDeleted")
	assert.Equal(t, normalColumn.IsCascade, optionColumn.IsCascade, "field IsCascade")
	assert.Equal(t, normalColumn.IsVersion, optionColumn.IsVersion, "field IsVersion")
	assert.Equal(t, normalColumn.DefaultIsEmpty, optionColumn.DefaultIsEmpty, "field DefaultIsEmpty")
	assert.Equal(t, normalColumn.EnumOptions, optionColumn.EnumOptions, "field EnumOptions")
	assert.Equal(t, normalColumn.SetOptions, optionColumn.SetOptions, "field SetOptions")
	assert.Equal(t, normalColumn.DisableTimeZone, optionColumn.DisableTimeZone, "field DisableTimeZone")
	assert.Equal(t, normalColumn.TimeZone, optionColumn.TimeZone, "field TimeZone")
	assert.Equal(t, normalColumn.Comment, optionColumn.Comment, "field Comment")
	assert.Equal(t, normalColumn.Collation, optionColumn.Collation, "field Collation")
}
