github.com/aldelo/common@v1.5.1/wrapper/mysql/mysql.go (about) 1 package mysql 2 3 /* 4 * Copyright 2020-2023 Aldelo, LP 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 import ( 20 "database/sql" 21 "errors" 22 "fmt" 23 "github.com/aldelo/common/wrapper/xray" 24 awsxray "github.com/aws/aws-xray-sdk-go/xray" 25 "strings" 26 "sync" 27 "time" 28 29 util "github.com/aldelo/common" 30 "github.com/jmoiron/sqlx" 31 32 // this package is used by database/sql as we are wrapping the sql access functionality in this utility package 33 _ "github.com/go-sql-driver/mysql" 34 ) 35 36 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 37 // MySql struct Usage Hint 38 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 39 40 /* 41 *************************************************************************************************************** 42 First, Create "../model/global.go" 43 *************************************************************************************************************** 44 45 package model 46 47 import ( 48 "errors" 49 "time" 50 data "github.com/aldelo/common/wrapper/mysql" 51 ) 52 53 // package level accessible to the mysql server database object 54 var db *data.MySql 55 56 // SetDB allows code outside of package to set the mysql database reference 57 func SetDB(dbx *data.MySql) { 58 db = dbx 59 } 60 61 // BeginTran starts db transaction 62 func BeginTran() { 63 if db != nil { 64 db.Begin() 65 } 66 } 67 68 // CommitTran commits db transaction 69 func CommitTran() { 70 if db != nil { 71 db.Commit() 72 } 73 } 74 75 // RollbackTran rolls back db transaction 76 func RollbackTran() { 77 if db != nil { 78 db.Rollback() 79 } 80 } 81 82 */ 83 84 /* 85 *************************************************************************************************************** 86 Second, Prepare DB Object for Use in "../main.go" 87 *************************************************************************************************************** 88 89 package main 90 91 import ( 92 ... 93 data "github.com/aldelo/common/wrapper/mysql" 94 "???/model" // ??? is path to model package 95 ... 96 ) 97 98 ... 99 100 func main() { 101 ... 102 103 // ======================================== 104 // setup database connection 105 // ======================================== 106 107 // 108 // declare mysql server object 109 // 110 s := new(data.MySql) 111 112 // 113 // set mysql dsn fields 114 // 115 s.Host = "" // from aws aurora endpoint 116 s.Port = 0 // custom port number if applicable (0 will ignore this field) 117 s.Database = "" // database name 118 s.UserName = "" // database server user name 119 s.Password = "" // database server user password 120 121 // 122 // open mysql server database connection 123 // 124 if err := s.Open(); err != nil { 125 s.Close() 126 } else { 127 // add mysql db object to model global 128 model.SetDB(&s) 129 130 // defer db clean up upon execution ends 131 defer model.SetDB(nil) 132 defer s.Close() 133 } 134 135 ... 136 } 137 138 */ 139 140 /* 141 *************************************************************************************************************** 142 Third, Using MySql Struct 143 *************************************************************************************************************** 144 145 package model 146 147 import ( 148 "bytes" 149 "database/sql" // this import is needed for db struct tags 150 "errors" 151 "time" 152 util "github.com/aldelo/common" 153 ) 154 155 // create a struct, and use db struct tags to identify parameter names 156 // db struct tags can contain ,required ,size=# if string 157 type Customer struct { 158 CustomerID int `db:"customerID"` 159 CompanyName string `db:"companyName"` 160 } 161 162 // when composing sql statements, if statement is long, use bytes.Buffer (or use /QueryBuilder.go) 163 var b bytes.Buffer 164 165 b.WriteString("xyz ") 166 b.WriteString("123") 167 168 v := b.String() // v = xyz 123 169 170 // for insert, update, logical delete, physical delete 171 // use the appropriate functions from db struct, located in model/global.go 172 // the db struct is global in scope for code files within model package 173 db.GetStruct(...) 174 db.GetSliceStruct(...) 175 // etc 176 177 */ 178 179 // ================================================================================================================ 180 // STRUCTS 181 // ================================================================================================================ 182 183 // MySql struct encapsulates the MySql server database access functionality by wrapping Sqlx package with top level methods 184 // 185 // Charset = utf8, utf8mb4 (< Default) 186 // Collation = utf8mb4_general_ci (< Default), utf8_general_ci 187 // ...Timeout = must be decimal number with unit suffix (ms, s, m, h), such as "30s", "0.5m", "1m30s" 188 // 189 // MaxOpenConns = maximum open and idle connections for the connection pool, default is 0, which is unlimited (db *sqlx.DB internally is a connection pool object) 190 // MaxIdleConns = maximum number of idle connections to keep in the connection pool, default is 0 which is unlimited, this number should be equal or less than MaxOpenConns 191 // MaxConnIdleTime = maximum duration that an idle connection be kept in the connection pool, default is 0 which has no time limit, suggest 5 - 10 minutes if set 192 type MySql struct { 193 // MySql server connection properties 194 UserName string 195 Password string 196 197 Host string 198 Port int 199 Database string 200 201 Charset string // utf8, utf8mb4 202 Collation string // utf8mb4_general_ci, utf8_general_ci 203 ConnectTimeout string // must be decimal number with unit suffix (ms, s, m, h), such as "30s", "0.5m", "1m30s" 204 ReadTimeout string // must be decimal number with unit suffix (ms, s, m, h), such as "30s", "0.5m", "1m30s" 205 WriteTimeout string // must be decimal number with unit suffix (ms, s, m, h), such as "30s", "0.5m", "1m30s" 206 RejectReadOnly bool 207 208 MaxOpenConns int 209 MaxIdleConns int 210 MaxConnIdleTime time.Duration 211 212 // mysql server state object 213 db *sqlx.DB 214 txMap map[string]*MySqlTransaction 215 mux sync.RWMutex 216 217 lastPing time.Time 218 _parentSegment *xray.XRayParentSegment 219 } 220 221 // MySqlTransaction represents an instance of the sqlx.Tx. 222 // Since sqlx.DB is actually a connection pool container, it may initiate multiple sql transactions, 223 // therefore, independent sqlx.Tx must be managed individually throughout its lifecycle. 224 // 225 // each mysql transaction created will also register with MySql struct, so that during Close/Cleanup, all the related outstanding Transactions can rollback 226 type MySqlTransaction struct { 227 id string 228 parent *MySql 229 tx *sqlx.Tx 230 closed bool 231 _xrayTxSeg *xray.XSegment 232 } 233 234 // Commit will commit the current mysql transaction and close off from further uses (if commit was successful). 235 // on commit error, transaction will not close off 236 func (t *MySqlTransaction) Commit() (err error) { 237 if t._xrayTxSeg != nil { 238 defer func() { 239 if err != nil { 240 _ = t._xrayTxSeg.Seg.AddError(err) 241 } 242 if t.closed { 243 t._xrayTxSeg.Close() 244 } 245 }() 246 } 247 248 if t.closed { 249 err = fmt.Errorf("MySql Commit Transaction Not Valid, Transaction Already Closed") 250 return err 251 } else if util.LenTrim(t.id) == 0 { 252 err = fmt.Errorf("MySql Commit Transaction Not Valid, ID is Missing") 253 return err 254 } else if t.parent == nil { 255 err = fmt.Errorf("MySql Commit Transaction Not Valid, Parent is Missing") 256 return err 257 } else if t.tx == nil { 258 err = fmt.Errorf("MySql Commit Transaction Not Valid, Transaction Object Nil") 259 return err 260 } 261 262 if err = t.parent.Ping(); err != nil { 263 // ping failed 264 if t._xrayTxSeg != nil { 265 _ = t._xrayTxSeg.Seg.AddError(err) 266 t._xrayTxSeg.Close() 267 t._xrayTxSeg = nil 268 } 269 return err 270 } 271 272 if t._xrayTxSeg == nil { 273 // not using xray 274 if e := t.tx.Commit(); e != nil { 275 // on commit error, return error, don't close off transaction 276 err = fmt.Errorf("MySql Commit Transaction Failed, %s", e) 277 return err 278 } else { 279 // transaction commit success 280 t.closed = true 281 282 // remove this transaction from mysql parent txMap 283 t.parent.mux.Lock() 284 delete(t.parent.txMap, t.id) 285 t.parent.mux.Unlock() 286 287 // success response 288 return nil 289 } 290 } else { 291 // using xray 292 subSeg := t._xrayTxSeg.NewSubSegment("Commit-Transaction") 293 defer subSeg.Close() 294 295 subSeg.Capture("Commit-Transaction-Do", func() error { 296 if e := t.tx.Commit(); e != nil { 297 // on commit error, return error, don't close off transaction 298 err = fmt.Errorf("MySql Commit Transaction Failed, %s", e) 299 _ = subSeg.Seg.AddError(err) 300 return err 301 } else { 302 // transaction commit success 303 t.closed = true 304 305 // remove this transaction from mysql parent txMap 306 t.parent.mux.Lock() 307 delete(t.parent.txMap, t.id) 308 t.parent.mux.Unlock() 309 310 // success response 311 return nil 312 } 313 }) 314 315 return err 316 } 317 } 318 319 // Rollback will rollback the current mysql transaction and close off from further uses (whether rollback succeeds or failures) 320 func (t *MySqlTransaction) Rollback() (err error) { 321 if t._xrayTxSeg != nil { 322 defer func() { 323 if err != nil { 324 _ = t._xrayTxSeg.Seg.AddError(err) 325 } 326 t._xrayTxSeg.Close() 327 }() 328 } 329 330 if t.closed { 331 err = fmt.Errorf("MySql Rollback Transaction Not Valid, Transaction Already Closed") 332 return err 333 } else if util.LenTrim(t.id) == 0 { 334 err = fmt.Errorf("MySql Rollback Transaction Not Valid, ID is Missing") 335 return err 336 } else if t.parent == nil { 337 err = fmt.Errorf("MySql Rollback Transaction Not Valid, Parent is Missing") 338 return err 339 } else if t.tx == nil { 340 err = fmt.Errorf("MySql Rollback Transaction Not Valid, Transaction Object Nil") 341 return err 342 } 343 344 if err = t.parent.Ping(); err != nil { 345 // ping failed 346 if t._xrayTxSeg != nil { 347 _ = t._xrayTxSeg.Seg.AddError(err) 348 t._xrayTxSeg.Close() 349 t._xrayTxSeg = nil 350 } 351 return err 352 } 353 354 // perform rollback 355 if t._xrayTxSeg == nil { 356 if e := t.tx.Rollback(); e != nil { 357 err = fmt.Errorf("MySql Rollback Transaction Failed, %s", e) 358 } 359 } else { 360 subSeg := t._xrayTxSeg.NewSubSegment("Rollback-Transaction") 361 defer subSeg.Close() 362 363 subSeg.Capture("Rollback-Transaction-Do", func() error { 364 if e := t.tx.Rollback(); e != nil { 365 err = fmt.Errorf("MySql Rollback Transaction Failed, %s", e) 366 _ = subSeg.Seg.AddError(err) 367 return err 368 } else { 369 return nil 370 } 371 }) 372 } 373 374 // transaction rollback success or failure, always close off transaction 375 t.closed = true 376 377 // remove this transaction from mysql parent txMap 378 t.parent.mux.Lock() 379 delete(t.parent.txMap, t.id) 380 t.parent.mux.Unlock() 381 382 // return response 383 return err 384 } 385 386 // ready checks if MySqlTransaction 387 func (t *MySqlTransaction) ready() error { 388 if t.closed { 389 return fmt.Errorf("MySql Transaction Not Valid, Transaction Already Closed") 390 } else if util.LenTrim(t.id) == 0 { 391 return fmt.Errorf("MySql Transaction Not Valid, ID is Missing") 392 } else if t.parent == nil { 393 return fmt.Errorf("MySql Transaction Not Valid, Parent is Missing") 394 } else if t.tx == nil { 395 return fmt.Errorf("MySql Transaction Not Valid, Transaction Object Nil") 396 } 397 398 return nil 399 } 400 401 // MySqlResult defines sql action query result info 402 // [ Notes ] 403 // 1. NewlyInsertedID = ONLY FOR INSERT, ONLY IF AUTO_INCREMENT PRIMARY KEY (Custom PK ID Will Have This Field as 0 Always) 404 type MySqlResult struct { 405 RowsAffected int64 406 NewlyInsertedID int64 // ONLY FOR INSERT, ONLY IF AUTO_INCREMENT PRIMARY KEY (Custom PK ID Will Have This Field as 0 Always) 407 Err error 408 } 409 410 // ================================================================================================================ 411 // STRUCT FUNCTIONS 412 // ================================================================================================================ 413 414 // ---------------------------------------------------------------------------------------------------------------- 415 // utility functions 416 // ---------------------------------------------------------------------------------------------------------------- 417 418 // GetDsn serializes MySql server dsn to connection string, for use in database connectivity 419 func (svr *MySql) GetDsn() (string, error) { 420 // 421 // first validate input 422 // 423 if len(svr.UserName) == 0 { 424 return "", errors.New("User Name is Required") 425 } 426 427 if len(svr.Password) == 0 { 428 return "", errors.New("Password is Required") 429 } 430 431 if len(svr.Host) == 0 { 432 return "", errors.New("MySQL Host Address is Required") 433 } 434 435 if len(svr.Database) == 0 { 436 return "", errors.New("MySQL Database Name is Required") 437 } 438 439 // 440 // now create mysql connection string 441 // format = [username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN] 442 // 443 str := svr.UserName + ":" + svr.Password 444 str += "@(" + svr.Host 445 446 if svr.Port > 0 { 447 str += ":" + util.Itoa(svr.Port) 448 } 449 450 str += ")/" + svr.Database 451 452 if util.LenTrim(svr.Charset) > 0 { 453 str += "?charset=" + svr.Charset 454 } else { 455 str += "?charset=utf8mb4" 456 } 457 458 if util.LenTrim(svr.Collation) > 0 { 459 str += "&collation=" + svr.Collation 460 } else { 461 str += "&collation=utf8mb4_general_ci" 462 } 463 464 str += "&parseTime=true" 465 466 if util.LenTrim(svr.ConnectTimeout) > 0 { 467 str += "&timeout=" + svr.ConnectTimeout 468 } 469 470 if util.LenTrim(svr.ReadTimeout) > 0 { 471 str += "&readTimeout=" + svr.ReadTimeout 472 } 473 474 if util.LenTrim(svr.WriteTimeout) > 0 { 475 str += "&writeTimeout=" + svr.WriteTimeout 476 } 477 478 if svr.RejectReadOnly { 479 str += "&rejectReadOnly=true" 480 } 481 482 // return to caller 483 return str, nil 484 } 485 486 // cleanUpAllSqlTransactions is a helper that cleans up (rolls back) any outstanding sql transactions in the txMap 487 func (svr *MySql) cleanUpAllSqlTransactions() { 488 svr.mux.Lock() 489 if svr.txMap != nil && len(svr.txMap) > 0 { 490 for k, v := range svr.txMap { 491 if v != nil && !v.closed && v.tx != nil { 492 _ = v.tx.Rollback() 493 if v._xrayTxSeg != nil { 494 v._xrayTxSeg.Close() 495 } 496 } 497 delete(svr.txMap, k) 498 } 499 svr.txMap = make(map[string]*MySqlTransaction) 500 } 501 svr.mux.Unlock() 502 } 503 504 // Open will open a database either as normal or with xray tracing, 505 // Open uses the dsn properties defined in the struct fields 506 func (svr *MySql) Open(parentSegment ...*xray.XRayParentSegment) error { 507 if !xray.XRayServiceOn() { 508 return svr.openNormal() 509 } else { 510 if len(parentSegment) > 0 { 511 svr._parentSegment = parentSegment[0] 512 } 513 514 return svr.openWithXRay() 515 } 516 } 517 518 // openNormal opens a database by connecting to it, using the dsn properties defined in the struct fields 519 func (svr *MySql) openNormal() error { 520 // clean up first 521 svr.db = nil 522 svr.cleanUpAllSqlTransactions() 523 524 // declare 525 var str string 526 var err error 527 528 // get connect string 529 str, err = svr.GetDsn() 530 531 if err != nil { 532 return err 533 } 534 535 // validate connection string 536 if len(str) == 0 { 537 return errors.New("MySQL Server Connect String Generated Cannot Be Empty") 538 } 539 540 // now ready to open mysql database 541 svr.db, err = sqlx.Open("mysql", str) 542 543 if err != nil { 544 return err 545 } 546 547 // test mysql server state object 548 if err = svr.db.Ping(); err != nil { 549 svr.db = nil 550 return err 551 } 552 svr.lastPing = time.Now() 553 554 if svr.MaxOpenConns > 0 { 555 svr.db.SetMaxOpenConns(svr.MaxOpenConns) 556 } 557 558 if svr.MaxIdleConns > 0 { 559 if svr.MaxIdleConns <= svr.MaxOpenConns || svr.MaxOpenConns == 0 { 560 svr.db.SetMaxIdleConns(svr.MaxIdleConns) 561 } else { 562 svr.db.SetMaxIdleConns(svr.MaxOpenConns) 563 } 564 } 565 566 if svr.MaxConnIdleTime > 0 { 567 svr.db.SetConnMaxIdleTime(svr.MaxConnIdleTime) 568 } 569 570 svr.db.SetConnMaxLifetime(0) 571 572 // mysql server state object successfully opened 573 return nil 574 } 575 576 // openWithXRay opens a database by connecting to it, wrap with XRay tracing, using the dsn properties defined in the struct fields 577 func (svr *MySql) openWithXRay() (err error) { 578 trace := xray.NewSegment("MySql-Open-Entry", svr._parentSegment) 579 defer trace.Close() 580 defer func() { 581 _ = trace.Seg.AddMetadata("DB-Host", svr.Host) 582 _ = trace.Seg.AddMetadata("DB-Database", svr.Database) 583 _ = trace.Seg.AddMetadata("DB-UserName", svr.UserName) 584 _ = trace.Seg.AddMetadata("DB-Charset", svr.Charset) 585 _ = trace.Seg.AddMetadata("DB-Collation", svr.Collation) 586 _ = trace.Seg.AddMetadata("DB-ConnectTimeout", svr.ConnectTimeout) 587 _ = trace.Seg.AddMetadata("DB-ReadTimeout", svr.ReadTimeout) 588 _ = trace.Seg.AddMetadata("DB-WriteTimeout", svr.WriteTimeout) 589 590 if err != nil { 591 _ = trace.Seg.AddError(err) 592 } 593 }() 594 595 // clean up first 596 svr.db = nil 597 svr.cleanUpAllSqlTransactions() 598 599 // declare 600 var str string 601 602 trace.Capture("Get-DSN", func() error { 603 // get connect string 604 str, err = svr.GetDsn() 605 return err 606 }) 607 608 if err != nil { 609 return err 610 } 611 612 // validate connection string 613 if len(str) == 0 { 614 err = errors.New("MySQL Server Connect String Generated Cannot Be Empty") 615 return err 616 } 617 618 trace.Capture("Get-SQL-Context", func() error { 619 // now ready to open mysql database 620 baseDb, e := awsxray.SQLContext("mysql", str) 621 622 if e != nil { 623 err = fmt.Errorf("openWithXRay() Failed During xray.SQLContext(): %s", e.Error()) 624 return err 625 } 626 627 svr.db = sqlx.NewDb(baseDb, "mysql") 628 return nil 629 }) 630 631 if err != nil { 632 return err 633 } 634 635 // test mysql server state object 636 subTrace := trace.NewSubSegment("MySql-Open-Ping") 637 defer subTrace.Close() 638 defer func() { 639 if err != nil { 640 _ = subTrace.Seg.AddError(err) 641 } 642 }() 643 644 subTrace.Capture("Ping", func() error { 645 if err = svr.db.PingContext(trace.Ctx); err != nil { 646 svr.db = nil 647 return err 648 } 649 svr.lastPing = time.Now() 650 return nil 651 }) 652 653 if err != nil { 654 return err 655 } 656 657 if svr.MaxOpenConns > 0 { 658 svr.db.SetMaxOpenConns(svr.MaxOpenConns) 659 } 660 661 if svr.MaxIdleConns > 0 { 662 if svr.MaxIdleConns <= svr.MaxOpenConns || svr.MaxOpenConns == 0 { 663 svr.db.SetMaxIdleConns(svr.MaxIdleConns) 664 } else { 665 svr.db.SetMaxIdleConns(svr.MaxOpenConns) 666 } 667 } 668 669 if svr.MaxConnIdleTime > 0 { 670 svr.db.SetConnMaxIdleTime(svr.MaxConnIdleTime) 671 } 672 673 svr.db.SetConnMaxLifetime(0) 674 675 // mysql server state object successfully opened 676 return nil 677 } 678 679 // Close will close the database connection and set db to nil 680 func (svr *MySql) Close() error { 681 svr.cleanUpAllSqlTransactions() 682 683 if svr.db != nil { 684 if err := svr.db.Close(); err != nil { 685 return err 686 } 687 688 // clean up 689 svr.db = nil 690 svr._parentSegment = nil 691 svr.lastPing = time.Time{} 692 return nil 693 } 694 695 return nil 696 } 697 698 // Ping tests if current database connection is still active and ready 699 func (svr *MySql) Ping() (err error) { 700 if svr.db == nil { 701 return errors.New("MySQL Server Not Connected") 702 } 703 704 if time.Now().Sub(svr.lastPing) < 90*time.Second { 705 return nil 706 } 707 708 if !xray.XRayServiceOn() { 709 if err := svr.db.Ping(); err != nil { 710 return err 711 } 712 } else { 713 trace := xray.NewSegment("MySql-Ping", svr._parentSegment) 714 defer trace.Close() 715 defer func() { 716 _ = trace.Seg.AddMetadata("Ping-Timestamp-UTC", time.Now().UTC()) 717 718 if err != nil { 719 _ = trace.Seg.AddError(err) 720 } 721 }() 722 723 if err = svr.db.PingContext(trace.Ctx); err != nil { 724 return err 725 } 726 } 727 728 // database ok 729 svr.lastPing = time.Now() 730 return nil 731 } 732 733 // Begin starts a mysql transaction, and stores the transaction object into txMap until commit or rollback. 734 // ensure that transaction related actions are executed from the MySqlTransaction object. 735 func (svr *MySql) Begin() (*MySqlTransaction, error) { 736 // verify if the database connection is good 737 if err := svr.Ping(); err != nil { 738 // begin failed 739 return nil, err 740 } 741 742 if !xray.XRayServiceOn() { 743 // begin transaction on database 744 if tx, err := svr.db.Beginx(); err != nil { 745 // begin failed 746 return nil, err 747 } else { 748 // begin succeeded, create MySqlTransaction and return result 749 myTx := &MySqlTransaction{ 750 id: util.NewULID(), 751 parent: svr, 752 tx: tx, 753 closed: false, 754 _xrayTxSeg: nil, 755 } 756 if svr.txMap == nil { 757 svr.txMap = make(map[string]*MySqlTransaction) 758 } 759 svr.txMap[myTx.id] = myTx 760 return myTx, nil 761 } 762 } else { 763 // begin transaction on database 764 xseg := xray.NewSegment("MySql-Transaction", svr._parentSegment) 765 subXSeg := xseg.NewSubSegment("Begin-Transaction") 766 767 if tx, err := svr.db.BeginTxx(subXSeg.Ctx, &sql.TxOptions{Isolation: 0, ReadOnly: false}); err != nil { 768 // begin failed 769 _ = subXSeg.Seg.AddError(err) 770 _ = xseg.Seg.AddError(err) 771 subXSeg.Close() 772 xseg.Close() 773 return nil, err 774 } else { 775 // begin succeeded, create MySqlTransaction and return result 776 myTx := &MySqlTransaction{ 777 id: util.NewULID(), 778 parent: svr, 779 tx: tx, 780 closed: false, 781 _xrayTxSeg: xseg, 782 } 783 if svr.txMap == nil { 784 svr.txMap = make(map[string]*MySqlTransaction) 785 } 786 svr.txMap[myTx.id] = myTx 787 subXSeg.Close() 788 return myTx, nil 789 } 790 } 791 } 792 793 // ---------------------------------------------------------------------------------------------------------------- 794 // query and marshal to 'struct slice' or 'struct' helpers 795 // ---------------------------------------------------------------------------------------------------------------- 796 797 // GetStructSlice performs query with optional variadic parameters, and unmarshal result rows into target struct slice, 798 // in essence, each row of data is marshaled into the given struct, and multiple struct form the slice, 799 // such as: []Customer where each row represent a customer, and multiple customers being part of the slice 800 // [ Parameters ] 801 // 802 // dest = pointer to the struct slice or address of struct slice, this is the result of rows to be marshaled into struct slice 803 // query = sql query, optionally having parameters marked as ?, where each represents a parameter position 804 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 805 // 806 // [ Return Values ] 807 // 1. notFound = indicates no rows found in query (aka sql.ErrNoRows), if error is detected, notFound is always false 808 // 2. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and dest is nil) 809 // 810 // [ Notes ] 811 // 1. if error == nil, and len(dest struct slice) == 0 then zero struct slice result 812 func (svr *MySql) GetStructSlice(dest interface{}, query string, args ...interface{}) (notFound bool, retErr error) { 813 // verify if the database connection is good 814 if err := svr.Ping(); err != nil { 815 return false, err 816 } 817 818 // perform select action, and unmarshal result rows into target struct slice 819 var err error 820 821 // not in transaction mode 822 // query using db object 823 if !xray.XRayServiceOn() { 824 err = svr.db.Select(dest, query, args...) 825 } else { 826 trace := xray.NewSegment("MySql-Select-GetStructSlice", svr._parentSegment) 827 defer trace.Close() 828 defer func() { 829 _ = trace.Seg.AddMetadata("SQL-Query", query) 830 _ = trace.Seg.AddMetadata("SQL-Param-Values", args) 831 _ = trace.Seg.AddMetadata("Struct-Slice-Result", dest) 832 if err != nil { 833 _ = trace.Seg.AddError(err) 834 } 835 }() 836 837 err = svr.db.SelectContext(trace.Ctx, dest, query, args...) 838 } 839 840 // if err is sql.ErrNoRows then treat as no error 841 if err != nil && err == sql.ErrNoRows { 842 notFound = true 843 dest = nil 844 err = nil 845 } else { 846 notFound = false 847 } 848 849 // return error 850 return notFound, err 851 } 852 853 // GetStructSlice performs query with optional variadic parameters, and unmarshal result rows into target struct slice, 854 // in essence, each row of data is marshaled into the given struct, and multiple struct form the slice, 855 // such as: []Customer where each row represent a customer, and multiple customers being part of the slice 856 // [ Parameters ] 857 // 858 // dest = pointer to the struct slice or address of struct slice, this is the result of rows to be marshaled into struct slice 859 // query = sql query, optionally having parameters marked as ?, where each represents a parameter position 860 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 861 // 862 // [ Return Values ] 863 // 1. notFound = indicates no rows found in query (aka sql.ErrNoRows), if error is detected, notFound is always false 864 // 2. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and dest is nil) 865 // 866 // [ Notes ] 867 // 1. if error == nil, and len(dest struct slice) == 0 then zero struct slice result 868 func (t *MySqlTransaction) GetStructSlice(dest interface{}, query string, args ...interface{}) (notFound bool, retErr error) { 869 if retErr = t.ready(); retErr != nil { 870 return false, retErr 871 } 872 873 // verify if the database connection is good 874 if retErr = t.parent.Ping(); retErr != nil { 875 return false, retErr 876 } 877 878 // perform select action, and unmarshal result rows into target struct slice 879 var err error 880 881 // in transaction mode 882 // query using tx object 883 if t._xrayTxSeg == nil { 884 err = t.tx.Select(dest, query, args...) 885 } else { 886 subTrace := t._xrayTxSeg.NewSubSegment("Transaction-Select-GetStructSlice") 887 defer subTrace.Close() 888 defer func() { 889 _ = subTrace.Seg.AddMetadata("SQL-Query", query) 890 _ = subTrace.Seg.AddMetadata("SQL-Param-Values", args) 891 _ = subTrace.Seg.AddMetadata("Struct-Slice-Result", dest) 892 if err != nil { 893 _ = subTrace.Seg.AddError(err) 894 } 895 }() 896 897 err = t.tx.SelectContext(subTrace.Ctx, dest, query, args...) 898 } 899 900 // if err is sql.ErrNoRows then treat as no error 901 if err != nil && err == sql.ErrNoRows { 902 notFound = true 903 dest = nil 904 err = nil 905 } else { 906 notFound = false 907 } 908 909 // return error 910 return notFound, err 911 } 912 913 // GetStruct performs query with optional variadic parameters, and unmarshal single result row into single target struct, 914 // such as: Customer struct where one row of data represent a customer 915 // [ Parameters ] 916 // 917 // dest = pointer to struct or address of struct, this is the result of row to be marshaled into this struct 918 // query = sql query, optionally having parameters marked as ?, where each represents a parameter position 919 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 920 // 921 // [ Return Values ] 922 // 1. notFound = indicates no rows found in query (aka sql.ErrNoRows), if error is detected, notFound is always false 923 // 2. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and dest is nil) 924 func (svr *MySql) GetStruct(dest interface{}, query string, args ...interface{}) (notFound bool, retErr error) { 925 // verify if the database connection is good 926 if err := svr.Ping(); err != nil { 927 return false, err 928 } 929 930 // perform select action, and unmarshal result row (single row) into target struct (single object) 931 var err error 932 933 // not in transaction mode 934 // query using db object 935 if !xray.XRayServiceOn() { 936 err = svr.db.Get(dest, query, args...) 937 } else { 938 trace := xray.NewSegment("MySql-Select-GetStruct", svr._parentSegment) 939 defer trace.Close() 940 defer func() { 941 _ = trace.Seg.AddMetadata("SQL-Query", query) 942 _ = trace.Seg.AddMetadata("SQL-Param-Values", args) 943 _ = trace.Seg.AddMetadata("Struct-Result", dest) 944 if err != nil { 945 _ = trace.Seg.AddError(err) 946 } 947 }() 948 949 err = svr.db.GetContext(trace.Ctx, dest, query, args...) 950 } 951 952 // if err is sql.ErrNoRows then treat as no error 953 if err != nil && err == sql.ErrNoRows { 954 notFound = true 955 dest = nil 956 err = nil 957 } else { 958 notFound = false 959 } 960 961 // return error 962 return notFound, err 963 } 964 965 // GetStruct performs query with optional variadic parameters, and unmarshal single result row into single target struct, 966 // such as: Customer struct where one row of data represent a customer 967 // [ Parameters ] 968 // 969 // dest = pointer to struct or address of struct, this is the result of row to be marshaled into this struct 970 // query = sql query, optionally having parameters marked as ?, where each represents a parameter position 971 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 972 // 973 // [ Return Values ] 974 // 1. notFound = indicates no rows found in query (aka sql.ErrNoRows), if error is detected, notFound is always false 975 // 2. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and dest is nil) 976 func (t *MySqlTransaction) GetStruct(dest interface{}, query string, args ...interface{}) (notFound bool, retErr error) { 977 if retErr = t.ready(); retErr != nil { 978 return false, retErr 979 } 980 981 // verify if the database connection is good 982 if retErr = t.parent.Ping(); retErr != nil { 983 return false, retErr 984 } 985 986 // perform select action, and unmarshal result row (single row) into target struct (single object) 987 var err error 988 989 // in transaction mode 990 // query using tx object 991 if t._xrayTxSeg == nil { 992 err = t.tx.Get(dest, query, args...) 993 } else { 994 subTrace := t._xrayTxSeg.NewSubSegment("Transaction-Select-GetStruct") 995 defer subTrace.Close() 996 defer func() { 997 _ = subTrace.Seg.AddMetadata("SQL-Query", query) 998 _ = subTrace.Seg.AddMetadata("SQL-Param-Values", args) 999 _ = subTrace.Seg.AddMetadata("Struct-Result", dest) 1000 if err != nil { 1001 _ = subTrace.Seg.AddError(err) 1002 } 1003 }() 1004 1005 err = t.tx.GetContext(subTrace.Ctx, dest, query, args...) 1006 } 1007 1008 // if err is sql.ErrNoRows then treat as no error 1009 if err != nil && err == sql.ErrNoRows { 1010 notFound = true 1011 dest = nil 1012 err = nil 1013 } else { 1014 notFound = false 1015 } 1016 1017 // return error 1018 return notFound, err 1019 } 1020 1021 // ---------------------------------------------------------------------------------------------------------------- 1022 // query and get rows helpers 1023 // ---------------------------------------------------------------------------------------------------------------- 1024 1025 // GetRowsByOrdinalParams performs query with optional variadic parameters to get ROWS of result, and returns *sqlx.Rows 1026 // [ Parameters ] 1027 // 1028 // query = sql query, optionally having parameters marked as ?, where each represents a parameter position 1029 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 1030 // 1031 // [ Return Values ] 1032 // 1. *sqlx.Rows = pointer to sqlx.Rows; or nil if no rows yielded 1033 // 2. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and sqlx.Rows is returned as nil) 1034 // 1035 // [ Ranged Loop & Scan ] 1036 // 1. to loop, use: 1037 // for { 1038 // if !rows.Next() { break } 1039 // rows.Scan(&x, &y, etc) 1040 // } 1041 // 2. to scan, use: r.Scan(&x, &y, ...), where r is the row struct in loop, where &x &y etc are the scanned output value (scan in order of select columns sequence) 1042 // 1043 // [ Continuous Loop & Scan ] 1044 // 1. Continuous loop until endOfRows = true is yielded from ScanSlice() or ScanStruct() 1045 // 2. ScanSlice(): accepts *sqlx.Rows, scans rows result into target pointer slice (if no error, endOfRows = true is returned) 1046 // 3. ScanStruct(): accepts *sqlx.Rows, scans current single row result into target pointer struct, returns endOfRows as true of false; if endOfRows = true, loop should stop 1047 func (svr *MySql) GetRowsByOrdinalParams(query string, args ...interface{}) (*sqlx.Rows, error) { 1048 // verify if the database connection is good 1049 if err := svr.Ping(); err != nil { 1050 return nil, err 1051 } 1052 1053 // perform select action, and return sqlx rows 1054 var rows *sqlx.Rows 1055 var err error 1056 1057 // not in transaction mode 1058 // query using db object 1059 if !xray.XRayServiceOn() { 1060 rows, err = svr.db.Queryx(query, args...) 1061 } else { 1062 trace := xray.NewSegment("MySql-Select-GetRowsByOrdinalParams", svr._parentSegment) 1063 defer trace.Close() 1064 defer func() { 1065 _ = trace.Seg.AddMetadata("SQL-Query", query) 1066 _ = trace.Seg.AddMetadata("SQL-Param-Values", args) 1067 _ = trace.Seg.AddMetadata("Rows-Result", rows) 1068 if err != nil { 1069 _ = trace.Seg.AddError(err) 1070 } 1071 }() 1072 1073 rows, err = svr.db.QueryxContext(trace.Ctx, query, args...) 1074 } 1075 1076 // if err is sql.ErrNoRows then treat as no error 1077 if err != nil && err == sql.ErrNoRows { 1078 rows = nil 1079 err = nil 1080 } 1081 1082 // return result 1083 return rows, err 1084 } 1085 1086 // GetRowsByOrdinalParams performs query with optional variadic parameters to get ROWS of result, and returns *sqlx.Rows 1087 // [ Parameters ] 1088 // 1089 // query = sql query, optionally having parameters marked as ?, where each represents a parameter position 1090 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 1091 // 1092 // [ Return Values ] 1093 // 1. *sqlx.Rows = pointer to sqlx.Rows; or nil if no rows yielded 1094 // 2. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and sqlx.Rows is returned as nil) 1095 // 1096 // [ Ranged Loop & Scan ] 1097 // 1. to loop, use: 1098 // for { 1099 // if !rows.Next() { break } 1100 // rows.Scan(&x, &y, etc) 1101 // } 1102 // 2. to scan, use: r.Scan(&x, &y, ...), where r is the row struct in loop, where &x &y etc are the scanned output value (scan in order of select columns sequence) 1103 // 1104 // [ Continuous Loop & Scan ] 1105 // 1. Continuous loop until endOfRows = true is yielded from ScanSlice() or ScanStruct() 1106 // 2. ScanSlice(): accepts *sqlx.Rows, scans rows result into target pointer slice (if no error, endOfRows = true is returned) 1107 // 3. ScanStruct(): accepts *sqlx.Rows, scans current single row result into target pointer struct, returns endOfRows as true of false; if endOfRows = true, loop should stop 1108 func (t *MySqlTransaction) GetRowsByOrdinalParams(query string, args ...interface{}) (*sqlx.Rows, error) { 1109 if err := t.ready(); err != nil { 1110 return nil, err 1111 } 1112 1113 // verify if the database connection is good 1114 if err := t.parent.Ping(); err != nil { 1115 return nil, err 1116 } 1117 1118 // perform select action, and return sqlx rows 1119 var rows *sqlx.Rows 1120 var err error 1121 1122 // in transaction mode 1123 // query using tx object 1124 if t._xrayTxSeg == nil { 1125 rows, err = t.tx.Queryx(query, args...) 1126 } else { 1127 subTrace := t._xrayTxSeg.NewSubSegment("Transaction-Select-GetRowsByOrdinalParams") 1128 defer subTrace.Close() 1129 defer func() { 1130 _ = subTrace.Seg.AddMetadata("SQL-Query", query) 1131 _ = subTrace.Seg.AddMetadata("SQL-Param-Values", args) 1132 _ = subTrace.Seg.AddMetadata("Rows-Result", rows) 1133 if err != nil { 1134 _ = subTrace.Seg.AddError(err) 1135 } 1136 }() 1137 1138 rows, err = t.tx.QueryxContext(subTrace.Ctx, query, args...) 1139 } 1140 1141 // if err is sql.ErrNoRows then treat as no error 1142 if err != nil && err == sql.ErrNoRows { 1143 rows = nil 1144 err = nil 1145 } 1146 1147 // return result 1148 return rows, err 1149 } 1150 1151 // GetRowsByNamedMapParam performs query with named map containing parameters to get ROWS of result, and returns *sqlx.Rows 1152 // [ Syntax ] 1153 // 1. in sql = instead of defining ordinal parameters ?, each parameter in sql does not need to be ordinal, rather define with :xyz (must have : in front of param name), where xyz is name of parameter, such as :customerID 1154 // 2. in go = setup a map variable: var p = make(map[string]interface{}) 1155 // 3. in go = to set values into map variable: p["xyz"] = abc 1156 // where xyz is the parameter name matching the sql :xyz (do not include : in go map "xyz") 1157 // where abc is the value of the parameter value, whether string or other data types 1158 // note: in using map, just add additional map elements using the p["xyz"] = abc syntax 1159 // note: if parameter value can be a null, such as nullint, nullstring, use util.ToNullTime(), ToNullInt(), ToNullString(), etc. 1160 // 4. in go = when calling this function passing the map variable, simply pass the map variable p into the args parameter 1161 // 1162 // [ Parameters ] 1163 // 1164 // query = sql query, optionally having parameters marked as :xyz for each parameter name, where each represents a named parameter 1165 // args = required, the map variable of the named parameters 1166 // 1167 // [ Return Values ] 1168 // 1. *sqlx.Rows = pointer to sqlx.Rows; or nil if no rows yielded 1169 // 2. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and sqlx.Rows is returned as nil) 1170 // 1171 // [ Ranged Loop & Scan ] 1172 // 1. to loop, use: 1173 // for { 1174 // if !rows.Next() { break } 1175 // rows.Scan(&x, &y, etc) 1176 // } 1177 // 2. to scan, use: r.Scan(&x, &y, ...), where r is the row struct in loop, where &x &y etc are the scanned output value (scan in order of select columns sequence) 1178 // 1179 // [ Continuous Loop & Scan ] 1180 // 1. Continuous loop until endOfRows = true is yielded from ScanSlice() or ScanStruct() 1181 // 2. ScanSlice(): accepts *sqlx.Rows, scans rows result into target pointer slice (if no error, endOfRows = true is returned) 1182 // 3. ScanStruct(): accepts *sqlx.Rows, scans current single row result into target pointer struct, returns endOfRows as true of false; if endOfRows = true, loop should stop 1183 func (svr *MySql) GetRowsByNamedMapParam(query string, args map[string]interface{}) (*sqlx.Rows, error) { 1184 // verify if the database connection is good 1185 if err := svr.Ping(); err != nil { 1186 return nil, err 1187 } 1188 1189 // perform select action, and return sqlx rows 1190 var rows *sqlx.Rows 1191 var err error 1192 1193 // not in transaction mode 1194 // query using db object 1195 if !xray.XRayServiceOn() { 1196 rows, err = svr.db.NamedQuery(query, args) 1197 } else { 1198 trace := xray.NewSegment("MySql-Select-GetRowsByNamedMapParam", svr._parentSegment) 1199 defer trace.Close() 1200 defer func() { 1201 _ = trace.Seg.AddMetadata("SQL-Query", query) 1202 _ = trace.Seg.AddMetadata("SQL-Param-Values", args) 1203 _ = trace.Seg.AddMetadata("Rows-Result", rows) 1204 if err != nil { 1205 _ = trace.Seg.AddError(err) 1206 } 1207 }() 1208 1209 rows, err = svr.db.NamedQueryContext(trace.Ctx, query, args) 1210 } 1211 1212 if err != nil && err == sql.ErrNoRows { 1213 // no rows 1214 rows = nil 1215 err = nil 1216 } 1217 1218 // return result 1219 return rows, err 1220 } 1221 1222 // GetRowsByNamedMapParam performs query with named map containing parameters to get ROWS of result, and returns *sqlx.Rows 1223 // [ Syntax ] 1224 // 1. in sql = instead of defining ordinal parameters ?, each parameter in sql does not need to be ordinal, rather define with :xyz (must have : in front of param name), where xyz is name of parameter, such as :customerID 1225 // 2. in go = setup a map variable: var p = make(map[string]interface{}) 1226 // 3. in go = to set values into map variable: p["xyz"] = abc 1227 // where xyz is the parameter name matching the sql :xyz (do not include : in go map "xyz") 1228 // where abc is the value of the parameter value, whether string or other data types 1229 // note: in using map, just add additional map elements using the p["xyz"] = abc syntax 1230 // note: if parameter value can be a null, such as nullint, nullstring, use util.ToNullTime(), ToNullInt(), ToNullString(), etc. 1231 // 4. in go = when calling this function passing the map variable, simply pass the map variable p into the args parameter 1232 // 1233 // [ Parameters ] 1234 // 1235 // query = sql query, optionally having parameters marked as :xyz for each parameter name, where each represents a named parameter 1236 // args = required, the map variable of the named parameters 1237 // 1238 // [ Return Values ] 1239 // 1. *sqlx.Rows = pointer to sqlx.Rows; or nil if no rows yielded 1240 // 2. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and sqlx.Rows is returned as nil) 1241 // 1242 // [ Ranged Loop & Scan ] 1243 // 1. to loop, use: 1244 // for { 1245 // if !rows.Next() { break } 1246 // rows.Scan(&x, &y, etc) 1247 // } 1248 // 2. to scan, use: r.Scan(&x, &y, ...), where r is the row struct in loop, where &x &y etc are the scanned output value (scan in order of select columns sequence) 1249 // 1250 // [ Continuous Loop & Scan ] 1251 // 1. Continuous loop until endOfRows = true is yielded from ScanSlice() or ScanStruct() 1252 // 2. ScanSlice(): accepts *sqlx.Rows, scans rows result into target pointer slice (if no error, endOfRows = true is returned) 1253 // 3. ScanStruct(): accepts *sqlx.Rows, scans current single row result into target pointer struct, returns endOfRows as true of false; if endOfRows = true, loop should stop 1254 func (t *MySqlTransaction) GetRowsByNamedMapParam(query string, args map[string]interface{}) (*sqlx.Rows, error) { 1255 if err := t.ready(); err != nil { 1256 return nil, err 1257 } 1258 1259 // verify if the database connection is good 1260 if err := t.parent.Ping(); err != nil { 1261 return nil, err 1262 } 1263 1264 // perform select action, and return sqlx rows 1265 var rows *sqlx.Rows 1266 var err error 1267 1268 // in transaction mode 1269 // query using tx object 1270 if t._xrayTxSeg == nil { 1271 rows, err = t.tx.NamedQuery(query, args) 1272 } else { 1273 subTrace := t._xrayTxSeg.NewSubSegment("Transaction-Select-GetRowsByNamedMapParam") 1274 defer subTrace.Close() 1275 defer func() { 1276 _ = subTrace.Seg.AddMetadata("SQL-Query", query) 1277 _ = subTrace.Seg.AddMetadata("SQL-Param-Values", args) 1278 _ = subTrace.Seg.AddMetadata("Rows-Result", rows) 1279 if err != nil { 1280 _ = subTrace.Seg.AddError(err) 1281 } 1282 }() 1283 1284 rows, err = sqlx.NamedQueryContext(subTrace.Ctx, t.tx, query, args) 1285 } 1286 1287 if err != nil && err == sql.ErrNoRows { 1288 // no rows 1289 rows = nil 1290 err = nil 1291 } 1292 1293 // return result 1294 return rows, err 1295 } 1296 1297 // GetRowsByStructParam performs query with a struct as parameter input to get ROWS of result, and returns *sqlx.Rows 1298 // [ Syntax ] 1299 // 1. in sql = instead of defining ordinal parameters ?, each parameter in sql does not need to be ordinal, rather define with :xyz (must have : in front of param name), where xyz is name of parameter, such as :customerID 1300 // 2. in sql = important: the :xyz defined where xyz portion of parameter name must batch the struct tag's `db:"xyz"` 1301 // 3. in go = a struct containing struct tags that matches the named parameters will be set with values, and passed into this function's args parameter input 1302 // 4. in go = when calling this function passing the struct variable, simply pass the struct variable into the args parameter 1303 // 1304 // [ Parameters ] 1305 // 1306 // query = sql query, optionally having parameters marked as :xyz for each parameter name, where each represents a named parameter 1307 // args = required, the struct variable where struct fields' struct tags match to the named parameters 1308 // 1309 // [ Return Values ] 1310 // 1. *sqlx.Rows = pointer to sqlx.Rows; or nil if no rows yielded 1311 // 2. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and sqlx.Rows is returned as nil) 1312 // 1313 // [ Ranged Loop & Scan ] 1314 // 1. to loop, use: 1315 // for { 1316 // if !rows.Next() { break } 1317 // rows.Scan(&x, &y, etc) 1318 // } 1319 // 2. to scan, use: r.Scan(&x, &y, ...), where r is the row struct in loop, where &x &y etc are the scanned output value (scan in order of select columns sequence) 1320 // 1321 // [ Continuous Loop & Scan ] 1322 // 1. Continuous loop until endOfRows = true is yielded from ScanSlice() or ScanStruct() 1323 // 2. ScanSlice(): accepts *sqlx.Rows, scans rows result into target pointer slice (if no error, endOfRows = true is returned) 1324 // 3. ScanStruct(): accepts *sqlx.Rows, scans current single row result into target pointer struct, returns endOfRows as true of false; if endOfRows = true, loop should stop 1325 func (svr *MySql) GetRowsByStructParam(query string, args interface{}) (*sqlx.Rows, error) { 1326 // verify if the database connection is good 1327 if err := svr.Ping(); err != nil { 1328 return nil, err 1329 } 1330 1331 // perform select action, and return sqlx rows 1332 var rows *sqlx.Rows 1333 var err error 1334 1335 // not in transaction mode 1336 // query using db object 1337 if !xray.XRayServiceOn() { 1338 rows, err = svr.db.NamedQuery(query, args) 1339 } else { 1340 trace := xray.NewSegment("MySql-Select-GetRowsByStructParam", svr._parentSegment) 1341 defer trace.Close() 1342 defer func() { 1343 _ = trace.Seg.AddMetadata("SQL-Query", query) 1344 _ = trace.Seg.AddMetadata("SQL-Param-Values", args) 1345 _ = trace.Seg.AddMetadata("Rows-Result", rows) 1346 if err != nil { 1347 _ = trace.Seg.AddError(err) 1348 } 1349 }() 1350 1351 rows, err = svr.db.NamedQueryContext(trace.Ctx, query, args) 1352 } 1353 1354 if err != nil && err == sql.ErrNoRows { 1355 // no rows 1356 rows = nil 1357 err = nil 1358 } 1359 1360 // return result 1361 return rows, err 1362 } 1363 1364 // GetRowsByStructParam performs query with a struct as parameter input to get ROWS of result, and returns *sqlx.Rows 1365 // [ Syntax ] 1366 // 1. in sql = instead of defining ordinal parameters ?, each parameter in sql does not need to be ordinal, rather define with :xyz (must have : in front of param name), where xyz is name of parameter, such as :customerID 1367 // 2. in sql = important: the :xyz defined where xyz portion of parameter name must batch the struct tag's `db:"xyz"` 1368 // 3. in go = a struct containing struct tags that matches the named parameters will be set with values, and passed into this function's args parameter input 1369 // 4. in go = when calling this function passing the struct variable, simply pass the struct variable into the args parameter 1370 // 1371 // [ Parameters ] 1372 // 1373 // query = sql query, optionally having parameters marked as :xyz for each parameter name, where each represents a named parameter 1374 // args = required, the struct variable where struct fields' struct tags match to the named parameters 1375 // 1376 // [ Return Values ] 1377 // 1. *sqlx.Rows = pointer to sqlx.Rows; or nil if no rows yielded 1378 // 2. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and sqlx.Rows is returned as nil) 1379 // 1380 // [ Ranged Loop & Scan ] 1381 // 1. to loop, use: 1382 // for { 1383 // if !rows.Next() { break } 1384 // rows.Scan(&x, &y, etc) 1385 // } 1386 // 2. to scan, use: r.Scan(&x, &y, ...), where r is the row struct in loop, where &x &y etc are the scanned output value (scan in order of select columns sequence) 1387 // 1388 // [ Continuous Loop & Scan ] 1389 // 1. Continuous loop until endOfRows = true is yielded from ScanSlice() or ScanStruct() 1390 // 2. ScanSlice(): accepts *sqlx.Rows, scans rows result into target pointer slice (if no error, endOfRows = true is returned) 1391 // 3. ScanStruct(): accepts *sqlx.Rows, scans current single row result into target pointer struct, returns endOfRows as true of false; if endOfRows = true, loop should stop 1392 func (t *MySqlTransaction) GetRowsByStructParam(query string, args interface{}) (*sqlx.Rows, error) { 1393 if err := t.ready(); err != nil { 1394 return nil, err 1395 } 1396 1397 // verify if the database connection is good 1398 if err := t.parent.Ping(); err != nil { 1399 return nil, err 1400 } 1401 1402 // perform select action, and return sqlx rows 1403 var rows *sqlx.Rows 1404 var err error 1405 1406 // in transaction mode 1407 // query using tx object 1408 if t._xrayTxSeg == nil { 1409 rows, err = t.tx.NamedQuery(query, args) 1410 } else { 1411 subTrace := t._xrayTxSeg.NewSubSegment("Transaction-Select-GetRowsByStructParam") 1412 defer subTrace.Close() 1413 defer func() { 1414 _ = subTrace.Seg.AddMetadata("SQL-Query", query) 1415 _ = subTrace.Seg.AddMetadata("SQL-Param-Values", args) 1416 _ = subTrace.Seg.AddMetadata("Rows-Result", rows) 1417 if err != nil { 1418 _ = subTrace.Seg.AddError(err) 1419 } 1420 }() 1421 1422 rows, err = sqlx.NamedQueryContext(subTrace.Ctx, t.tx, query, args) 1423 } 1424 1425 if err != nil && err == sql.ErrNoRows { 1426 // no rows 1427 rows = nil 1428 err = nil 1429 } 1430 1431 // return result 1432 return rows, err 1433 } 1434 1435 // ---------------------------------------------------------------------------------------------------------------- 1436 // scan row data and marshal to 'slice' or 'struct' helpers 1437 // ---------------------------------------------------------------------------------------------------------------- 1438 1439 // ScanSlice takes in *sqlx.Rows as parameter, will invoke the rows.Next() to advance to next row position, 1440 // and marshals current row's column values into a pointer reference to a slice, 1441 // this enables us to quickly retrieve a slice of current row column values without knowing how many columns or names or columns (columns appear in select columns sequence), 1442 // to loop thru all rows, use range, and loop until endOfRows = true; the dest is nil if no columns found; the dest is pointer of slice when columns exists 1443 // [ Parameters ] 1444 // 1445 // rows = *sqlx.Rows 1446 // dest = pointer or address to slice, such as: variable to "*[]string", or variable to "&cList for declaration cList []string" 1447 // 1448 // [ Return Values ] 1449 // 1. endOfRows = true if this action call yielded end of rows, meaning stop further processing of current loop (rows will be closed automatically) 1450 // 2. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and dest is set as nil) 1451 func (svr *MySql) ScanSlice(rows *sqlx.Rows, dest []interface{}) (endOfRows bool, err error) { 1452 // ensure rows pointer is set 1453 if rows == nil { 1454 return true, nil 1455 } 1456 1457 // call rows.Next() first to position the row 1458 if !xray.XRayServiceOn() { 1459 if rows.Next() { 1460 // now slice scan 1461 dest, err = rows.SliceScan() 1462 1463 // if err is sql.ErrNoRows then treat as no error 1464 if err != nil && err == sql.ErrNoRows { 1465 endOfRows = true 1466 dest = nil 1467 err = nil 1468 _ = rows.Close() 1469 return 1470 } 1471 1472 if err != nil { 1473 // has error 1474 endOfRows = false // although error but may not be at end of rows 1475 dest = nil 1476 return 1477 } 1478 1479 // slice scan success, but may not be at end of rows 1480 // exit function, and inform caller not endOfRows 1481 return false, nil 1482 } else { 1483 endOfRows = true 1484 dest = nil 1485 err = nil 1486 _ = rows.Close() 1487 } 1488 } else { 1489 trace := xray.NewSegment("MySql-InMemory-ScanSlice", svr._parentSegment) 1490 defer trace.Close() 1491 defer func() { 1492 if err != nil { 1493 _ = trace.Seg.AddError(err) 1494 } 1495 }() 1496 1497 trace.Capture("ScanSlice-Do", func() error { 1498 if rows.Next() { 1499 // now slice scan 1500 dest, err = rows.SliceScan() 1501 1502 // if err is sql.ErrNoRows then treat as no error 1503 if err != nil && err == sql.ErrNoRows { 1504 endOfRows = true 1505 dest = nil 1506 err = nil 1507 _ = rows.Close() 1508 return nil 1509 } 1510 1511 if err != nil { 1512 // has error 1513 endOfRows = false // although error but may not be at end of rows 1514 dest = nil 1515 return err 1516 } 1517 1518 // slice scan success, but may not be at end of rows 1519 // exit function, and inform caller not endOfRows 1520 endOfRows = false 1521 return nil 1522 } else { 1523 endOfRows = true 1524 dest = nil 1525 err = nil 1526 _ = rows.Close() 1527 return nil 1528 } 1529 }, &xray.XTraceData{ 1530 Meta: map[string]interface{}{ 1531 "Rows-To-Scan": rows, 1532 "Slice-Result": dest, 1533 "End-Of-Rows": endOfRows, 1534 }, 1535 }) 1536 } 1537 1538 // no more rows 1539 return endOfRows, err 1540 } 1541 1542 // ScanStruct takes in *sqlx.Rows, will invoke the rows.Next() to advance to next row position, 1543 // and marshals current row's column values into a pointer reference to a struct, 1544 // the struct fields and row columns must match for both name and sequence position, 1545 // this enables us to quickly convert the row's columns into a defined struct automatically, 1546 // to loop thru all rows, use range, and loop until endOfRows = true; the dest is nil if no columns found; the dest is pointer of struct when mapping is complete 1547 // [ Parameters ] 1548 // 1549 // rows = *sqlx.Rows 1550 // dest = pointer or address to struct, such as: variable to "*Customer", or variable to "&c for declaration c Customer" 1551 // 1552 // [ Return Values ] 1553 // 1. endOfRows = true if this action call yielded end of rows, meaning stop further processing of current loop (rows will be closed automatically) 1554 // 2. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and dest is set as nil) 1555 func (svr *MySql) ScanStruct(rows *sqlx.Rows, dest interface{}) (endOfRows bool, err error) { 1556 // ensure rows pointer is set 1557 if rows == nil { 1558 return true, nil 1559 } 1560 1561 // call rows.Next() first to position the row 1562 if !xray.XRayServiceOn() { 1563 if rows.Next() { 1564 // now struct scan 1565 err = rows.StructScan(dest) 1566 1567 // if err is sql.ErrNoRows then treat as no error 1568 if err != nil && err == sql.ErrNoRows { 1569 endOfRows = true 1570 dest = nil 1571 err = nil 1572 _ = rows.Close() 1573 return 1574 } 1575 1576 if err != nil { 1577 // has error 1578 endOfRows = false // although error but may not be at end of rows 1579 dest = nil 1580 return 1581 } 1582 1583 // struct scan successful, but may not be at end of rows 1584 return false, nil 1585 } else { 1586 endOfRows = true 1587 dest = nil 1588 err = nil 1589 _ = rows.Close() 1590 } 1591 } else { 1592 trace := xray.NewSegment("MySql-InMemory-ScanStruct", svr._parentSegment) 1593 defer trace.Close() 1594 defer func() { 1595 if err != nil { 1596 _ = trace.Seg.AddError(err) 1597 } 1598 }() 1599 1600 trace.Capture("ScanStruct-Do", func() error { 1601 if rows.Next() { 1602 // now struct scan 1603 err = rows.StructScan(dest) 1604 1605 // if err is sql.ErrNoRows then treat as no error 1606 if err != nil && err == sql.ErrNoRows { 1607 endOfRows = true 1608 dest = nil 1609 err = nil 1610 _ = rows.Close() 1611 return nil 1612 } 1613 1614 if err != nil { 1615 // has error 1616 endOfRows = false // although error but may not be at end of rows 1617 dest = nil 1618 return err 1619 } 1620 1621 // struct scan successful, but may not be at end of rows 1622 endOfRows = false 1623 return nil 1624 } else { 1625 endOfRows = true 1626 dest = nil 1627 err = nil 1628 _ = rows.Close() 1629 return nil 1630 } 1631 }, &xray.XTraceData{ 1632 Meta: map[string]interface{}{ 1633 "Rows-To-Scan": rows, 1634 "Struct-Result": dest, 1635 "End-Of-Rows": endOfRows, 1636 }, 1637 }) 1638 } 1639 1640 // no more rows 1641 return endOfRows, err 1642 } 1643 1644 // ---------------------------------------------------------------------------------------------------------------- 1645 // query for single row helper 1646 // ---------------------------------------------------------------------------------------------------------------- 1647 1648 // GetSingleRow performs query with optional variadic parameters to get a single ROW of result, and returns *sqlx.Row (This function returns SINGLE ROW) 1649 // [ Parameters ] 1650 // 1651 // query = sql query, optionally having parameters marked as ?, where each represents a parameter position 1652 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 1653 // 1654 // [ Return Values ] 1655 // 1. *sqlx.Row = pointer to sqlx.Row; or nil if no row yielded 1656 // 2. if error != nil, then error is encountered (if error = sql.ErrNoRows, then error is treated as nil, and sqlx.Row is returned as nil) 1657 // 1658 // [ Scan Values ] 1659 // 1. Use row.Scan() and pass in pointer or address of variable to receive scanned value outputs (Scan is in the order of column sequences in select statement) 1660 // 1661 // [ WARNING !!! ] 1662 // 1663 // WHEN USING Scan(), MUST CHECK Scan Result Error for sql.ErrNoRow status 1664 // SUGGESTED TO USE ScanColumnsByRow() Instead of Scan() 1665 func (svr *MySql) GetSingleRow(query string, args ...interface{}) (*sqlx.Row, error) { 1666 // verify if the database connection is good 1667 if err := svr.Ping(); err != nil { 1668 return nil, err 1669 } 1670 1671 // perform select action, and return sqlx row 1672 var row *sqlx.Row 1673 var err error 1674 1675 // not in transaction mode 1676 // query using db object 1677 if !xray.XRayServiceOn() { 1678 row = svr.db.QueryRowx(query, args...) 1679 } else { 1680 trace := xray.NewSegment("MySql-Select-GetSingleRow", svr._parentSegment) 1681 defer trace.Close() 1682 defer func() { 1683 _ = trace.Seg.AddMetadata("SQL-Query", query) 1684 _ = trace.Seg.AddMetadata("SQL-Param-Values", args) 1685 _ = trace.Seg.AddMetadata("Row-Result", row) 1686 if err != nil { 1687 _ = trace.Seg.AddError(err) 1688 } 1689 }() 1690 1691 row = svr.db.QueryRowxContext(trace.Ctx, query, args...) 1692 } 1693 1694 if row == nil { 1695 err = errors.New("No Row Data Found From Query") 1696 } else { 1697 err = row.Err() 1698 1699 if err != nil { 1700 if err == sql.ErrNoRows { 1701 // no rows 1702 row = nil 1703 err = nil 1704 } else { 1705 // has error 1706 row = nil 1707 } 1708 } 1709 } 1710 1711 // return result 1712 return row, err 1713 } 1714 1715 // GetSingleRow performs query with optional variadic parameters to get a single ROW of result, and returns *sqlx.Row (This function returns SINGLE ROW) 1716 // [ Parameters ] 1717 // 1718 // query = sql query, optionally having parameters marked as ?, where each represents a parameter position 1719 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 1720 // 1721 // [ Return Values ] 1722 // 1. *sqlx.Row = pointer to sqlx.Row; or nil if no row yielded 1723 // 2. if error != nil, then error is encountered (if error = sql.ErrNoRows, then error is treated as nil, and sqlx.Row is returned as nil) 1724 // 1725 // [ Scan Values ] 1726 // 1. Use row.Scan() and pass in pointer or address of variable to receive scanned value outputs (Scan is in the order of column sequences in select statement) 1727 // 1728 // [ WARNING !!! ] 1729 // 1730 // WHEN USING Scan(), MUST CHECK Scan Result Error for sql.ErrNoRow status 1731 // SUGGESTED TO USE ScanColumnsByRow() Instead of Scan() 1732 func (t *MySqlTransaction) GetSingleRow(query string, args ...interface{}) (*sqlx.Row, error) { 1733 if err := t.ready(); err != nil { 1734 return nil, err 1735 } 1736 1737 // verify if the database connection is good 1738 if err := t.parent.Ping(); err != nil { 1739 return nil, err 1740 } 1741 1742 // perform select action, and return sqlx row 1743 var row *sqlx.Row 1744 var err error 1745 1746 // in transaction mode 1747 // query using tx object 1748 if t._xrayTxSeg == nil { 1749 row = t.tx.QueryRowx(query, args...) 1750 } else { 1751 subTrace := t._xrayTxSeg.NewSubSegment("Transaction-Select-GetSingleRow") 1752 defer subTrace.Close() 1753 defer func() { 1754 _ = subTrace.Seg.AddMetadata("SQL-Query", query) 1755 _ = subTrace.Seg.AddMetadata("SQL-Param-Values", args) 1756 _ = subTrace.Seg.AddMetadata("Row-Result", row) 1757 if err != nil { 1758 _ = subTrace.Seg.AddError(err) 1759 } 1760 }() 1761 1762 row = t.tx.QueryRowxContext(subTrace.Ctx, query, args...) 1763 } 1764 1765 if row == nil { 1766 err = errors.New("No Row Data Found From Query") 1767 } else { 1768 err = row.Err() 1769 1770 if err != nil { 1771 if err == sql.ErrNoRows { 1772 // no rows 1773 row = nil 1774 err = nil 1775 } else { 1776 // has error 1777 row = nil 1778 } 1779 } 1780 } 1781 1782 // return result 1783 return row, err 1784 } 1785 1786 // ---------------------------------------------------------------------------------------------------------------- 1787 // scan single row data and marshal to 'slice' or 'struct' or specific fields, or scan columns helpers 1788 // ---------------------------------------------------------------------------------------------------------------- 1789 1790 // ScanSliceByRow takes in *sqlx.Row as parameter, and marshals current row's column values into a pointer reference to a slice, 1791 // this enables us to quickly retrieve a slice of current row column values without knowing how many columns or names or columns (columns appear in select columns sequence) 1792 // [ Parameters ] 1793 // 1794 // row = *sqlx.Row 1795 // dest = pointer or address to slice, such as: variable to "*[]string", or variable to "&cList for declaration cList []string" 1796 // 1797 // [ Return Values ] 1798 // 1. notFound = true if no row is found in current scan 1799 // 2. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and dest is set as nil and notFound is true) 1800 func (svr *MySql) ScanSliceByRow(row *sqlx.Row, dest []interface{}) (notFound bool, err error) { 1801 // if row is nil, treat as no row and not an error 1802 if row == nil { 1803 dest = nil 1804 return true, nil 1805 } 1806 1807 // perform slice scan on the given row 1808 if !xray.XRayServiceOn() { 1809 dest, err = row.SliceScan() 1810 1811 // if err is sql.ErrNoRows then treat as no error 1812 if err != nil && err == sql.ErrNoRows { 1813 dest = nil 1814 return true, nil 1815 } 1816 1817 if err != nil { 1818 // has error 1819 dest = nil 1820 return false, err // although error but may not be not found 1821 } 1822 1823 notFound = false 1824 err = nil 1825 } else { 1826 trace := xray.NewSegment("MySql-InMemory-ScanSliceByRow", svr._parentSegment) 1827 defer trace.Close() 1828 defer func() { 1829 if err != nil { 1830 _ = trace.Seg.AddError(err) 1831 } 1832 }() 1833 1834 trace.Capture("ScanSliceByRow-Do", func() error { 1835 dest, err = row.SliceScan() 1836 1837 // if err is sql.ErrNoRows then treat as no error 1838 if err != nil && err == sql.ErrNoRows { 1839 dest = nil 1840 notFound = true 1841 err = nil 1842 return nil 1843 } 1844 1845 if err != nil { 1846 // has error 1847 notFound = false 1848 dest = nil 1849 return err // although error but may not be not found 1850 } 1851 1852 notFound = false 1853 err = nil 1854 return nil 1855 }, &xray.XTraceData{ 1856 Meta: map[string]interface{}{ 1857 "Row-To-Scan": row, 1858 "Slice-Result": dest, 1859 }, 1860 }) 1861 } 1862 1863 // slice scan success 1864 return notFound, err 1865 } 1866 1867 // ScanStructByRow takes in *sqlx.Row, and marshals current row's column values into a pointer reference to a struct, 1868 // the struct fields and row columns must match for both name and sequence position, 1869 // this enables us to quickly convert the row's columns into a defined struct automatically, 1870 // the dest is nil if no columns found; the dest is pointer of struct when mapping is complete 1871 // [ Parameters ] 1872 // 1873 // row = *sqlx.Row 1874 // dest = pointer or address to struct, such as: variable to "*Customer", or variable to "&c for declaration c Customer" 1875 // 1876 // [ Return Values ] 1877 // 1. notFound = true if no row is found in current scan 1878 // 2. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and dest is set as nil and notFound is true) 1879 func (svr *MySql) ScanStructByRow(row *sqlx.Row, dest interface{}) (notFound bool, err error) { 1880 // if row is nil, treat as no row and not an error 1881 if row == nil { 1882 dest = nil 1883 return true, nil 1884 } 1885 1886 // now struct scan 1887 if !xray.XRayServiceOn() { 1888 err = row.StructScan(dest) 1889 1890 // if err is sql.ErrNoRows then treat as no error 1891 if err != nil && err == sql.ErrNoRows { 1892 dest = nil 1893 return true, nil 1894 } 1895 1896 if err != nil { 1897 // has error 1898 dest = nil 1899 return false, err // although error but may not be not found 1900 } 1901 1902 notFound = false 1903 err = nil 1904 } else { 1905 trace := xray.NewSegment("MySql-InMemory-ScanStructByRow", svr._parentSegment) 1906 defer trace.Close() 1907 defer func() { 1908 if err != nil { 1909 _ = trace.Seg.AddError(err) 1910 } 1911 }() 1912 1913 trace.Capture("ScanStructByRow-Do", func() error { 1914 err = row.StructScan(dest) 1915 1916 // if err is sql.ErrNoRows then treat as no error 1917 if err != nil && err == sql.ErrNoRows { 1918 dest = nil 1919 notFound = true 1920 err = nil 1921 return nil 1922 } 1923 1924 if err != nil { 1925 // has error 1926 dest = nil 1927 notFound = false 1928 return err // although error but may not be not found 1929 } 1930 1931 notFound = false 1932 err = nil 1933 return nil 1934 }, &xray.XTraceData{ 1935 Meta: map[string]interface{}{ 1936 "Row-To-Scan": row, 1937 "Struct-Result": dest, 1938 }, 1939 }) 1940 } 1941 1942 // struct scan successful 1943 return notFound, err 1944 } 1945 1946 // ScanColumnsByRow accepts a *sqlx row, and scans specific columns into dest outputs, 1947 // this is different than ScanSliceByRow or ScanStructByRow because this function allows specific extraction of column values into target fields, 1948 // (note: this function must extra all row column values to dest variadic parameters as present in the row parameter) 1949 // [ Parameters ] 1950 // 1951 // row = *sqlx.Row representing the row containing columns to extract, note that this function MUST extract all columns from this row 1952 // dest = MUST BE pointer (or &variable) to target variable to receive the column value, data type must match column data type value, and sequence of dest must be in the order of columns sequence 1953 // 1954 // [ Return Values ] 1955 // 1. notFound = true if no row is found in current scan 1956 // 2. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and dest is set as nil and notFound is true) 1957 // 1958 // [ Example ] 1959 // 1. assuming: Select CustomerID, CustomerName, Address FROM Customer Where CustomerPhone='123'; 1960 // 2. assuming: row // *sqlx.Row derived from GetSingleRow() or specific row from GetRowsByOrdinalParams() / GetRowsByNamedMapParam() / GetRowsByStructParam() 1961 // 3. assuming: var CustomerID int64 1962 // var CustomerName string 1963 // var Address string 1964 // 4. notFound, err := svr.ScanColumnsByRow(row, &CustomerID, &CustomerName, &Address) 1965 func (svr *MySql) ScanColumnsByRow(row *sqlx.Row, dest ...interface{}) (notFound bool, err error) { 1966 // if row is nil, treat as no row and not an error 1967 if row == nil { 1968 return true, nil 1969 } 1970 1971 // now scan columns from row 1972 if !xray.XRayServiceOn() { 1973 err = row.Scan(dest...) 1974 1975 // if err is sql.ErrNoRows then treat as no error 1976 if err != nil && err == sql.ErrNoRows { 1977 return true, nil 1978 } 1979 1980 if err != nil { 1981 // has error 1982 return false, err // although error but may not be not found 1983 } 1984 1985 notFound = false 1986 err = nil 1987 } else { 1988 trace := xray.NewSegment("MySql-InMemory-ScanColumnsByRow", svr._parentSegment) 1989 defer trace.Close() 1990 defer func() { 1991 if err != nil { 1992 _ = trace.Seg.AddError(err) 1993 } 1994 }() 1995 1996 trace.Capture("ScanColumnsByRow_Do", func() error { 1997 err = row.Scan(dest...) 1998 1999 // if err is sql.ErrNoRows then treat as no error 2000 if err != nil && err == sql.ErrNoRows { 2001 notFound = true 2002 err = nil 2003 return nil 2004 } 2005 2006 if err != nil { 2007 // has error 2008 notFound = false 2009 return err // although error but may not be not found 2010 } 2011 2012 notFound = false 2013 err = nil 2014 return nil 2015 }, &xray.XTraceData{ 2016 Meta: map[string]interface{}{ 2017 "Row-To-Scan": row, 2018 "Dest-Vars-Result": dest, 2019 }, 2020 }) 2021 } 2022 2023 // scan columns successful 2024 return notFound, err 2025 } 2026 2027 // ---------------------------------------------------------------------------------------------------------------- 2028 // query for single value in single row helpers 2029 // ---------------------------------------------------------------------------------------------------------------- 2030 2031 // GetScalarString performs query with optional variadic parameters, and returns the first row and first column value in string data type 2032 // [ Parameters ] 2033 // 2034 // query = sql query, optionally having parameters marked as ?, where each represents a parameter position 2035 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 2036 // 2037 // [ Return Values ] 2038 // 1. retVal = string value of scalar result, if no value, blank is returned 2039 // 2. retNotFound = now row found 2040 // 3. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and retVal is returned as blank) 2041 func (svr *MySql) GetScalarString(query string, args ...interface{}) (retVal string, retNotFound bool, retErr error) { 2042 // verify if the database connection is good 2043 if err := svr.Ping(); err != nil { 2044 return "", false, err 2045 } 2046 2047 // get row using query string and parameters 2048 var row *sqlx.Row 2049 2050 // not in transaction 2051 // use db object 2052 if !xray.XRayServiceOn() { 2053 row = svr.db.QueryRowx(query, args...) 2054 } else { 2055 trace := xray.NewSegment("MySql-Select-GetScalarString", svr._parentSegment) 2056 defer trace.Close() 2057 defer func() { 2058 _ = trace.Seg.AddMetadata("SQL-Query", query) 2059 _ = trace.Seg.AddMetadata("SQL-Param-Values", args) 2060 _ = trace.Seg.AddMetadata("Row-Result", row) 2061 if retErr != nil { 2062 _ = trace.Seg.AddError(retErr) 2063 } 2064 }() 2065 2066 row = svr.db.QueryRowxContext(trace.Ctx, query, args...) 2067 } 2068 2069 if row == nil { 2070 return "", false, errors.New("Scalar Query Yielded Empty Row") 2071 } else { 2072 retErr = row.Err() 2073 2074 if retErr != nil { 2075 if retErr == sql.ErrNoRows { 2076 // no rows 2077 retErr = nil 2078 return "", true, nil 2079 } else { 2080 // has error 2081 return "", false, retErr 2082 } 2083 } 2084 } 2085 2086 // get value via scan 2087 retErr = row.Scan(&retVal) 2088 2089 if retErr == sql.ErrNoRows { 2090 // no rows 2091 retErr = nil 2092 return "", true, nil 2093 } else { 2094 // return value 2095 return retVal, false, retErr 2096 } 2097 } 2098 2099 // GetScalarString performs query with optional variadic parameters, and returns the first row and first column value in string data type 2100 // [ Parameters ] 2101 // 2102 // query = sql query, optionally having parameters marked as ?, where each represents a parameter position 2103 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 2104 // 2105 // [ Return Values ] 2106 // 1. retVal = string value of scalar result, if no value, blank is returned 2107 // 2. retNotFound = now row found 2108 // 3. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and retVal is returned as blank) 2109 func (t *MySqlTransaction) GetScalarString(query string, args ...interface{}) (retVal string, retNotFound bool, retErr error) { 2110 if err := t.ready(); err != nil { 2111 return "", false, err 2112 } 2113 2114 // verify if the database connection is good 2115 if err := t.parent.Ping(); err != nil { 2116 return "", false, err 2117 } 2118 2119 // get row using query string and parameters 2120 var row *sqlx.Row 2121 2122 // in transaction 2123 // use tx object 2124 if t._xrayTxSeg == nil { 2125 row = t.tx.QueryRowx(query, args...) 2126 } else { 2127 subTrace := t._xrayTxSeg.NewSubSegment("Transaction-Select-GetScalarString") 2128 defer subTrace.Close() 2129 defer func() { 2130 _ = subTrace.Seg.AddMetadata("SQL-Query", query) 2131 _ = subTrace.Seg.AddMetadata("SQL-Param-Values", args) 2132 _ = subTrace.Seg.AddMetadata("Row-Result", row) 2133 if retErr != nil { 2134 _ = subTrace.Seg.AddError(retErr) 2135 } 2136 }() 2137 2138 row = t.tx.QueryRowxContext(subTrace.Ctx, query, args...) 2139 } 2140 2141 if row == nil { 2142 return "", false, errors.New("Scalar Query Yielded Empty Row") 2143 } else { 2144 retErr = row.Err() 2145 2146 if retErr != nil { 2147 if retErr == sql.ErrNoRows { 2148 // no rows 2149 retErr = nil 2150 return "", true, nil 2151 } else { 2152 // has error 2153 return "", false, retErr 2154 } 2155 } 2156 } 2157 2158 // get value via scan 2159 retErr = row.Scan(&retVal) 2160 2161 if retErr == sql.ErrNoRows { 2162 // no rows 2163 retErr = nil 2164 return "", true, nil 2165 } else { 2166 // return value 2167 return retVal, false, retErr 2168 } 2169 } 2170 2171 // GetScalarNullString performs query with optional variadic parameters, and returns the first row and first column value in sql.NullString{} data type 2172 // [ Parameters ] 2173 // 2174 // query = sql query, optionally having parameters marked as ?, where each represents a parameter position 2175 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 2176 // 2177 // [ Return Values ] 2178 // 1. retVal = string value of scalar result, if no value, sql.NullString{} is returned 2179 // 2. retNotFound = now row found 2180 // 3. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and retVal is returned as sql.NullString{}) 2181 func (svr *MySql) GetScalarNullString(query string, args ...interface{}) (retVal sql.NullString, retNotFound bool, retErr error) { 2182 // verify if the database connection is good 2183 if err := svr.Ping(); err != nil { 2184 return sql.NullString{}, false, err 2185 } 2186 2187 // get row using query string and parameters 2188 var row *sqlx.Row 2189 2190 // not in transaction 2191 // use db object 2192 if !xray.XRayServiceOn() { 2193 row = svr.db.QueryRowx(query, args...) 2194 } else { 2195 trace := xray.NewSegment("MySql-Select-GetScalarNullString", svr._parentSegment) 2196 defer trace.Close() 2197 defer func() { 2198 _ = trace.Seg.AddMetadata("SQL-Query", query) 2199 _ = trace.Seg.AddMetadata("SQL-Param-Values", args) 2200 _ = trace.Seg.AddMetadata("Row-Result", row) 2201 if retErr != nil { 2202 _ = trace.Seg.AddError(retErr) 2203 } 2204 }() 2205 2206 row = svr.db.QueryRowxContext(trace.Ctx, query, args...) 2207 } 2208 2209 if row == nil { 2210 retErr = errors.New("Scalar Query Yielded Empty Row") 2211 return sql.NullString{}, false, retErr 2212 } else { 2213 retErr = row.Err() 2214 2215 if retErr != nil { 2216 if retErr == sql.ErrNoRows { 2217 // no rows 2218 retErr = nil 2219 return sql.NullString{}, true, nil 2220 } else { 2221 // has error 2222 return sql.NullString{}, false, retErr 2223 } 2224 } 2225 } 2226 2227 // get value via scan 2228 retErr = row.Scan(&retVal) 2229 2230 if retErr == sql.ErrNoRows { 2231 // no rows 2232 retErr = nil 2233 return sql.NullString{}, true, nil 2234 } else { 2235 // return value 2236 return retVal, false, retErr 2237 } 2238 } 2239 2240 // GetScalarNullString performs query with optional variadic parameters, and returns the first row and first column value in sql.NullString{} data type 2241 // [ Parameters ] 2242 // 2243 // query = sql query, optionally having parameters marked as ?, where each represents a parameter position 2244 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 2245 // 2246 // [ Return Values ] 2247 // 1. retVal = string value of scalar result, if no value, sql.NullString{} is returned 2248 // 2. retNotFound = now row found 2249 // 3. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and retVal is returned as sql.NullString{}) 2250 func (t *MySqlTransaction) GetScalarNullString(query string, args ...interface{}) (retVal sql.NullString, retNotFound bool, retErr error) { 2251 if err := t.ready(); err != nil { 2252 return sql.NullString{}, false, err 2253 } 2254 2255 // verify if the database connection is good 2256 if err := t.parent.Ping(); err != nil { 2257 return sql.NullString{}, false, err 2258 } 2259 2260 // get row using query string and parameters 2261 var row *sqlx.Row 2262 2263 // in transaction 2264 // use tx object 2265 if t._xrayTxSeg == nil { 2266 row = t.tx.QueryRowx(query, args...) 2267 } else { 2268 subTrace := t._xrayTxSeg.NewSubSegment("Transaction-Select-GetScalarNullString") 2269 defer subTrace.Close() 2270 defer func() { 2271 _ = subTrace.Seg.AddMetadata("SQL-Query", query) 2272 _ = subTrace.Seg.AddMetadata("SQL-Param-Values", args) 2273 _ = subTrace.Seg.AddMetadata("Row-Result", row) 2274 if retErr != nil { 2275 _ = subTrace.Seg.AddError(retErr) 2276 } 2277 }() 2278 2279 row = t.tx.QueryRowxContext(subTrace.Ctx, query, args...) 2280 } 2281 2282 if row == nil { 2283 retErr = errors.New("Scalar Query Yielded Empty Row") 2284 return sql.NullString{}, false, retErr 2285 } else { 2286 retErr = row.Err() 2287 2288 if retErr != nil { 2289 if retErr == sql.ErrNoRows { 2290 // no rows 2291 retErr = nil 2292 return sql.NullString{}, true, nil 2293 } else { 2294 // has error 2295 return sql.NullString{}, false, retErr 2296 } 2297 } 2298 } 2299 2300 // get value via scan 2301 retErr = row.Scan(&retVal) 2302 2303 if retErr == sql.ErrNoRows { 2304 // no rows 2305 retErr = nil 2306 return sql.NullString{}, true, nil 2307 } else { 2308 // return value 2309 return retVal, false, retErr 2310 } 2311 } 2312 2313 // ---------------------------------------------------------------------------------------------------------------- 2314 // execute helpers 2315 // ---------------------------------------------------------------------------------------------------------------- 2316 2317 // ExecByOrdinalParams executes action query string and parameters to return result, if error, returns error object within result 2318 // [ Parameters ] 2319 // 2320 // actionQuery = sql action query, optionally having parameters marked as ?1, ?2 .. ?N, where each represents a parameter position 2321 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 2322 // 2323 // [ Return Values ] 2324 // 1. MySqlResult = represents the sql action result received (including error info if applicable) 2325 func (svr *MySql) ExecByOrdinalParams(actionQuery string, args ...interface{}) MySqlResult { 2326 // verify if the database connection is good 2327 if err := svr.Ping(); err != nil { 2328 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2329 } 2330 2331 // is new insertion? 2332 var isInsert bool 2333 2334 if strings.ToUpper(util.Left(actionQuery, 6)) == "INSERT" { 2335 isInsert = true 2336 } else { 2337 isInsert = false 2338 } 2339 2340 // perform exec action, and return to caller 2341 var result sql.Result 2342 var err error 2343 2344 if !xray.XRayServiceOn() { 2345 // not in transaction mode, 2346 // action using db object 2347 result, err = svr.db.Exec(actionQuery, args...) 2348 } else { 2349 // not in transaction mode, 2350 // action using db object 2351 trace := xray.NewSegment("MySql-Exec-ExecByOrdinalParams", svr._parentSegment) 2352 defer trace.Close() 2353 defer func() { 2354 _ = trace.Seg.AddMetadata("SQL-Query", actionQuery) 2355 _ = trace.Seg.AddMetadata("SQL-Param-Values", args) 2356 _ = trace.Seg.AddMetadata("Exec-Result", result) 2357 if err != nil { 2358 _ = trace.Seg.AddError(err) 2359 } 2360 }() 2361 2362 result, err = svr.db.ExecContext(trace.Ctx, actionQuery, args...) 2363 } 2364 2365 if err != nil { 2366 err = errors.New("ExecByOrdinalParams() Error: " + err.Error()) 2367 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2368 } 2369 2370 // if inserted, get last id if known 2371 var newID int64 2372 newID = 0 2373 2374 if isInsert { 2375 newID, err = result.LastInsertId() 2376 2377 if err != nil { 2378 err = errors.New("ExecByOrdinalParams() Get LastInsertId() Error: " + err.Error()) 2379 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2380 } 2381 } 2382 2383 // get rows affected by this action 2384 var affected int64 2385 affected = 0 2386 2387 affected, err = result.RowsAffected() 2388 2389 if err != nil { 2390 err = errors.New("ExecByOrdinalParams() Get RowsAffected() Error: " + err.Error()) 2391 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2392 } 2393 2394 // return result 2395 return MySqlResult{RowsAffected: affected, NewlyInsertedID: newID, Err: nil} 2396 } 2397 2398 // ExecByOrdinalParams executes action query string and parameters to return result, if error, returns error object within result 2399 // [ Parameters ] 2400 // 2401 // actionQuery = sql action query, optionally having parameters marked as ?1, ?2 .. ?N, where each represents a parameter position 2402 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 2403 // 2404 // [ Return Values ] 2405 // 1. MySqlResult = represents the sql action result received (including error info if applicable) 2406 func (t *MySqlTransaction) ExecByOrdinalParams(actionQuery string, args ...interface{}) MySqlResult { 2407 if err := t.ready(); err != nil { 2408 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2409 } 2410 2411 // verify if the database connection is good 2412 if err := t.parent.Ping(); err != nil { 2413 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2414 } 2415 2416 // is new insertion? 2417 var isInsert bool 2418 2419 if strings.ToUpper(util.Left(actionQuery, 6)) == "INSERT" { 2420 isInsert = true 2421 } else { 2422 isInsert = false 2423 } 2424 2425 // perform exec action, and return to caller 2426 var result sql.Result 2427 var err error 2428 2429 if t._xrayTxSeg == nil { 2430 // in transaction mode, 2431 // action using tx object 2432 result, err = t.tx.Exec(actionQuery, args...) 2433 } else { 2434 // in transaction mode, 2435 // action using tx object 2436 subTrace := t._xrayTxSeg.NewSubSegment("Transaction-Exec-ExecByOrdinalParams") 2437 defer subTrace.Close() 2438 defer func() { 2439 _ = subTrace.Seg.AddMetadata("SQL-Query", actionQuery) 2440 _ = subTrace.Seg.AddMetadata("SQL-Param-Values", args) 2441 _ = subTrace.Seg.AddMetadata("Exec-Result", result) 2442 if err != nil { 2443 _ = subTrace.Seg.AddError(err) 2444 } 2445 }() 2446 2447 result, err = t.tx.ExecContext(subTrace.Ctx, actionQuery, args...) 2448 } 2449 2450 if err != nil { 2451 err = errors.New("ExecByOrdinalParams() Error: " + err.Error()) 2452 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2453 } 2454 2455 // if inserted, get last id if known 2456 var newID int64 2457 newID = 0 2458 2459 if isInsert { 2460 newID, err = result.LastInsertId() 2461 2462 if err != nil { 2463 err = errors.New("ExecByOrdinalParams() Get LastInsertId() Error: " + err.Error()) 2464 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2465 } 2466 } 2467 2468 // get rows affected by this action 2469 var affected int64 2470 affected = 0 2471 2472 affected, err = result.RowsAffected() 2473 2474 if err != nil { 2475 err = errors.New("ExecByOrdinalParams() Get RowsAffected() Error: " + err.Error()) 2476 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2477 } 2478 2479 // return result 2480 return MySqlResult{RowsAffected: affected, NewlyInsertedID: newID, Err: nil} 2481 } 2482 2483 // ExecByNamedMapParam executes action query string with named map containing parameters to return result, if error, returns error object within result 2484 // [ Syntax ] 2485 // 1. in sql = instead of defining ordinal parameters ?, each parameter in sql does not need to be ordinal, rather define with :xyz (must have : in front of param name), where xyz is name of parameter, such as :customerID 2486 // 2. in go = setup a map variable: var p = make(map[string]interface{}) 2487 // 3. in go = to set values into map variable: p["xyz"] = abc 2488 // where xyz is the parameter name matching the sql :xyz (do not include : in go map "xyz") 2489 // where abc is the value of the parameter value, whether string or other data types 2490 // note: in using map, just add additional map elements using the p["xyz"] = abc syntax 2491 // note: if parameter value can be a null, such as nullint, nullstring, use util.ToNullTime(), ToNullInt(), ToNullString(), etc. 2492 // 4. in go = when calling this function passing the map variable, simply pass the map variable p into the args parameter 2493 // 2494 // [ Parameters ] 2495 // 2496 // actionQuery = sql action query, with named parameters using :xyz syntax 2497 // args = required, the map variable of the named parameters 2498 // 2499 // [ Return Values ] 2500 // 1. MySqlResult = represents the sql action result received (including error info if applicable) 2501 func (svr *MySql) ExecByNamedMapParam(actionQuery string, args map[string]interface{}) MySqlResult { 2502 // verify if the database connection is good 2503 if err := svr.Ping(); err != nil { 2504 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2505 } 2506 2507 // is new insertion? 2508 var isInsert bool 2509 2510 if strings.ToUpper(util.Left(actionQuery, 6)) == "INSERT" { 2511 isInsert = true 2512 } else { 2513 isInsert = false 2514 } 2515 2516 // perform exec action, and return to caller 2517 var result sql.Result 2518 var err error 2519 2520 if !xray.XRayServiceOn() { 2521 // not in transaction mode, 2522 // action using db object 2523 result, err = svr.db.NamedExec(actionQuery, args) 2524 } else { 2525 // not in transaction mode, 2526 // action using db object 2527 trace := xray.NewSegment("MySql-Exec-ExecByNamedMapParam", svr._parentSegment) 2528 defer trace.Close() 2529 defer func() { 2530 _ = trace.Seg.AddMetadata("SQL-Query", actionQuery) 2531 _ = trace.Seg.AddMetadata("SQL-Param-Values", args) 2532 _ = trace.Seg.AddMetadata("Exec-Result", result) 2533 if err != nil { 2534 _ = trace.Seg.AddError(err) 2535 } 2536 }() 2537 2538 result, err = svr.db.NamedExecContext(trace.Ctx, actionQuery, args) 2539 } 2540 2541 if err != nil { 2542 err = errors.New("ExecByNamedMapParam() Error: " + err.Error()) 2543 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2544 } 2545 2546 // if inserted, get last id if known 2547 var newID int64 2548 newID = 0 2549 2550 if isInsert { 2551 newID, err = result.LastInsertId() 2552 2553 if err != nil { 2554 err = errors.New("ExecByNamedMapParam() Get LastInsertId() Error: " + err.Error()) 2555 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2556 } 2557 } 2558 2559 // get rows affected by this action 2560 var affected int64 2561 affected = 0 2562 2563 affected, err = result.RowsAffected() 2564 2565 if err != nil { 2566 err = errors.New("ExecByNamedMapParam() Get RowsAffected() Error: " + err.Error()) 2567 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2568 } 2569 2570 // return result 2571 return MySqlResult{RowsAffected: affected, NewlyInsertedID: newID, Err: nil} 2572 } 2573 2574 // ExecByNamedMapParam executes action query string with named map containing parameters to return result, if error, returns error object within result 2575 // [ Syntax ] 2576 // 1. in sql = instead of defining ordinal parameters ?, each parameter in sql does not need to be ordinal, rather define with :xyz (must have : in front of param name), where xyz is name of parameter, such as :customerID 2577 // 2. in go = setup a map variable: var p = make(map[string]interface{}) 2578 // 3. in go = to set values into map variable: p["xyz"] = abc 2579 // where xyz is the parameter name matching the sql :xyz (do not include : in go map "xyz") 2580 // where abc is the value of the parameter value, whether string or other data types 2581 // note: in using map, just add additional map elements using the p["xyz"] = abc syntax 2582 // note: if parameter value can be a null, such as nullint, nullstring, use util.ToNullTime(), ToNullInt(), ToNullString(), etc. 2583 // 4. in go = when calling this function passing the map variable, simply pass the map variable p into the args parameter 2584 // 2585 // [ Parameters ] 2586 // 2587 // actionQuery = sql action query, with named parameters using :xyz syntax 2588 // args = required, the map variable of the named parameters 2589 // 2590 // [ Return Values ] 2591 // 1. MySqlResult = represents the sql action result received (including error info if applicable) 2592 func (t *MySqlTransaction) ExecByNamedMapParam(actionQuery string, args map[string]interface{}) MySqlResult { 2593 if err := t.ready(); err != nil { 2594 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2595 } 2596 2597 // verify if the database connection is good 2598 if err := t.parent.Ping(); err != nil { 2599 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2600 } 2601 2602 // is new insertion? 2603 var isInsert bool 2604 2605 if strings.ToUpper(util.Left(actionQuery, 6)) == "INSERT" { 2606 isInsert = true 2607 } else { 2608 isInsert = false 2609 } 2610 2611 // perform exec action, and return to caller 2612 var result sql.Result 2613 var err error 2614 2615 if t._xrayTxSeg == nil { 2616 // in transaction mode, 2617 // action using tx object 2618 result, err = t.tx.NamedExec(actionQuery, args) 2619 } else { 2620 // in transaction mode, 2621 // action using tx object 2622 subTrace := t._xrayTxSeg.NewSubSegment("Transaction-Exec-ExecByNamedMapParam") 2623 defer subTrace.Close() 2624 defer func() { 2625 _ = subTrace.Seg.AddMetadata("SQL-Query", actionQuery) 2626 _ = subTrace.Seg.AddMetadata("SQL-Param-Values", args) 2627 _ = subTrace.Seg.AddMetadata("Exec-Result", result) 2628 if err != nil { 2629 _ = subTrace.Seg.AddError(err) 2630 } 2631 }() 2632 2633 result, err = t.tx.NamedExecContext(subTrace.Ctx, actionQuery, args) 2634 2635 } 2636 2637 if err != nil { 2638 err = errors.New("ExecByNamedMapParam() Error: " + err.Error()) 2639 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2640 } 2641 2642 // if inserted, get last id if known 2643 var newID int64 2644 newID = 0 2645 2646 if isInsert { 2647 newID, err = result.LastInsertId() 2648 2649 if err != nil { 2650 err = errors.New("ExecByNamedMapParam() Get LastInsertId() Error: " + err.Error()) 2651 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2652 } 2653 } 2654 2655 // get rows affected by this action 2656 var affected int64 2657 affected = 0 2658 2659 affected, err = result.RowsAffected() 2660 2661 if err != nil { 2662 err = errors.New("ExecByNamedMapParam() Get RowsAffected() Error: " + err.Error()) 2663 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2664 } 2665 2666 // return result 2667 return MySqlResult{RowsAffected: affected, NewlyInsertedID: newID, Err: nil} 2668 } 2669 2670 // ExecByStructParam executes action query string with struct containing parameters to return result, if error, returns error object within result, 2671 // the struct fields' struct tags must match the parameter names, such as: struct tag `db:"customerID"` must match parameter name in sql as ":customerID" 2672 // [ Syntax ] 2673 // 1. in sql = instead of defining ordinal parameters ?, each parameter in sql does not need to be ordinal, rather define with :xyz (must have : in front of param name), where xyz is name of parameter, such as :customerID 2674 // 2. in go = using a struct to contain fields to match parameters, make sure struct tags match to the sql parameter names, such as struct tag `db:"customerID"` must match parameter name in sql as ":customerID" (the : is not part of the match) 2675 // 2676 // [ Parameters ] 2677 // 2678 // actionQuery = sql action query, with named parameters using :xyz syntax 2679 // args = required, the struct variable, whose fields having struct tags matching sql parameter names 2680 // 2681 // [ Return Values ] 2682 // 1. MySqlResult = represents the sql action result received (including error info if applicable) 2683 func (svr *MySql) ExecByStructParam(actionQuery string, args interface{}) MySqlResult { 2684 // verify if the database connection is good 2685 if err := svr.Ping(); err != nil { 2686 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2687 } 2688 2689 // is new insertion? 2690 var isInsert bool 2691 2692 if strings.ToUpper(util.Left(actionQuery, 6)) == "INSERT" { 2693 isInsert = true 2694 } else { 2695 isInsert = false 2696 } 2697 2698 // perform exec action, and return to caller 2699 var result sql.Result 2700 var err error 2701 2702 if !xray.XRayServiceOn() { 2703 // not in transaction mode, 2704 // action using db object 2705 result, err = svr.db.NamedExec(actionQuery, args) 2706 } else { 2707 // not in transaction mode, 2708 // action using db object 2709 trace := xray.NewSegment("MySql-Exec-ExecByStructParam", svr._parentSegment) 2710 defer trace.Close() 2711 defer func() { 2712 _ = trace.Seg.AddMetadata("SQL-Query", actionQuery) 2713 _ = trace.Seg.AddMetadata("SQL-Param-Values", args) 2714 _ = trace.Seg.AddMetadata("Exec-Result", result) 2715 if err != nil { 2716 _ = trace.Seg.AddError(err) 2717 } 2718 }() 2719 2720 result, err = svr.db.NamedExecContext(trace.Ctx, actionQuery, args) 2721 } 2722 2723 if err != nil { 2724 err = errors.New("ExecByStructParam() Error: " + err.Error()) 2725 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2726 } 2727 2728 // if inserted, get last id if known 2729 var newID int64 2730 newID = 0 2731 2732 if isInsert { 2733 newID, err = result.LastInsertId() 2734 2735 if err != nil { 2736 err = errors.New("ExecByStructParam() Get LastInsertId() Error: " + err.Error()) 2737 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2738 } 2739 } 2740 2741 // get rows affected by this action 2742 var affected int64 2743 affected = 0 2744 2745 affected, err = result.RowsAffected() 2746 2747 if err != nil { 2748 err = errors.New("ExecByStructParam() Get RowsAffected() Error: " + err.Error()) 2749 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2750 } 2751 2752 // return result 2753 return MySqlResult{RowsAffected: affected, NewlyInsertedID: newID, Err: nil} 2754 } 2755 2756 // ExecByStructParam executes action query string with struct containing parameters to return result, if error, returns error object within result, 2757 // the struct fields' struct tags must match the parameter names, such as: struct tag `db:"customerID"` must match parameter name in sql as ":customerID" 2758 // [ Syntax ] 2759 // 1. in sql = instead of defining ordinal parameters ?, each parameter in sql does not need to be ordinal, rather define with :xyz (must have : in front of param name), where xyz is name of parameter, such as :customerID 2760 // 2. in go = using a struct to contain fields to match parameters, make sure struct tags match to the sql parameter names, such as struct tag `db:"customerID"` must match parameter name in sql as ":customerID" (the : is not part of the match) 2761 // 2762 // [ Parameters ] 2763 // 2764 // actionQuery = sql action query, with named parameters using :xyz syntax 2765 // args = required, the struct variable, whose fields having struct tags matching sql parameter names 2766 // 2767 // [ Return Values ] 2768 // 1. MySqlResult = represents the sql action result received (including error info if applicable) 2769 func (t *MySqlTransaction) ExecByStructParam(actionQuery string, args interface{}) MySqlResult { 2770 if err := t.ready(); err != nil { 2771 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2772 } 2773 2774 // verify if the database connection is good 2775 if err := t.parent.Ping(); err != nil { 2776 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2777 } 2778 2779 // is new insertion? 2780 var isInsert bool 2781 2782 if strings.ToUpper(util.Left(actionQuery, 6)) == "INSERT" { 2783 isInsert = true 2784 } else { 2785 isInsert = false 2786 } 2787 2788 // perform exec action, and return to caller 2789 var result sql.Result 2790 var err error 2791 2792 if t._xrayTxSeg == nil { 2793 // in transaction mode, 2794 // action using tx object 2795 result, err = t.tx.NamedExec(actionQuery, args) 2796 } else { 2797 // in transaction mode, 2798 // action using tx object 2799 subTrace := t._xrayTxSeg.NewSubSegment("Transaction-Exec-ExecByStructParam") 2800 defer subTrace.Close() 2801 defer func() { 2802 _ = subTrace.Seg.AddMetadata("SQL-Query", actionQuery) 2803 _ = subTrace.Seg.AddMetadata("SQL-Param-Values", args) 2804 _ = subTrace.Seg.AddMetadata("Exec-Result", result) 2805 if err != nil { 2806 _ = subTrace.Seg.AddError(err) 2807 } 2808 }() 2809 2810 result, err = t.tx.NamedExecContext(subTrace.Ctx, actionQuery, args) 2811 } 2812 2813 if err != nil { 2814 err = errors.New("ExecByStructParam() Error: " + err.Error()) 2815 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2816 } 2817 2818 // if inserted, get last id if known 2819 var newID int64 2820 newID = 0 2821 2822 if isInsert { 2823 newID, err = result.LastInsertId() 2824 2825 if err != nil { 2826 err = errors.New("ExecByStructParam() Get LastInsertId() Error: " + err.Error()) 2827 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2828 } 2829 } 2830 2831 // get rows affected by this action 2832 var affected int64 2833 affected = 0 2834 2835 affected, err = result.RowsAffected() 2836 2837 if err != nil { 2838 err = errors.New("ExecByStructParam() Get RowsAffected() Error: " + err.Error()) 2839 return MySqlResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 2840 } 2841 2842 // return result 2843 return MySqlResult{RowsAffected: affected, NewlyInsertedID: newID, Err: nil} 2844 }