From b90b90dba264dc8cafea3bbb19b7b772a31da17f Mon Sep 17 00:00:00 2001 From: metalwolf Date: Tue, 10 Nov 2020 11:56:51 -0600 Subject: [PATCH] patch v0.3.0 --- README.md | 276 +++++++++++++++++++++++++++----------------------- xbase.go | 39 ++++--- xbase_test.go | 104 +++++++++++++++++++ xcursor.go | 37 +------ xtable.go | 72 +++++++++++-- 5 files changed, 341 insertions(+), 187 deletions(-) create mode 100644 xbase_test.go diff --git a/README.md b/README.md index f6390cc..724dc37 100644 --- a/README.md +++ b/README.md @@ -21,133 +21,6 @@ TO DO: - Synchro to upgrade DB tables and fields - Oracle, Informix, Mongo, other DBs -Version Changes Control -======================= - -v0.2.3 - 2020-06-03 ------------------------ -- Upgrade to xcore/v2 -- Modularization with go.mod - -v0.2.2 - 2020-06-03 ------------------------ -- Bug Corrected on Clonation of XRecord, it now consider XRecords (via interface Clone() XDatasetCollectionDef) as possible subset to clone too. - -v0.2.1 - 2020-02-11 ------------------------ -- Bug Corrected on String and GoString of XRecord and XRecords - -v0.2.0 - 2020-02-10 ------------------------ -- Modification to XRecord and XRecords to meet xcore v1.0.0 (.String and .GoString functions added, .Stringify function removed) - -v0.1.3 - 2020-01-08 ------------------------ -- Corrected an error in Insert, Update to use XRecordDef and XRecordsDef instead of XRecord and XRecords to be widely compatible with any entry parameter -- All functions will auto-identify if parameters are XRecord, *XRecord, XRecords or *XRecords - -v0.1.2 - 2020-01-06 ------------------------ -- Corrected an error in Select, Update, Delete to use int32, int64, float32 as values -- Added functions Min, Max, Avg - -v0.1.1 - 2019-12-17 ------------------------ -- Corrected an error in Upsert func that was inserting a 0 even if the key was present into the record - -v0.1.0 - 2019-12-06 ------------------------ -- Added new XTable Language functionality to know the default language of data into a table -- gofmt code formated before pushing a change on github - -v0.0.15 - 2019-10-11 ------------------------ -- Added record conversions for float values, from string, float32, int, etc -- Added Upsert function in table (update or insert if not exists) - -v0.0.14 - 2019-06-25 ------------------------ -- Added Clone on XRecord and XRecords to meet definition of xcore.XDatasetDef and xcore.XDatasetCollectionDef - -v0.0.13 - 2019-06-19 ------------------------ -- Error corrected on XTable.SelectAll. It was not working as expected -- Error corrected on XRecord.GetString. was returning "" instead of "" when the database field was null - -v0.0.12 - 2019-03-06 ------------------------ -- Support for Time functions added in the XRecord (instanciated from XDatasetDef) - -v0.0.11 - 2019-03-01 ------------------------ -- Many correction on Mysql support to make it work correctly -- Removed GetValue function from FieldDef -- "?" implemented for fields, conditions and having queries (in select, insert, update, delete statements) -- Values are directly passed to the query with "?", not a string representation of them - -v0.0.10 - 2019-02-18 ------------------------ -- Support for MySQL added -- Queries and conditions now uses "?" or "$x" for parameters -- Orderby implemented -- like and ilike implemented for text fields -- fields.GetValue function added when the code needs the raw string value (not like CreateValue where then value is created with ' for strings) - -v0.0.9 - 2019-02-15 ------------------------ -- New funcion for field: GetValue created -- Error corrected on conflict between CreateValue (with ' for strings) and GetValue (for use with $d to inject into queries) - -v0.0.8 - 2019-02-14 ------------------------ -- XCondition works with string queries (not yet with "?" parameters) -- Correction done on CreateValue for string fields (text, varchar, dates) - -v0.0.7 - 2019-02-05 ------------------------ -- Added XOrderBy, XOrder structures -- Added XGroupBy, XGroup structures -- Added XHaving structures -- Error corrected on xrecord.GetTime when the field comes NIL from database -- Added DEBUG main xdominion global variable. Set to true to print all the built queries - -V0.0.6 - 2019-01-15 ------------------------ -- Added conversion between types con Get* functions -- XTable.Count implemented - -V0.0.5 - 2019-01-15 ------------------------ -- XTable.Update implemented -- XTable.Delete implemented - -V0.0.4 - 2019-01-06 ------------------------ -- Modify XRecord to match XDataset last version (xcore 0.0.4) -- Modify XRecords to match XDatasetCollection last version (xcore 0.0.4) - -V0.0.3 - 2018-12-19 ------------------------ -- XField added: Float, Date, DateTime, Text, partially implemented -- XConditions and XCondition added, partially implemented -- XConstraints and XConstaint added, partially implemented -- XFieldSet added, partially implemented -- XOrderBy added, partially implemented - -V0.0.2 - 2018-12-17 ------------------------ -- Postgres implementation -- XTable created. select and insert partially done -- XField created, Integer and VarChar partially done -- XRecord created -- XRecords created -- XCursor created - -V0.0.1 - 2018-11-14 ------------------------ -- First commit, Eventually does not work yet -- Base object done - Manual: ======================= @@ -290,6 +163,21 @@ Query by Where: } ``` +Transactions: + +``` +tx, err := base.BeginTransaction() +res1, err := tb.Insert(XRecord{"f1": 5, "f2": "Data line 1"}, tx) +res2, err := tb.Update(2, XRecord{"f1": 5, "f2": "Data line 1"}, tx) +res3, err := tb.Delete(3, tx) +// Note that the transaction is always passed as a parameter to the insert, update, delete operations +if err != nil { + tx.Rollback() + return err +} +tx.Commit() +``` + 2. Reference ------------------------ @@ -305,4 +193,138 @@ XRecord + + +Version Changes Control +======================= + +v0.3.0 - 2020-11-10 +----------------------- +- Implementation of transactions, new XTransaction object and functions to create a transaction, commit or rollback it. + +v0.2.3 - 2020-06-03 +----------------------- +- Upgrade to xcore/v2 +- Modularization with go.mod + +v0.2.2 - 2020-06-03 +----------------------- +- Bug Corrected on Clonation of XRecord, it now consider XRecords (via interface Clone() XDatasetCollectionDef) as possible subset to clone too. + +v0.2.1 - 2020-02-11 +----------------------- +- Bug Corrected on String and GoString of XRecord and XRecords + +v0.2.0 - 2020-02-10 +----------------------- +- Modification to XRecord and XRecords to meet xcore v1.0.0 (.String and .GoString functions added, .Stringify function removed) + +v0.1.3 - 2020-01-08 +----------------------- +- Corrected an error in Insert, Update to use XRecordDef and XRecordsDef instead of XRecord and XRecords to be widely compatible with any entry parameter +- All functions will auto-identify if parameters are XRecord, *XRecord, XRecords or *XRecords + +v0.1.2 - 2020-01-06 +----------------------- +- Corrected an error in Select, Update, Delete to use int32, int64, float32 as values +- Added functions Min, Max, Avg + +v0.1.1 - 2019-12-17 +----------------------- +- Corrected an error in Upsert func that was inserting a 0 even if the key was present into the record + +v0.1.0 - 2019-12-06 +----------------------- +- Added new XTable Language functionality to know the default language of data into a table +- gofmt code formated before pushing a change on github + +v0.0.15 - 2019-10-11 +----------------------- +- Added record conversions for float values, from string, float32, int, etc +- Added Upsert function in table (update or insert if not exists) + +v0.0.14 - 2019-06-25 +----------------------- +- Added Clone on XRecord and XRecords to meet definition of xcore.XDatasetDef and xcore.XDatasetCollectionDef + +v0.0.13 - 2019-06-19 +----------------------- +- Error corrected on XTable.SelectAll. It was not working as expected +- Error corrected on XRecord.GetString. was returning "" instead of "" when the database field was null + +v0.0.12 - 2019-03-06 +----------------------- +- Support for Time functions added in the XRecord (instanciated from XDatasetDef) + +v0.0.11 - 2019-03-01 +----------------------- +- Many correction on Mysql support to make it work correctly +- Removed GetValue function from FieldDef +- "?" implemented for fields, conditions and having queries (in select, insert, update, delete statements) +- Values are directly passed to the query with "?", not a string representation of them + +v0.0.10 - 2019-02-18 +----------------------- +- Support for MySQL added +- Queries and conditions now uses "?" or "$x" for parameters +- Orderby implemented +- like and ilike implemented for text fields +- fields.GetValue function added when the code needs the raw string value (not like CreateValue where then value is created with ' for strings) + +v0.0.9 - 2019-02-15 +----------------------- +- New funcion for field: GetValue created +- Error corrected on conflict between CreateValue (with ' for strings) and GetValue (for use with $d to inject into queries) + +v0.0.8 - 2019-02-14 +----------------------- +- XCondition works with string queries (not yet with "?" parameters) +- Correction done on CreateValue for string fields (text, varchar, dates) + +v0.0.7 - 2019-02-05 +----------------------- +- Added XOrderBy, XOrder structures +- Added XGroupBy, XGroup structures +- Added XHaving structures +- Error corrected on xrecord.GetTime when the field comes NIL from database +- Added DEBUG main xdominion global variable. Set to true to print all the built queries + +V0.0.6 - 2019-01-15 +----------------------- +- Added conversion between types con Get* functions +- XTable.Count implemented + +V0.0.5 - 2019-01-15 +----------------------- +- XTable.Update implemented +- XTable.Delete implemented + +V0.0.4 - 2019-01-06 +----------------------- +- Modify XRecord to match XDataset last version (xcore 0.0.4) +- Modify XRecords to match XDatasetCollection last version (xcore 0.0.4) + +V0.0.3 - 2018-12-19 +----------------------- +- XField added: Float, Date, DateTime, Text, partially implemented +- XConditions and XCondition added, partially implemented +- XConstraints and XConstaint added, partially implemented +- XFieldSet added, partially implemented +- XOrderBy added, partially implemented + +V0.0.2 - 2018-12-17 +----------------------- +- Postgres implementation +- XTable created. select and insert partially done +- XField created, Integer and VarChar partially done +- XRecord created +- XRecords created +- XCursor created + +V0.0.1 - 2018-11-14 +----------------------- +- First commit, Eventually does not work yet +- Base object done + + --- diff --git a/xbase.go b/xbase.go index 2d774ff..4ec149c 100644 --- a/xbase.go +++ b/xbase.go @@ -1,6 +1,7 @@ package xdominion import ( + "context" "database/sql" "fmt" "log" @@ -17,7 +18,7 @@ As of 2018/12/01, only postgres and mysql are supported for now const ( // Version of XDominion - VERSION = "0.2.3" + VERSION = "0.3.0" // The distinct supported databases DB_Postgres = "postgres" @@ -90,25 +91,35 @@ func (b *XBase) Exec(query string, args ...interface{}) (*sql.Rows, error) { } func (b *XBase) Cursor() *Cursor { - return &Cursor{Base: b, Transactional: false} + return &Cursor{Base: b} } -func (b *XBase) CursorTransactional() *Cursor { - c := b.Cursor() - c.BeginTransaction() - return c +type XTransaction struct { + DB *XBase + TX *sql.Tx } -/* -func (b *XBase)BeginTransaction() { - b.DB.Begin() +func (b *XBase) BeginTransaction() (*XTransaction, error) { + ctx := context.Background() + tx, err := b.DB.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable}) + if err != nil { + return nil, err + } + return &XTransaction{ + DB: b, + TX: tx, + }, nil +} + +func (t *XTransaction) Exec(query string, args ...interface{}) (*sql.Rows, error) { + cursor, err := t.TX.Query(query, args...) + return cursor, err } -func (b *XBase)Commit() { - b.DB.Commit() +func (t *XTransaction) Commit() error { + return t.TX.Commit() } -func (b *XBase)Rollback() { - b.DB.Rollback() +func (t *XTransaction) Rollback() error { + return t.TX.Rollback() } -*/ diff --git a/xbase_test.go b/xbase_test.go new file mode 100644 index 0000000..99338af --- /dev/null +++ b/xbase_test.go @@ -0,0 +1,104 @@ +package xdominion + +import ( + "fmt" + "testing" +) + +func ExampleXBase() { + + base := &XBase{ + DBType: DB_Postgres, + Username: "username", + Password: "password", + Database: "test", + Host: DB_Localhost, + SSL: false, + } + base.Logon() +} + +func TestXCore_commit(t *testing.T) { + + base := &XBase{ + DBType: DB_Postgres, + Username: "username", + Password: "password", + Database: "test", + Host: DB_Localhost, + SSL: false, + } + base.Logon() + + tb := getTableDef(base) + + // insert + res1, err := tb.Insert(XRecord{"f1": 3, "f2": "Data line 1"}) + if err != nil { + fmt.Println(err) + } + fmt.Println(res1) + res2, err := tb.Insert(XRecord{"f1": 4, "f2": "Data line 2"}) + if err != nil { + fmt.Println(err) + } + fmt.Println(res2) + + tx, err := base.BeginTransaction() + + // insert + res3, err := tb.Insert(XRecord{"f1": 5, "f2": "Data line 1"}, tx) + if err != nil { + fmt.Println(err) + } + fmt.Println(res3) + + res4, err := tb.Insert(XRecord{"f1": 6, "f2": "Data line 2"}, tx) + if err != nil { + fmt.Println(err) + } + fmt.Println(res4) + + res41, err := tb.Update(XRecord{"f3": "some new data"}, tx) + if err != nil { + fmt.Println(err) + } + fmt.Println(res41) + + res42, err := tb.Delete(1, tx) + if err != nil { + fmt.Println(err) + } + fmt.Println(res42) + + // lets roll back + err = tx.Commit() + if err != nil { + fmt.Println(err) + } + + // should give an error, tx is rollbacked + res5, err := tb.Insert(XRecord{"f1": 7, "f2": "Data line 2"}, tx) + if err != nil { + fmt.Println(err) + } + fmt.Println(res5) + +} + +func getTableDef(base *XBase) *XTable { + t := NewXTable("test", "t_") + t.AddField(XFieldInteger{Name: "f1", Constraints: XConstraints{ + XConstraint{Type: PK}, + XConstraint{Type: AI}, + }}) // ai, pk + t.AddField(XFieldVarChar{Name: "f2", Size: 20, Constraints: XConstraints{ + XConstraint{Type: NN}, + }}) + t.AddField(XFieldText{Name: "f3"}) + t.AddField(XFieldDate{Name: "f4"}) + t.AddField(XFieldDateTime{Name: "f5"}) + t.AddField(XFieldFloat{Name: "f6"}) + t.SetBase(base) + return t +} diff --git a/xcursor.go b/xcursor.go index 1663128..347d356 100644 --- a/xcursor.go +++ b/xcursor.go @@ -1,16 +1,7 @@ package xdominion -import ( - // "fmt" - "errors" - "log" - // "database/sql" - _ "github.com/lib/pq" -) - type Cursor struct { - Base *XBase - Transactional bool + Base *XBase } func (c *Cursor) Query() interface{} { @@ -19,30 +10,4 @@ func (c *Cursor) Query() interface{} { } func (c *Cursor) Close() { - if c.Transactional { - c.Commit() - } -} - -func (c *Cursor) BeginTransaction() { - if c.Transactional { - log.Panic(errors.New("A transaction has already started in cursor")) - } - c.Transactional = true -} - -func (c *Cursor) Rollback() { - if !c.Transactional { - log.Panic(errors.New("There is no transaction declared in cursor")) - } - - c.Transactional = false -} - -func (c *Cursor) Commit() { - if !c.Transactional { - log.Panic(errors.New("There is no transaction declared in cursor")) - } - - c.Transactional = false } diff --git a/xtable.go b/xtable.go index cb4db22..51fdb25 100644 --- a/xtable.go +++ b/xtable.go @@ -1,6 +1,7 @@ package xdominion import ( + libsql "database/sql" "errors" "fmt" "strconv" @@ -330,20 +331,23 @@ func (t *XTable) SelectAll(args ...interface{}) (*XRecords, error) { // If data is XRecord, insert the record. Returns the key (same type as field type) interface{} // If data is XRecords, insert the collection of XRecord. Returns an array of keys (same type as field type) []interface{} // If data is SubQuery, intert the result of the sub query, return ? -func (t *XTable) Insert(data interface{}) (interface{}, error) { +func (t *XTable) Insert(data interface{}, args ...interface{}) (interface{}, error) { + + hastrx := false + var trx *XTransaction t.InsertedKey = nil switch data.(type) { case XRecords: xdata := data.(XRecords) - return t.Insert(&xdata) + return t.Insert(&xdata, args...) case *XRecords, XRecordsDef: rc := data.(XRecordsDef) nrc := rc.Count() keys := make([]interface{}, nrc) for i := 0; i < nrc; i++ { xt, _ := rc.Get(i) - k, err := t.Insert(xt) + k, err := t.Insert(xt, args...) if err != nil { return nil, err } @@ -353,7 +357,7 @@ func (t *XTable) Insert(data interface{}) (interface{}, error) { return keys, nil case XRecord: xdata := data.(XRecord) - return t.Insert(&xdata) + return t.Insert(&xdata, args...) case *XRecord, XRecordDef: default: @@ -363,6 +367,10 @@ func (t *XTable) Insert(data interface{}) (interface{}, error) { return nil, errors.New("Type of Data no known. Must be one of XRecord, XRecords, SubQuery") } + if len(args) > 0 { + trx, hastrx = args[0].(*XTransaction) + } + rc := data.(XRecordDef) sqlf := "" sqlv := "" @@ -399,6 +407,9 @@ func (t *XTable) Insert(data interface{}) (interface{}, error) { } sql := "insert into " + t.Name + " (" + sqlf + ") values (" + sqlv + ")" + var cursor *libsql.Rows + var err error + if primkey != "" { if t.Base.DBType == DB_Postgres { sql += " returning " + primkey @@ -410,7 +421,11 @@ func (t *XTable) Insert(data interface{}) (interface{}, error) { } var key interface{} - cursor, err := t.Base.Exec(sql, sqldata...) + if hastrx { + cursor, err = trx.Exec(sql, sqldata...) + } else { + cursor, err = t.Base.Exec(sql, sqldata...) + } if err != nil { return nil, err } @@ -432,7 +447,11 @@ func (t *XTable) Insert(data interface{}) (interface{}, error) { fmt.Println(sqldata) } - cursor, err := t.Base.Exec(sql, sqldata...) + if hastrx { + cursor, err = trx.Exec(sql, sqldata...) + } else { + cursor, err = t.Base.Exec(sql, sqldata...) + } if err != nil { return nil, err } @@ -460,12 +479,16 @@ func (t *XTable) Update(args ...interface{}) (int, error) { var conditions XConditions hasrecord := false var record XRecordDef + hastrx := false + var trx *XTransaction for _, p := range args { switch p.(type) { case int, int32, int64, float32, float64, string, time.Time: // position 0 only haskey = true key = p + case *XTransaction: + trx, hastrx = p.(*XTransaction) case XCondition: hasconditions = true conditions = XConditions{p.(XCondition)} @@ -534,7 +557,13 @@ func (t *XTable) Update(args ...interface{}) (int, error) { fmt.Println(sql) } - cursor, err := t.Base.Exec(sql, sqldata...) + var cursor *libsql.Rows + var err error + if hastrx { + cursor, err = trx.Exec(sql, sqldata...) + } else { + cursor, err = t.Base.Exec(sql, sqldata...) + } if err != nil { return 0, err } @@ -560,12 +589,16 @@ func (t *XTable) Upsert(args ...interface{}) (int, error) { var conditions XConditions hasrecord := false var record XRecordDef + hastrx := false + var trx *XTransaction for _, p := range args { switch p.(type) { case int, float64, string, time.Time: // position 0 only haskey = true key = p + case *XTransaction: + trx, hastrx = p.(*XTransaction) case XCondition: hasconditions = true conditions = XConditions{p.(XCondition)} @@ -596,13 +629,22 @@ func (t *XTable) Upsert(args ...interface{}) (int, error) { primkey := t.GetPrimaryKey() if rec != nil { thekey, _ := rec.Get(primkey.GetName()) - return t.Update(thekey, record) + if hastrx { + return t.Update(thekey, record, trx) + } else { + return t.Update(thekey, record) + } } hasprimkey, _ := record.Get(primkey.GetName()) if hasprimkey == nil { record.Set(primkey.GetName(), 0) } - _, e := t.Insert(record) + var e error + if hastrx { + _, e = t.Insert(record, trx) + } else { + _, e = t.Insert(record) + } if e != nil { return 0, e } @@ -615,12 +657,16 @@ func (t *XTable) Delete(args ...interface{}) (int, error) { var key interface{} hasconditions := false var conditions XConditions + hastrx := false + var trx *XTransaction for _, p := range args { switch p.(type) { case int, int32, int64, float32, float64, string, time.Time: haskey = true key = p + case *XTransaction: + trx, hastrx = p.(*XTransaction) case XCondition: hasconditions = true conditions = XConditions{p.(XCondition)} @@ -656,7 +702,13 @@ func (t *XTable) Delete(args ...interface{}) (int, error) { fmt.Println(sql) } - cursor, err := t.Base.Exec(sql, sqldata...) + var cursor *libsql.Rows + var err error + if hastrx { + cursor, err = trx.Exec(sql, sqldata...) + } else { + cursor, err = t.Base.Exec(sql, sqldata...) + } if err != nil { return 0, err }