554 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			554 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2014 beego Author. All Rights Reserved.
 | 
						|
//
 | 
						|
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
// you may not use this file except in compliance with the License.
 | 
						|
// You may obtain a copy of the License at
 | 
						|
//
 | 
						|
//      http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
//
 | 
						|
// Unless required by applicable law or agreed to in writing, software
 | 
						|
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
// See the License for the specific language governing permissions and
 | 
						|
// limitations under the License.
 | 
						|
 | 
						|
package orm
 | 
						|
 | 
						|
import (
 | 
						|
	"database/sql"
 | 
						|
	"encoding/json"
 | 
						|
	"fmt"
 | 
						|
	"os"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	_ "github.com/go-sql-driver/mysql"
 | 
						|
	_ "github.com/lib/pq"
 | 
						|
	_ "github.com/mattn/go-sqlite3"
 | 
						|
)
 | 
						|
 | 
						|
// A slice string field.
 | 
						|
type SliceStringField []string
 | 
						|
 | 
						|
func (e SliceStringField) Value() []string {
 | 
						|
	return []string(e)
 | 
						|
}
 | 
						|
 | 
						|
func (e *SliceStringField) Set(d []string) {
 | 
						|
	*e = SliceStringField(d)
 | 
						|
}
 | 
						|
 | 
						|
func (e *SliceStringField) Add(v string) {
 | 
						|
	*e = append(*e, v)
 | 
						|
}
 | 
						|
 | 
						|
func (e *SliceStringField) String() string {
 | 
						|
	return strings.Join(e.Value(), ",")
 | 
						|
}
 | 
						|
 | 
						|
func (e *SliceStringField) FieldType() int {
 | 
						|
	return TypeVarCharField
 | 
						|
}
 | 
						|
 | 
						|
func (e *SliceStringField) SetRaw(value interface{}) error {
 | 
						|
	f := func(str string) {
 | 
						|
		if len(str) > 0 {
 | 
						|
			parts := strings.Split(str, ",")
 | 
						|
			v := make([]string, 0, len(parts))
 | 
						|
			for _, p := range parts {
 | 
						|
				v = append(v, strings.TrimSpace(p))
 | 
						|
			}
 | 
						|
			e.Set(v)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	switch d := value.(type) {
 | 
						|
	case []string:
 | 
						|
		e.Set(d)
 | 
						|
	case string:
 | 
						|
		f(d)
 | 
						|
	case []byte:
 | 
						|
		f(string(d))
 | 
						|
	default:
 | 
						|
		return fmt.Errorf("<SliceStringField.SetRaw> unknown value `%v`", value)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (e *SliceStringField) RawValue() interface{} {
 | 
						|
	return e.String()
 | 
						|
}
 | 
						|
 | 
						|
var _ Fielder = new(SliceStringField)
 | 
						|
 | 
						|
// A json field.
 | 
						|
type JSONFieldTest struct {
 | 
						|
	Name string
 | 
						|
	Data string
 | 
						|
}
 | 
						|
 | 
						|
func (e *JSONFieldTest) String() string {
 | 
						|
	data, _ := json.Marshal(e)
 | 
						|
	return string(data)
 | 
						|
}
 | 
						|
 | 
						|
func (e *JSONFieldTest) FieldType() int {
 | 
						|
	return TypeTextField
 | 
						|
}
 | 
						|
 | 
						|
func (e *JSONFieldTest) SetRaw(value interface{}) error {
 | 
						|
	switch d := value.(type) {
 | 
						|
	case string:
 | 
						|
		return json.Unmarshal([]byte(d), e)
 | 
						|
	case []byte:
 | 
						|
		return json.Unmarshal(d, e)
 | 
						|
	default:
 | 
						|
		return fmt.Errorf("<JSONField.SetRaw> unknown value `%v`", value)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (e *JSONFieldTest) RawValue() interface{} {
 | 
						|
	return e.String()
 | 
						|
}
 | 
						|
 | 
						|
var _ Fielder = new(JSONFieldTest)
 | 
						|
 | 
						|
type Data struct {
 | 
						|
	ID       int `orm:"column(id)"`
 | 
						|
	Boolean  bool
 | 
						|
	Byte     byte
 | 
						|
	Int8     int8
 | 
						|
	Uint8    uint8
 | 
						|
	Rune     rune
 | 
						|
	Char     string    `orm:"size(50)"`
 | 
						|
	Text     string    `orm:"type(text)"`
 | 
						|
	JSON     string    `orm:"type(json);default({\"name\":\"json\"})"`
 | 
						|
	Jsonb    string    `orm:"type(jsonb)"`
 | 
						|
	Time     time.Time `orm:"type(time)"`
 | 
						|
	Date     time.Time `orm:"type(date)"`
 | 
						|
	DateTime time.Time `orm:"column(datetime)"`
 | 
						|
	Int      int
 | 
						|
	Uint     uint
 | 
						|
	Int16    int16
 | 
						|
	Uint16   uint16
 | 
						|
	Int32    int32
 | 
						|
	Int64    int64
 | 
						|
	Uint32   uint32
 | 
						|
	Float32  float32
 | 
						|
	Uint64   uint64
 | 
						|
	Float64  float64
 | 
						|
	Decimal  float64 `orm:"digits(8);decimals(4)"`
 | 
						|
}
 | 
						|
 | 
						|
type DataNull struct {
 | 
						|
	ID                int             `orm:"column(id)"`
 | 
						|
	Char              string          `orm:"null;size(50)"`
 | 
						|
	Text              string          `orm:"null;type(text)"`
 | 
						|
	JSON              string          `orm:"type(json);null"`
 | 
						|
	Jsonb             string          `orm:"type(jsonb);null"`
 | 
						|
	Time              time.Time       `orm:"null;type(time)"`
 | 
						|
	Date              time.Time       `orm:"null;type(date)"`
 | 
						|
	DateTime          time.Time       `orm:"null;column(datetime)"`
 | 
						|
	DateTimePrecision time.Time       `orm:"null;type(datetime);precision(4)"`
 | 
						|
	Boolean           bool            `orm:"null"`
 | 
						|
	Byte              byte            `orm:"null"`
 | 
						|
	Int8              int8            `orm:"null"`
 | 
						|
	Uint8             uint8           `orm:"null"`
 | 
						|
	Rune              rune            `orm:"null"`
 | 
						|
	Int               int             `orm:"null"`
 | 
						|
	Uint              uint            `orm:"null"`
 | 
						|
	Int16             int16           `orm:"null"`
 | 
						|
	Uint16            uint16          `orm:"null"`
 | 
						|
	Int32             int32           `orm:"null"`
 | 
						|
	Int64             int64           `orm:"null"`
 | 
						|
	Uint32            uint32          `orm:"null"`
 | 
						|
	Float32           float32         `orm:"null"`
 | 
						|
	Uint64            uint64          `orm:"null"`
 | 
						|
	Float64           float64         `orm:"null"`
 | 
						|
	Decimal           float64         `orm:"digits(8);decimals(4);null"`
 | 
						|
	NullString        sql.NullString  `orm:"null"`
 | 
						|
	NullBool          sql.NullBool    `orm:"null"`
 | 
						|
	NullFloat64       sql.NullFloat64 `orm:"null"`
 | 
						|
	NullInt64         sql.NullInt64   `orm:"null"`
 | 
						|
	BooleanPtr        *bool           `orm:"null"`
 | 
						|
	CharPtr           *string         `orm:"null;size(50)"`
 | 
						|
	TextPtr           *string         `orm:"null;type(text)"`
 | 
						|
	BytePtr           *byte           `orm:"null"`
 | 
						|
	RunePtr           *rune           `orm:"null"`
 | 
						|
	IntPtr            *int            `orm:"null"`
 | 
						|
	Int8Ptr           *int8           `orm:"null"`
 | 
						|
	Int16Ptr          *int16          `orm:"null"`
 | 
						|
	Int32Ptr          *int32          `orm:"null"`
 | 
						|
	Int64Ptr          *int64          `orm:"null"`
 | 
						|
	UintPtr           *uint           `orm:"null"`
 | 
						|
	Uint8Ptr          *uint8          `orm:"null"`
 | 
						|
	Uint16Ptr         *uint16         `orm:"null"`
 | 
						|
	Uint32Ptr         *uint32         `orm:"null"`
 | 
						|
	Uint64Ptr         *uint64         `orm:"null"`
 | 
						|
	Float32Ptr        *float32        `orm:"null"`
 | 
						|
	Float64Ptr        *float64        `orm:"null"`
 | 
						|
	DecimalPtr        *float64        `orm:"digits(8);decimals(4);null"`
 | 
						|
	TimePtr           *time.Time      `orm:"null;type(time)"`
 | 
						|
	DatePtr           *time.Time      `orm:"null;type(date)"`
 | 
						|
	DateTimePtr       *time.Time      `orm:"null"`
 | 
						|
}
 | 
						|
 | 
						|
type (
 | 
						|
	String  string
 | 
						|
	Boolean bool
 | 
						|
	Byte    byte
 | 
						|
	Rune    rune
 | 
						|
	Int     int
 | 
						|
	Int8    int8
 | 
						|
	Int16   int16
 | 
						|
	Int32   int32
 | 
						|
	Int64   int64
 | 
						|
	Uint    uint
 | 
						|
	Uint8   uint8
 | 
						|
	Uint16  uint16
 | 
						|
	Uint32  uint32
 | 
						|
	Uint64  uint64
 | 
						|
	Float32 float64
 | 
						|
	Float64 float64
 | 
						|
)
 | 
						|
 | 
						|
type DataCustom struct {
 | 
						|
	ID      int `orm:"column(id)"`
 | 
						|
	Boolean Boolean
 | 
						|
	Byte    Byte
 | 
						|
	Int8    Int8
 | 
						|
	Uint8   Uint8
 | 
						|
	Rune    Rune
 | 
						|
	Char    string `orm:"size(50)"`
 | 
						|
	Text    string `orm:"type(text)"`
 | 
						|
	Int     Int
 | 
						|
	Uint    Uint
 | 
						|
	Int16   Int16
 | 
						|
	Uint16  Uint16
 | 
						|
	Int32   Int32
 | 
						|
	Int64   Int64
 | 
						|
	Uint32  Uint32
 | 
						|
	Float32 Float32
 | 
						|
	Uint64  Uint64
 | 
						|
	Float64 Float64
 | 
						|
	Decimal Float64 `orm:"digits(8);decimals(4)"`
 | 
						|
}
 | 
						|
 | 
						|
// only for mysql
 | 
						|
type UserBig struct {
 | 
						|
	ID   uint64 `orm:"column(id)"`
 | 
						|
	Name string
 | 
						|
}
 | 
						|
 | 
						|
type TM struct {
 | 
						|
	ID           int       `orm:"column(id)"`
 | 
						|
	TMPrecision1 time.Time `orm:"type(datetime);precision(3)"`
 | 
						|
	TMPrecision2 time.Time `orm:"auto_now_add;type(datetime);precision(4)"`
 | 
						|
}
 | 
						|
 | 
						|
func (t *TM) TableName() string {
 | 
						|
	return "tm"
 | 
						|
}
 | 
						|
 | 
						|
func NewTM() *TM {
 | 
						|
	obj := new(TM)
 | 
						|
	return obj
 | 
						|
}
 | 
						|
 | 
						|
type DeptInfo struct {
 | 
						|
	ID           int       `orm:"column(id)"`
 | 
						|
	Created      time.Time `orm:"auto_now_add"`
 | 
						|
	DeptName     string
 | 
						|
	EmployeeName string
 | 
						|
	Salary       int
 | 
						|
}
 | 
						|
 | 
						|
type UnregisterModel struct {
 | 
						|
	ID           int       `orm:"column(id)"`
 | 
						|
	Created      time.Time `orm:"auto_now_add"`
 | 
						|
	DeptName     string
 | 
						|
	EmployeeName string
 | 
						|
	Salary       int
 | 
						|
}
 | 
						|
 | 
						|
type User struct {
 | 
						|
	ID             int    `orm:"column(id)"`
 | 
						|
	UserName       string `orm:"size(30);unique"`
 | 
						|
	Email          string `orm:"size(100)"`
 | 
						|
	Password       string `orm:"size(100)"`
 | 
						|
	Status         int16  `orm:"column(Status)"`
 | 
						|
	IsStaff        bool
 | 
						|
	IsActive       bool `orm:"default(true)"`
 | 
						|
	Unexported     bool `orm:"-"`
 | 
						|
	UnexportedBool bool
 | 
						|
	Created        time.Time `orm:"auto_now_add;type(date)"`
 | 
						|
	Updated        time.Time `orm:"auto_now"`
 | 
						|
	Profile        *Profile  `orm:"null;rel(one);on_delete(set_null)"`
 | 
						|
	Posts          []*Post   `orm:"reverse(many)" json:"-"`
 | 
						|
	ShouldSkip     string    `orm:"-"`
 | 
						|
	Nums           int
 | 
						|
	Langs          SliceStringField `orm:"size(100)"`
 | 
						|
	Extra          JSONFieldTest    `orm:"type(text)"`
 | 
						|
}
 | 
						|
 | 
						|
func (u *User) TableIndex() [][]string {
 | 
						|
	return [][]string{
 | 
						|
		{"Id", "UserName"},
 | 
						|
		{"Id", "Created"},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (u *User) TableUnique() [][]string {
 | 
						|
	return [][]string{
 | 
						|
		{"UserName", "Email"},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func NewUser() *User {
 | 
						|
	obj := new(User)
 | 
						|
	return obj
 | 
						|
}
 | 
						|
 | 
						|
type Profile struct {
 | 
						|
	ID       int `orm:"column(id)"`
 | 
						|
	Age      int16
 | 
						|
	Money    float64
 | 
						|
	User     *User `orm:"reverse(one)" json:"-"`
 | 
						|
	BestPost *Post `orm:"rel(one);null"`
 | 
						|
}
 | 
						|
 | 
						|
func (u *Profile) TableName() string {
 | 
						|
	return "user_profile"
 | 
						|
}
 | 
						|
 | 
						|
func NewProfile() *Profile {
 | 
						|
	obj := new(Profile)
 | 
						|
	return obj
 | 
						|
}
 | 
						|
 | 
						|
type Post struct {
 | 
						|
	ID               int       `orm:"column(id)"`
 | 
						|
	User             *User     `orm:"rel(fk)"`
 | 
						|
	Title            string    `orm:"size(60)"`
 | 
						|
	Content          string    `orm:"type(text)"`
 | 
						|
	Created          time.Time `orm:"auto_now_add"`
 | 
						|
	Updated          time.Time `orm:"auto_now"`
 | 
						|
	UpdatedPrecision time.Time `orm:"auto_now;type(datetime);precision(4)"`
 | 
						|
	Tags             []*Tag    `orm:"rel(m2m);rel_through(github.com/beego/beego/v2/client/orm.PostTags)"`
 | 
						|
}
 | 
						|
 | 
						|
func (u *Post) TableIndex() [][]string {
 | 
						|
	return [][]string{
 | 
						|
		{"Id", "Created"},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func NewPost() *Post {
 | 
						|
	obj := new(Post)
 | 
						|
	return obj
 | 
						|
}
 | 
						|
 | 
						|
type Tag struct {
 | 
						|
	ID       int     `orm:"column(id)"`
 | 
						|
	Name     string  `orm:"size(30)"`
 | 
						|
	BestPost *Post   `orm:"rel(one);null"`
 | 
						|
	Posts    []*Post `orm:"reverse(many)" json:"-"`
 | 
						|
}
 | 
						|
 | 
						|
func NewTag() *Tag {
 | 
						|
	obj := new(Tag)
 | 
						|
	return obj
 | 
						|
}
 | 
						|
 | 
						|
type PostTags struct {
 | 
						|
	ID   int   `orm:"column(id)"`
 | 
						|
	Post *Post `orm:"rel(fk)"`
 | 
						|
	Tag  *Tag  `orm:"rel(fk)"`
 | 
						|
}
 | 
						|
 | 
						|
func (m *PostTags) TableName() string {
 | 
						|
	return "prefix_post_tags"
 | 
						|
}
 | 
						|
 | 
						|
type Comment struct {
 | 
						|
	ID      int       `orm:"column(id)"`
 | 
						|
	Post    *Post     `orm:"rel(fk);column(post)"`
 | 
						|
	Content string    `orm:"type(text)"`
 | 
						|
	Parent  *Comment  `orm:"null;rel(fk)"`
 | 
						|
	Created time.Time `orm:"auto_now_add"`
 | 
						|
}
 | 
						|
 | 
						|
func NewComment() *Comment {
 | 
						|
	obj := new(Comment)
 | 
						|
	return obj
 | 
						|
}
 | 
						|
 | 
						|
type Group struct {
 | 
						|
	ID          int `orm:"column(gid);size(32)"`
 | 
						|
	Name        string
 | 
						|
	Permissions []*Permission `orm:"reverse(many)" json:"-"`
 | 
						|
}
 | 
						|
 | 
						|
type Permission struct {
 | 
						|
	ID     int `orm:"column(id)"`
 | 
						|
	Name   string
 | 
						|
	Groups []*Group `orm:"rel(m2m);rel_through(github.com/beego/beego/v2/client/orm.GroupPermissions)"`
 | 
						|
}
 | 
						|
 | 
						|
type GroupPermissions struct {
 | 
						|
	ID         int         `orm:"column(id)"`
 | 
						|
	Group      *Group      `orm:"rel(fk)"`
 | 
						|
	Permission *Permission `orm:"rel(fk)"`
 | 
						|
}
 | 
						|
 | 
						|
type ModelID struct {
 | 
						|
	ID int64
 | 
						|
}
 | 
						|
 | 
						|
type ModelBase struct {
 | 
						|
	ModelID
 | 
						|
 | 
						|
	Created time.Time `orm:"auto_now_add;type(datetime)"`
 | 
						|
	Updated time.Time `orm:"auto_now;type(datetime)"`
 | 
						|
}
 | 
						|
 | 
						|
type InLine struct {
 | 
						|
	// Common Fields
 | 
						|
	ModelBase
 | 
						|
 | 
						|
	// Other Fields
 | 
						|
	Name  string `orm:"unique"`
 | 
						|
	Email string
 | 
						|
}
 | 
						|
 | 
						|
type Index struct {
 | 
						|
	// Common Fields
 | 
						|
	Id int `orm:"column(id)"`
 | 
						|
 | 
						|
	// Other Fields
 | 
						|
	F1 int `orm:"column(f1);index"`
 | 
						|
	F2 int `orm:"column(f2);index"`
 | 
						|
}
 | 
						|
 | 
						|
func NewInLine() *InLine {
 | 
						|
	return new(InLine)
 | 
						|
}
 | 
						|
 | 
						|
type InLineOneToOne struct {
 | 
						|
	// Common Fields
 | 
						|
	ModelBase
 | 
						|
 | 
						|
	Note   string
 | 
						|
	InLine *InLine `orm:"rel(fk);column(inline)"`
 | 
						|
}
 | 
						|
 | 
						|
func NewInLineOneToOne() *InLineOneToOne {
 | 
						|
	return new(InLineOneToOne)
 | 
						|
}
 | 
						|
 | 
						|
type IntegerPk struct {
 | 
						|
	ID    int64 `orm:"pk"`
 | 
						|
	Value string
 | 
						|
}
 | 
						|
 | 
						|
type UintPk struct {
 | 
						|
	ID   uint32 `orm:"pk"`
 | 
						|
	Name string
 | 
						|
}
 | 
						|
 | 
						|
type PtrPk struct {
 | 
						|
	ID       *IntegerPk `orm:"pk;rel(one)"`
 | 
						|
	Positive bool
 | 
						|
}
 | 
						|
 | 
						|
type StrPk struct {
 | 
						|
	Id    string `orm:"column(id);size(64);pk"`
 | 
						|
	Value string
 | 
						|
}
 | 
						|
 | 
						|
var DBARGS = struct {
 | 
						|
	Driver string
 | 
						|
	Source string
 | 
						|
	Debug  string
 | 
						|
}{
 | 
						|
	os.Getenv("ORM_DRIVER"),
 | 
						|
	os.Getenv("ORM_SOURCE"),
 | 
						|
	os.Getenv("ORM_DEBUG"),
 | 
						|
}
 | 
						|
 | 
						|
var (
 | 
						|
	IsMysql    = DBARGS.Driver == "mysql"
 | 
						|
	IsSqlite   = DBARGS.Driver == "sqlite3"
 | 
						|
	IsPostgres = DBARGS.Driver == "postgres"
 | 
						|
	IsTidb     = DBARGS.Driver == "tidb"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	dORM     Ormer
 | 
						|
	dDbBaser dbBaser
 | 
						|
)
 | 
						|
 | 
						|
var helpinfo = `need driver and source!
 | 
						|
 | 
						|
	Default DB Drivers.
 | 
						|
 | 
						|
	  driver: url
 | 
						|
	   mysql: https://github.com/go-sql-driver/mysql
 | 
						|
	 sqlite3: https://github.com/mattn/go-sqlite3
 | 
						|
	postgres: https://github.com/lib/pq
 | 
						|
	tidb: https://github.com/pingcap/tidb
 | 
						|
 | 
						|
	usage:
 | 
						|
 | 
						|
	go get -u github.com/beego/beego/v2/client/orm
 | 
						|
	go get -u github.com/go-sql-driver/mysql
 | 
						|
	go get -u github.com/mattn/go-sqlite3
 | 
						|
	go get -u github.com/lib/pq
 | 
						|
	go get -u github.com/pingcap/tidb
 | 
						|
 | 
						|
	#### MySQL
 | 
						|
	mysql -u root -e 'create database orm_test;'
 | 
						|
	export ORM_DRIVER=mysql
 | 
						|
	export ORM_SOURCE="root:@/orm_test?charset=utf8"
 | 
						|
	go test -v github.com/beego/beego/v2/client/orm
 | 
						|
 | 
						|
 | 
						|
	#### Sqlite3
 | 
						|
	export ORM_DRIVER=sqlite3
 | 
						|
	export ORM_SOURCE='file:memory_test?mode=memory'
 | 
						|
	go test -v github.com/beego/beego/v2/client/orm
 | 
						|
 | 
						|
 | 
						|
	#### PostgreSQL
 | 
						|
	psql -c 'create database orm_test;' -U postgres
 | 
						|
	export ORM_DRIVER=postgres
 | 
						|
	export ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
 | 
						|
	go test -v github.com/beego/beego/v2/client/orm
 | 
						|
 | 
						|
	#### TiDB
 | 
						|
	export ORM_DRIVER=tidb
 | 
						|
	export ORM_SOURCE='memory://test/test'
 | 
						|
	go test -v github.com/beego/beego/v2/pgk/orm
 | 
						|
 | 
						|
	`
 | 
						|
 | 
						|
func init() {
 | 
						|
	// Debug, _ = StrTo(DBARGS.Debug).Bool()
 | 
						|
	Debug = true
 | 
						|
 | 
						|
	if DBARGS.Driver == "" || DBARGS.Source == "" {
 | 
						|
		fmt.Println(helpinfo)
 | 
						|
		os.Exit(2)
 | 
						|
	}
 | 
						|
 | 
						|
	err := RegisterDataBase("default", DBARGS.Driver, DBARGS.Source, MaxIdleConnections(20))
 | 
						|
	if err != nil {
 | 
						|
		panic(fmt.Sprintf("can not register database: %v", err))
 | 
						|
	}
 | 
						|
 | 
						|
	alias := getDbAlias("default")
 | 
						|
	if alias.Driver == DRMySQL {
 | 
						|
		alias.Engine = "INNODB"
 | 
						|
	}
 | 
						|
}
 |