| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435 |
- // Copyright 2019 Yunion
- //
- // 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 sqlchemy
- import (
- "fmt"
- "reflect"
- "sort"
- "sync"
- "yunion.io/x/log"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/utils"
- )
- // ITableSpec is the interface represents a table
- type ITableSpec interface {
- // Insert performs an insert operation that insert one record at a time
- Insert(dt interface{}) error
- // InsertOrUpdate performs an atomic insert or update operation that insert a new record to update the record with current value
- InsertOrUpdate(dt interface{}) error
- // Update performs an update operation
- Update(dt interface{}, onUpdate func() error) (UpdateDiffs, error)
- // Increment performs a special update that do an atomic incremental update of the numeric fields
- Increment(diff, target interface{}) error
- // Decrement performs a special update that do an atomic decremental update of the numeric fields
- Decrement(diff, target interface{}) error
- // DataType returns the data type corresponding to the table
- DataType() reflect.Type
- // ColumnSpec returns the column definition of a spcific column
- ColumnSpec(name string) IColumnSpec
- // Name returns the name of the table
- Name() string
- // Columns returns the array of columns definitions
- Columns() []IColumnSpec
- // PrimaryColumns returns the array of columns of primary keys
- PrimaryColumns() []IColumnSpec
- // Indexes
- Indexes() []STableIndex
- // Expression returns expression of the table
- Expression() string
- // Instance returns an instance of STable for this spec
- Instance() *STable
- // DropForeignKeySQL returns the SQL statements to drop foreignkeys for this table
- DropForeignKeySQL() []string
- // AddIndex adds index to table
- AddIndex(unique bool, cols ...string) bool
- // SyncSQL returns SQL strings to synchronize the data and model definition of the table
- SyncSQL() []string
- // Sync forces synchronize the data and model definition of the table
- Sync() error
- // Fetch query a struct
- Fetch(dt interface{}) error
- // Database returns the database of this table
- Database() *SDatabase
- // Drop drops table
- Drop() error
- // getter of Extra Options
- GetExtraOptions() TableExtraOptions
- // setter of Extra Options
- SetExtraOptions(opts TableExtraOptions)
- }
- // STableSpec defines the table specification, which implements ITableSpec
- type STableSpec struct {
- structType reflect.Type
- name string
- _columns []IColumnSpec
- _indexes []STableIndex
- _contraints []STableConstraint
- extraOptions TableExtraOptions
- sDBReferer
- IsLinked bool
- syncedIndex bool
- syncIndexLock *sync.Mutex
- }
- // STable is an instance of table for query, system will automatically give a alias to this table
- type STable struct {
- spec ITableSpec
- alias string
- }
- // STableField represents a field in a table, implements IQueryField
- type STableField struct {
- table *STable
- spec IColumnSpec
- alias string
- }
- // NewTableSpecFromStruct generates STableSpec based on the information of a struct model
- func NewTableSpecFromStruct(s interface{}, name string) *STableSpec {
- return NewTableSpecFromStructWithDBName(s, name, DefaultDB)
- }
- func NewTableSpecFromStructWithDBName(s interface{}, name string, dbName DBName) *STableSpec {
- val := reflect.Indirect(reflect.ValueOf(s))
- st := val.Type()
- if st.Kind() != reflect.Struct {
- panic("expect Struct kind")
- }
- table := &STableSpec{
- name: name,
- structType: st,
- sDBReferer: sDBReferer{
- dbName: dbName,
- },
- syncedIndex: false,
- syncIndexLock: &sync.Mutex{},
- }
- return table
- }
- func NewTableSpecFromISpecWithDBName(spec ITableSpec, name string, dbName DBName, extraOpts TableExtraOptions) *STableSpec {
- table := &STableSpec{
- name: name,
- structType: spec.DataType(),
- sDBReferer: sDBReferer{
- dbName: dbName,
- },
- extraOptions: extraOpts,
- IsLinked: true,
- syncedIndex: false,
- syncIndexLock: &sync.Mutex{},
- }
- return table
- }
- // Name implementation of STableSpec for ITableSpec
- func (ts *STableSpec) Name() string {
- return ts.name
- }
- // Expression implementation of STableSpec for ITableSpec
- func (ts *STableSpec) Expression() string {
- qChar := ts.Database().backend.QuoteChar()
- return fmt.Sprintf("%s%s%s", qChar, ts.name, qChar)
- }
- func (ts *STableSpec) SyncColumnIndexes() error {
- if !ts.Exists() {
- return errors.Wrap(errors.ErrNotFound, "table not exists")
- }
- ts.syncIndexLock.Lock()
- defer ts.syncIndexLock.Unlock()
- if ts.syncedIndex {
- return nil
- }
- cols, err := ts.Database().backend.FetchTableColumnSpecs(ts)
- if err != nil {
- return errors.Wrap(err, "FetchTableColumnSpecs")
- }
- if len(cols) != len(ts._columns) {
- colsName := map[string]bool{}
- for _, col := range ts._columns {
- colsName[col.Name()] = true
- }
- removed := []string{}
- for _, col := range cols {
- if _, ok := colsName[col.Name()]; !ok {
- removed = append(removed, col.Name())
- }
- }
- return errors.Wrapf(errors.ErrInvalidStatus, "ts %s col %d != actual col %d need remove columns %s", ts.Name(), len(ts._columns), len(cols), removed)
- }
- for i := range cols {
- cols[i].SetColIndex(i)
- }
- // sort colums
- sort.Slice(cols, func(i, j int) bool {
- return compareColumnSpec(cols[i], cols[j]) < 0
- })
- sort.Slice(ts._columns, func(i, j int) bool {
- return compareColumnSpec(ts._columns[i], ts._columns[j]) < 0
- })
- // compare columns and assign colindex
- for i := range ts._columns {
- comp := compareColumnSpec(cols[i], ts._columns[i])
- if comp != 0 {
- return errors.Wrapf(errors.ErrInvalidStatus, "colname %s != %s", cols[i].Name(), ts._columns[i].Name())
- }
- ts._columns[i].SetColIndex(cols[i].GetColIndex())
- }
- // sort columns according to colindex
- sort.Slice(ts._columns, func(i, j int) bool {
- return compareColumnIndex(ts._columns[i], ts._columns[j]) < 0
- })
- ts.syncedIndex = true
- return nil
- }
- // Clone makes a clone of a table, so we may create a new table of the same schema
- func (ts *STableSpec) Clone(name string, autoIncOffset int64) *STableSpec {
- nts, _ := ts.CloneWithSyncColumnOrder(name, autoIncOffset, false)
- return nts
- }
- // Clone makes a clone of a table, so we may create a new table of the same schema
- func (ts *STableSpec) CloneWithSyncColumnOrder(name string, autoIncOffset int64, syncColOrder bool) (*STableSpec, error) {
- if ts.Exists() && syncColOrder {
- // if table exists, sync column index
- err := ts.SyncColumnIndexes()
- if err != nil {
- return nil, errors.Wrap(err, "SyncColumnIndexes")
- }
- }
- columns := ts.Columns()
- newCols := make([]IColumnSpec, len(columns))
- for i := range newCols {
- col := columns[i]
- if col.IsAutoIncrement() {
- colValue := reflect.Indirect(reflect.ValueOf(col))
- newColValue := reflect.Indirect(reflect.New(colValue.Type()))
- newColValue.Set(colValue)
- newCol := newColValue.Addr().Interface().(IColumnSpec)
- newCol.SetAutoIncrementOffset(autoIncOffset)
- newCols[i] = newCol
- } else {
- newCols[i] = col
- }
- }
- nts := &STableSpec{
- structType: ts.structType,
- name: name,
- _columns: newCols,
- _contraints: ts._contraints,
- sDBReferer: ts.sDBReferer,
- syncedIndex: false,
- syncIndexLock: &sync.Mutex{},
- }
- newIndexes := make([]STableIndex, len(ts._indexes))
- for i := range ts._indexes {
- newIndexes[i] = ts._indexes[i].clone(nts)
- }
- nts._indexes = newIndexes
- return nts, nil
- }
- // Columns implementation of STableSpec for ITableSpec
- func (ts *STableSpec) Columns() []IColumnSpec {
- ts.syncIndexLock.Lock()
- defer ts.syncIndexLock.Unlock()
- if ts._columns == nil {
- val := reflect.Indirect(reflect.New(ts.structType))
- ts.struct2TableSpec(val)
- }
- return ts._columns
- }
- // PrimaryColumns implementation of STableSpec for ITableSpec
- func (ts *STableSpec) PrimaryColumns() []IColumnSpec {
- ret := make([]IColumnSpec, 0)
- columns := ts.Columns()
- for i := range columns {
- if columns[i].IsPrimary() {
- ret = append(ret, columns[i])
- }
- }
- return ret
- }
- // Indexes implementation of STableSpec for ITableSpec
- func (ts *STableSpec) Indexes() []STableIndex {
- return ts._indexes
- }
- // DataType implementation of STableSpec for ITableSpec
- func (ts *STableSpec) DataType() reflect.Type {
- return ts.structType
- }
- // CreateSQL returns the SQL for creating this table
- func (ts *STableSpec) CreateSQLs() []string {
- return ts.Database().backend.GetCreateSQLs(ts)
- }
- // NewTableInstance return an new table instance from an ITableSpec
- func NewTableInstance(ts ITableSpec) *STable {
- table := STable{spec: ts, alias: getTableAliasName()}
- return &table
- }
- // Instance return an new table instance from an instance of STableSpec
- func (ts *STableSpec) Instance() *STable {
- return NewTableInstance(ts)
- }
- // ColumnSpec implementation of STableSpec for ITableSpec
- func (ts *STableSpec) ColumnSpec(name string) IColumnSpec {
- for _, c := range ts.Columns() {
- if c.Name() == name {
- return c
- }
- }
- return nil
- }
- // Field implementation of STableSpec for IQuerySource
- func (tbl *STable) Field(name string, alias ...string) IQueryField {
- // name = reflectutils.StructFieldName(name)
- name = utils.CamelSplit(name, "_")
- spec := tbl.spec.ColumnSpec(name)
- if spec == nil {
- log.Warningf("column %s not found in table %s", name, tbl.spec.Name())
- return nil
- }
- col := STableField{table: tbl, spec: spec}
- if len(alias) > 0 {
- return col.Label(alias[0])
- }
- return &col
- }
- // Fields implementation of STable for IQuerySource
- func (tbl *STable) Fields() []IQueryField {
- ret := make([]IQueryField, 0)
- for _, c := range tbl.spec.Columns() {
- ret = append(ret, tbl.Field(c.Name()))
- }
- return ret
- }
- // Database implementaion of STable for IQuerySource
- func (tbl *STable) database() *SDatabase {
- return tbl.spec.Database()
- }
- // Expression implementation of STable for IQuerySource
- func (tbl *STable) Expression() string {
- return tbl.spec.Expression()
- }
- // Alias implementation of STable for IQuerySource
- func (tbl *STable) Alias() string {
- return tbl.alias
- }
- // Variables implementation of STable for IQuerySource
- func (tbl *STable) Variables() []interface{} {
- return []interface{}{}
- }
- // Expression implementation of STableField for IQueryField
- func (c *STableField) Expression() string {
- qChar := c.database().backend.QuoteChar()
- return fmt.Sprintf("%s%s%s.%s%s%s", qChar, c.table.Alias(), qChar, qChar, c.spec.Name(), qChar)
- }
- // Name implementation of STableField for IQueryField
- func (c *STableField) Name() string {
- if len(c.alias) > 0 {
- return c.alias
- }
- return c.spec.Name()
- }
- // Reference implementation of STableField for IQueryField
- func (c *STableField) Reference() string {
- qChar := c.database().backend.QuoteChar()
- return fmt.Sprintf("%s%s%s.%s%s%s", qChar, c.table.Alias(), qChar, qChar, c.Name(), qChar)
- }
- // Label implementation of STableField for IQueryField
- func (c *STableField) Label(label string) IQueryField {
- if len(label) > 0 {
- // label make a copy of the field
- nc := *c
- nc.alias = label
- return &nc
- } else {
- return c
- }
- }
- // Variables implementation of STableField for IQueryField
- func (c *STableField) Variables() []interface{} {
- return nil
- }
- // ConvertFromValue implementation of STableField for IQueryField
- func (c *STableField) ConvertFromValue(val interface{}) interface{} {
- return c.spec.ConvertFromValue(val)
- }
- // database implementation of STableField for IQueryField
- func (c *STableField) database() *SDatabase {
- return c.table.database()
- }
|