github.com/aldelo/common@v1.5.1/wrapper/sqlserver/sqlserver.go (about) 1 package sqlserver 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 "net/url" 24 "strings" 25 26 util "github.com/aldelo/common" 27 "github.com/jmoiron/sqlx" 28 29 // this package is used by database/sql as we are wrapping the sql access functionality in this utility package 30 _ "github.com/denisenkom/go-mssqldb" 31 ) 32 33 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 34 // SQLServer struct Usage Guide 35 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 36 37 /* 38 *************************************************************************************************************** 39 First, Create "../model/global.go" 40 *************************************************************************************************************** 41 42 package model 43 44 import ( 45 "errors" 46 "time" 47 data "github.com/aldelo/common/wrapper/sqlserver" 48 ) 49 50 // package level accessible to the sqlserver database object 51 var db *data.SQLServer 52 53 // SetDB allows code outside of package to set the sqlserver database reference 54 func SetDB(dbx *data.SQLServer) { 55 db = dbx 56 } 57 58 // BeginTran starts db transaction 59 func BeginTran() { 60 if db != nil { 61 db.Begin() 62 } 63 } 64 65 // CommitTran commits db transaction 66 func CommitTran() { 67 if db != nil { 68 db.Commit() 69 } 70 } 71 72 // RollbackTran rolls back db transaction 73 func RollbackTran() { 74 if db != nil { 75 db.Rollback() 76 } 77 } 78 79 */ 80 81 /* 82 *************************************************************************************************************** 83 Second, Prepare DB Object for Use in "../main.go" 84 *************************************************************************************************************** 85 86 package main 87 88 import ( 89 ... 90 data "github.com/aldelo/common/wrapper/sqlserver" 91 "???/model" // ??? is path to the model package 92 ... 93 ) 94 95 ... 96 97 func main() { 98 ... 99 100 // ======================================== 101 // setup database connection 102 // ======================================== 103 104 // 105 // declare sqlserver object 106 // 107 s := new(data.SQLServer) 108 109 // 110 // set sqlserver dsn fields 111 // 112 s.AppName = "" // application name from the calling agent 113 s.Host = "" // from aws aurora endpoint 114 s.Port = 0 // custom port number if applicable (0 will ignore this field) 115 s.Database = "" // database name 116 s.UserName = "" // database server user name 117 s.Password = "" // database server user password 118 s.Encrypted = false // set to false to not use encryption 119 s.Instance = "" // optional 120 s.Timeout = 15 // seconds 121 122 // 123 // open sqlserver database connection 124 // 125 if err := s.Open(); err != nil { 126 s.Close() 127 } else { 128 // add sqlserver object to model global 129 model.SetDB(&s) 130 131 // defer db clean up upon execution ends 132 defer model.SetDB(nil) 133 defer s.Close() 134 } 135 136 ... 137 } 138 139 */ 140 141 /* 142 *************************************************************************************************************** 143 Third, Using SqlServer Struct 144 *************************************************************************************************************** 145 146 package model 147 148 import ( 149 "bytes" 150 "database/sql" // this import is needed for db struct tags 151 "errors" 152 "time" 153 util "github.com/aldelo/common" 154 ) 155 156 // create a struct, and use db struct tags to identify parameter names 157 // db struct tags can contain ,required ,size=# if string 158 type Customer struct { 159 CustomerID int `db:"customerID"` 160 CompanyName string `db:"companyName"` 161 } 162 163 // when composing sql statements, if statement is long, use bytes.Buffer (or use data/QueryBuilder.go) 164 var b bytes.Buffer 165 166 b.WriteString("xyz ") 167 b.WriteString("123") 168 169 v := b.String() // v = xyz 123 170 171 // for insert, update, logical delete, physical delete 172 // use the appropriate functions from db struct, located in model/global.go 173 // the db struct is global in scope for code files within model package 174 db.GetStruct(...) 175 db.GetSliceStruct(...) 176 // etc 177 178 */ 179 180 // ================================================================================================================ 181 // STRUCTS 182 // ================================================================================================================ 183 184 // SQLServer struct encapsulates the SQLServer database access functionality (using sqlx package) 185 type SQLServer struct { 186 // SQLServer connection properties 187 Host string 188 Port int 189 Instance string 190 Database string 191 UserName string 192 Password string 193 Timeout int 194 Encrypted bool 195 AppName string 196 197 // SQLSvr state object 198 db *sqlx.DB 199 tx *sqlx.Tx 200 } 201 202 // SQLResult defines sql action query result info 203 type SQLResult struct { 204 RowsAffected int64 205 NewlyInsertedID int64 206 Err error 207 } 208 209 // ================================================================================================================ 210 // STRUCT FUNCTIONS 211 // ================================================================================================================ 212 213 // ---------------------------------------------------------------------------------------------------------------- 214 // utility functions 215 // ---------------------------------------------------------------------------------------------------------------- 216 217 // GetDsnADO serialize SQLServer dsn to ado style connection string, for use in database connectivity (dsn.Port is ignored) 218 func (svr *SQLServer) GetDsnADO() (string, error) { 219 // 220 // first validate input 221 // 222 if len(svr.Host) == 0 { 223 return "", errors.New("SQL Server Host is Required") 224 } 225 226 if len(svr.Database) == 0 { 227 return "", errors.New("SQL Database is Required") 228 } 229 230 if len(svr.UserName) == 0 { 231 return "", errors.New("User ID is Required") 232 } 233 234 // 235 // now create ado style connection string 236 // 237 str := "server=" + svr.Host 238 239 if len(svr.Instance) > 0 { 240 str += "\\" + svr.Instance + ";" 241 } else { 242 str += ";" 243 } 244 245 str += "database=" + svr.Database + ";" 246 247 if len(svr.AppName) > 0 { 248 str += "app name=" + svr.AppName + ";" 249 } 250 251 str += "user id=" + svr.UserName + ";" 252 253 if len(svr.Password) > 0 { 254 str += "password=" + svr.Password + ";" 255 } 256 257 if svr.Timeout > 0 { 258 str += "connection timeout=" + util.Itoa(svr.Timeout) + ";" 259 } else { 260 str += "connection timeout=0;" 261 } 262 263 if !svr.Encrypted { 264 str += "encrypt=disable;" 265 } else { 266 str += "encrypt=true;" 267 } 268 269 // remove last semi-colon from str 270 str = str[:len(str)-1] 271 272 // return to caller 273 return str, nil 274 } 275 276 // GetDsnURL serialize sql server dsn to url style connection string, for use in database connectivity 277 func (svr *SQLServer) GetDsnURL() (string, error) { 278 // 279 // first validate input 280 // 281 if len(svr.Host) == 0 { 282 return "", errors.New("SQL Server Host is Required") 283 } 284 285 if len(svr.Database) == 0 { 286 return "", errors.New("SQL Database is Required") 287 } 288 289 if len(svr.UserName) == 0 { 290 return "", errors.New("User ID is Required") 291 } 292 293 // 294 // now create url style connection string 295 // 296 query := url.Values{} 297 query.Add("app name", svr.AppName) 298 query.Add("database", svr.Database) 299 300 if svr.Timeout >= 0 && svr.Timeout <= 60 { 301 query.Add("connection timeout", util.Itoa(svr.Timeout)) 302 } else { 303 query.Add("connection timeout", "0") 304 } 305 306 if !svr.Encrypted { 307 query.Add("encrypt", "disable") 308 } else { 309 query.Add("encrypt", "true") 310 } 311 312 var h string 313 314 if svr.Port > 0 { 315 h = fmt.Sprintf("%s:%d", svr.Host, svr.Port) 316 } else if len(svr.Instance) > 0 { 317 h = fmt.Sprintf("%s\\%s", svr.Host, svr.Instance) 318 } else { 319 h = svr.Host 320 } 321 322 u := url.URL{ 323 Scheme: "sqlserver", 324 User: url.UserPassword(svr.UserName, svr.Password), 325 Host: h, 326 RawQuery: query.Encode(), 327 } 328 329 // return to caller 330 return u.String(), nil 331 } 332 333 // Open a database by connecting to it, using the dsn properties defined in the struct fields 334 // 335 // useADOConnectString = if ignored, default is true, to use URL connect string format, set parameter value to false explicitly 336 func (svr *SQLServer) Open(useADOConnectString ...bool) error { 337 // 338 // get parameter value, 339 // default is expected 340 // 341 ado := true 342 343 if len(useADOConnectString) > 0 { 344 ado = useADOConnectString[0] 345 } 346 347 // declare 348 var str string 349 var err error 350 351 // get connect string 352 if ado { 353 str, err = svr.GetDsnADO() 354 } else { 355 str, err = svr.GetDsnURL() 356 } 357 358 if err != nil { 359 svr.tx = nil 360 svr.db = nil 361 return err 362 } 363 364 // validate connection string 365 if len(str) == 0 { 366 svr.tx = nil 367 svr.db = nil 368 return errors.New("SQL Server Connect String Generated Cannot Be Empty") 369 } 370 371 // now ready to open sql server database 372 svr.db, err = sqlx.Open("sqlserver", str) 373 374 if err != nil { 375 svr.tx = nil 376 svr.db = nil 377 return err 378 } 379 380 // test sql server state object 381 if err = svr.db.Ping(); err != nil { 382 svr.tx = nil 383 svr.db = nil 384 return err 385 } 386 387 // upon open, transaction object already nil 388 svr.tx = nil 389 390 // sql server state object successfully opened 391 return nil 392 } 393 394 // Close will close the database connection and set db to nil 395 func (svr *SQLServer) Close() error { 396 if svr.db != nil { 397 if err := svr.db.Close(); err != nil { 398 return err 399 } 400 401 // clean up 402 svr.tx = nil 403 svr.db = nil 404 return nil 405 } 406 407 return nil 408 } 409 410 // Ping tests if current database connection is still active and ready 411 func (svr *SQLServer) Ping() error { 412 if svr.db == nil { 413 return errors.New("SQL Server Not Connected") 414 } 415 416 if err := svr.db.Ping(); err != nil { 417 return err 418 } 419 420 // database ok 421 return nil 422 } 423 424 // Begin starts a database transaction, and stores the transaction object until commit or rollback 425 func (svr *SQLServer) Begin() error { 426 // verify if the database connection is good 427 if err := svr.Ping(); err != nil { 428 return err 429 } 430 431 // does transaction already exist 432 if svr.tx != nil { 433 return errors.New("Transaction Already Started") 434 } 435 436 // begin transaction on database 437 tx, err := svr.db.Beginx() 438 439 if err != nil { 440 return err 441 } 442 443 // transaction begin successful, 444 // store tx into svr.tx field 445 svr.tx = tx 446 447 // return nil as success 448 return nil 449 } 450 451 // Commit finalizes a database transaction, and commits changes to database 452 func (svr *SQLServer) Commit() error { 453 // verify if the database connection is good 454 if err := svr.Ping(); err != nil { 455 return err 456 } 457 458 // does transaction already exist 459 if svr.tx == nil { 460 return errors.New("Transaction Does Not Exist") 461 } 462 463 // perform tx commit 464 if err := svr.tx.Commit(); err != nil { 465 return err 466 } 467 468 // commit successful 469 svr.tx = nil 470 return nil 471 } 472 473 // Rollback cancels pending database changes for the current transaction and clears out transaction object 474 func (svr *SQLServer) Rollback() error { 475 // verify if the database connection is good 476 if err := svr.Ping(); err != nil { 477 return err 478 } 479 480 // does transaction already exist 481 if svr.tx == nil { 482 return errors.New("Transaction Does Not Exist") 483 } 484 485 // perform tx commit 486 if err := svr.tx.Rollback(); err != nil { 487 svr.tx = nil 488 return err 489 } 490 491 // commit successful 492 svr.tx = nil 493 return nil 494 } 495 496 // ---------------------------------------------------------------------------------------------------------------- 497 // query and marshal to 'struct slice' or 'struct' helpers 498 // ---------------------------------------------------------------------------------------------------------------- 499 500 // GetStructSlice performs query with optional variadic parameters, and unmarshal result rows into target struct slice, 501 // in essence, each row of data is marshaled into the given struct, and multiple struct form the slice, 502 // such as: []Customer where each row represent a customer, and multiple customers being part of the slice 503 // [ Parameters ] 504 // 505 // dest = pointer to the struct slice or address of struct slice, this is the result of rows to be marshaled into struct slice 506 // query = sql query, optionally having parameters marked as @p1, @p2, ... @pN, where each represents a parameter position 507 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 508 // 509 // [ Return Values ] 510 // 1. notFound = indicates no rows found in query (aka sql.ErrNoRows), if error is detected, notFound is always false 511 // 2. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and dest is nil) 512 // 513 // [ Notes ] 514 // 1. if error == nil, and len(dest struct slice) == 0 then zero struct slice result 515 func (svr *SQLServer) GetStructSlice(dest interface{}, query string, args ...interface{}) (notFound bool, retErr error) { 516 // verify if the database connection is good 517 if err := svr.Ping(); err != nil { 518 return false, err 519 } 520 521 // perform select action, and unmarshal result rows into target struct slice 522 var err error 523 524 if svr.tx == nil { 525 // not in transaction mode 526 // query using db object 527 err = svr.db.Select(dest, query, args...) 528 } else { 529 // in transaction mode 530 // query using tx object 531 err = svr.tx.Select(dest, query, args...) 532 } 533 534 // if err is sql.ErrNoRows then treat as no error 535 if err != nil && err == sql.ErrNoRows { 536 notFound = true 537 dest = nil 538 err = nil 539 } else { 540 notFound = false 541 } 542 543 // return error 544 return notFound, err 545 } 546 547 // GetStruct performs query with optional variadic parameters, and unmarshal single result row into single target struct, 548 // such as: Customer struct where one row of data represent a customer 549 // [ Parameters ] 550 // 551 // dest = pointer to struct or address of struct, this is the result of row to be marshaled into this struct 552 // query = sql query, optionally having parameters marked as @p1, @p2, ... @pN, where each represents a parameter position 553 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 554 // 555 // [ Return Values ] 556 // 1. notFound = indicates no rows found in query (aka sql.ErrNoRows), if error is detected, notFound is always false 557 // 2. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and dest is nil) 558 func (svr *SQLServer) GetStruct(dest interface{}, query string, args ...interface{}) (notFound bool, retErr error) { 559 // verify if the database connection is good 560 if err := svr.Ping(); err != nil { 561 return false, err 562 } 563 564 // perform select action, and unmarshal result row (single row) into target struct (single object) 565 var err error 566 567 if svr.tx == nil { 568 // not in transaction mode 569 // query using db object 570 err = svr.db.Get(dest, query, args...) 571 } else { 572 // in transaction mode 573 // query using tx object 574 err = svr.tx.Get(dest, query, args...) 575 } 576 577 // if err is sql.ErrNoRows then treat as no error 578 if err != nil && err == sql.ErrNoRows { 579 notFound = true 580 dest = nil 581 err = nil 582 } else { 583 notFound = false 584 } 585 586 // return error 587 return notFound, err 588 } 589 590 // ---------------------------------------------------------------------------------------------------------------- 591 // query and get rows helpers 592 // ---------------------------------------------------------------------------------------------------------------- 593 594 // GetRowsByOrdinalParams performs query with optional variadic parameters to get ROWS of result, and returns *sqlx.Rows 595 // [ Parameters ] 596 // 597 // query = sql query, optionally having parameters marked as @p1, @p2, ... @pN, where each represents a parameter position 598 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 599 // 600 // [ Return Values ] 601 // 1. *sqlx.Rows = pointer to sqlx.Rows; or nil if no rows yielded 602 // 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) 603 // 604 // [ Ranged Loop & Scan ] 605 // 1. to loop, use: for _, r := range rows 606 // 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) 607 // 608 // [ Continuous Loop & Scan ] 609 // 1. Continuous loop until endOfRows = true is yielded from ScanSlice() or ScanStruct() 610 // 2. ScanSlice(): accepts *sqlx.Rows, scans rows result into target pointer slice (if no error, endOfRows = true is returned) 611 // 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 612 func (svr *SQLServer) GetRowsByOrdinalParams(query string, args ...interface{}) (*sqlx.Rows, error) { 613 // verify if the database connection is good 614 if err := svr.Ping(); err != nil { 615 return nil, err 616 } 617 618 // perform select action, and return sqlx rows 619 var rows *sqlx.Rows 620 var err error 621 622 if svr.tx == nil { 623 // not in transaction mode 624 // query using db object 625 rows, err = svr.db.Queryx(query, args...) 626 } else { 627 // in transaction mode 628 // query using tx object 629 rows, err = svr.tx.Queryx(query, args...) 630 } 631 632 // if err is sql.ErrNoRows then treat as no error 633 if err != nil && err == sql.ErrNoRows { 634 rows = nil 635 err = nil 636 } 637 638 // return result 639 return rows, err 640 } 641 642 // GetRowsByNamedMapParam performs query with named map containing parameters to get ROWS of result, and returns *sqlx.Rows 643 // [ Syntax ] 644 // 1. in sql = instead of defining ordinal parameters @p1..@pN, 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 645 // 2. in go = setup a map variable: var p = make(map[string]interface{}) 646 // 3. in go = to set values into map variable: p["xyz"] = abc 647 // where xyz is the parameter name matching the sql :xyz (do not include : in go map "xyz") 648 // where abc is the value of the parameter value, whether string or other data types 649 // note: in using map, just add additional map elements using the p["xyz"] = abc syntax 650 // note: if parameter value can be a null, such as nullint, nullstring, use util.ToNullTime(), ToNullInt(), ToNullString(), etc. 651 // 4. in go = when calling this function passing the map variable, simply pass the map variable p into the args parameter 652 // 653 // [ Parameters ] 654 // 655 // query = sql query, optionally having parameters marked as :xyz for each parameter name, where each represents a named parameter 656 // args = required, the map variable of the named parameters 657 // 658 // [ Return Values ] 659 // 1. *sqlx.Rows = pointer to sqlx.Rows; or nil if no rows yielded 660 // 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) 661 // 662 // [ Ranged Loop & Scan ] 663 // 1. to loop, use: for _, r := range rows 664 // 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) 665 // 666 // [ Continuous Loop & Scan ] 667 // 1. Continuous loop until endOfRows = true is yielded from ScanSlice() or ScanStruct() 668 // 2. ScanSlice(): accepts *sqlx.Rows, scans rows result into target pointer slice (if no error, endOfRows = true is returned) 669 // 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 670 func (svr *SQLServer) GetRowsByNamedMapParam(query string, args map[string]interface{}) (*sqlx.Rows, error) { 671 // verify if the database connection is good 672 if err := svr.Ping(); err != nil { 673 return nil, err 674 } 675 676 // perform select action, and return sqlx rows 677 var rows *sqlx.Rows 678 var err error 679 680 if svr.tx == nil { 681 // not in transaction mode 682 // query using db object 683 rows, err = svr.db.NamedQuery(query, args) 684 } else { 685 // in transaction mode 686 // query using tx object 687 rows, err = svr.tx.NamedQuery(query, args) 688 } 689 690 if err != nil && err == sql.ErrNoRows { 691 // no rows 692 rows = nil 693 err = nil 694 } 695 696 // return result 697 return rows, err 698 } 699 700 // GetRowsByStructParam performs query with a struct as parameter input to get ROWS of result, and returns *sqlx.Rows 701 // [ Syntax ] 702 // 1. in sql = instead of defining ordinal parameters @p1..@pN, 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 703 // 2. in sql = important: the :xyz defined where xyz portion of parameter name must batch the struct tag's `db:"xyz"` 704 // 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 705 // 4. in go = when calling this function passing the struct variable, simply pass the struct variable into the args parameter 706 // 707 // [ Parameters ] 708 // 709 // query = sql query, optionally having parameters marked as :xyz for each parameter name, where each represents a named parameter 710 // args = required, the struct variable where struct fields' struct tags match to the named parameters 711 // 712 // [ Return Values ] 713 // 1. *sqlx.Rows = pointer to sqlx.Rows; or nil if no rows yielded 714 // 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) 715 // 716 // [ Ranged Loop & Scan ] 717 // 1. to loop, use: for _, r := range rows 718 // 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) 719 // 720 // [ Continuous Loop & Scan ] 721 // 1. Continuous loop until endOfRows = true is yielded from ScanSlice() or ScanStruct() 722 // 2. ScanSlice(): accepts *sqlx.Rows, scans rows result into target pointer slice (if no error, endOfRows = true is returned) 723 // 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 724 func (svr *SQLServer) GetRowsByStructParam(query string, args interface{}) (*sqlx.Rows, error) { 725 // verify if the database connection is good 726 if err := svr.Ping(); err != nil { 727 return nil, err 728 } 729 730 // perform select action, and return sqlx rows 731 var rows *sqlx.Rows 732 var err error 733 734 if svr.tx == nil { 735 // not in transaction mode 736 // query using db object 737 rows, err = svr.db.NamedQuery(query, args) 738 } else { 739 // in transaction mode 740 // query using tx object 741 rows, err = svr.tx.NamedQuery(query, args) 742 } 743 744 if err != nil && err == sql.ErrNoRows { 745 // no rows 746 rows = nil 747 err = nil 748 } 749 750 // return result 751 return rows, err 752 } 753 754 // ---------------------------------------------------------------------------------------------------------------- 755 // scan row data and marshal to 'slice' or 'struct' helpers 756 // ---------------------------------------------------------------------------------------------------------------- 757 758 // ScanSlice takes in *sqlx.Rows as parameter, will invoke the rows.Next() to advance to next row position, 759 // and marshals current row's column values into a pointer reference to a slice, 760 // 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), 761 // 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 762 // [ Parameters ] 763 // 764 // rows = *sqlx.Rows 765 // dest = pointer or address to slice, such as: variable to "*[]string", or variable to "&cList for declaration cList []string" 766 // 767 // [ Return Values ] 768 // 1. endOfRows = true if this action call yielded end of rows, meaning stop further processing of current loop 769 // 2. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and dest is set as nil) 770 func (svr *SQLServer) ScanSlice(rows *sqlx.Rows, dest []interface{}) (endOfRows bool, err error) { 771 // ensure rows pointer is set 772 if rows == nil { 773 return true, nil 774 } 775 776 // call rows.Next() first to position the row 777 if rows.Next() { 778 // now slice scan 779 dest, err = rows.SliceScan() 780 781 // if err is sql.ErrNoRows then treat as no error 782 if err != nil && err == sql.ErrNoRows { 783 endOfRows = true 784 dest = nil 785 err = nil 786 return 787 } 788 789 if err != nil { 790 // has error 791 endOfRows = false // although error but may not be at end of rows 792 dest = nil 793 return 794 } 795 796 // slice scan success, but may not be at end of rows 797 return false, nil 798 } 799 800 // no more rows 801 return true, nil 802 } 803 804 // ScanStruct takes in *sqlx.Rows, will invoke the rows.Next() to advance to next row position, 805 // and marshals current row's column values into a pointer reference to a struct, 806 // the struct fields and row columns must match for both name and sequence position, 807 // this enables us to quickly convert the row's columns into a defined struct automatically, 808 // 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 809 // [ Parameters ] 810 // 811 // rows = *sqlx.Rows 812 // dest = pointer or address to struct, such as: variable to "*Customer", or variable to "&c for declaration c Customer" 813 // 814 // [ Return Values ] 815 // 1. endOfRows = true if this action call yielded end of rows, meaning stop further processing of current loop 816 // 2. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and dest is set as nil) 817 func (svr *SQLServer) ScanStruct(rows *sqlx.Rows, dest interface{}) (endOfRows bool, err error) { 818 // ensure rows pointer is set 819 if rows == nil { 820 return true, nil 821 } 822 823 // call rows.Next() first to position the row 824 if rows.Next() { 825 // now struct scan 826 err = rows.StructScan(dest) 827 828 // if err is sql.ErrNoRows then treat as no error 829 if err != nil && err == sql.ErrNoRows { 830 endOfRows = true 831 dest = nil 832 err = nil 833 return 834 } 835 836 if err != nil { 837 // has error 838 endOfRows = false // although error but may not be at end of rows 839 dest = nil 840 return 841 } 842 843 // struct scan successful, but may not be at end of rows 844 return false, nil 845 } 846 847 // no more rows 848 return true, nil 849 } 850 851 // ---------------------------------------------------------------------------------------------------------------- 852 // query for single row helper 853 // ---------------------------------------------------------------------------------------------------------------- 854 855 // GetSingleRow performs query with optional variadic parameters to get a single ROW of result, and returns *sqlx.Row (This function returns SINGLE ROW) 856 // [ Parameters ] 857 // 858 // query = sql query, optionally having parameters marked as @p1, @p2, ... @pN, where each represents a parameter position 859 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 860 // 861 // [ Return Values ] 862 // 1. *sqlx.Row = pointer to sqlx.Row; or nil if no row yielded 863 // 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) 864 // 865 // [ Scan Values ] 866 // 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) 867 // 868 // [ WARNING !!! ] 869 // 870 // WHEN USING Scan(), MUST CHECK Scan Result Error for sql.ErrNoRow status 871 // SUGGESTED TO USE ScanColumnsByRow() Instead of Scan() 872 func (svr *SQLServer) GetSingleRow(query string, args ...interface{}) (*sqlx.Row, error) { 873 // verify if the database connection is good 874 if err := svr.Ping(); err != nil { 875 return nil, err 876 } 877 878 // perform select action, and return sqlx row 879 var row *sqlx.Row 880 var err error 881 882 if svr.tx == nil { 883 // not in transaction mode 884 // query using db object 885 row = svr.db.QueryRowx(query, args...) 886 } else { 887 // in transaction mode 888 // query using tx object 889 row = svr.tx.QueryRowx(query, args...) 890 } 891 892 if row == nil { 893 err = errors.New("No Row Data Found From Query") 894 } else { 895 err = row.Err() 896 897 if err != nil { 898 if err == sql.ErrNoRows { 899 // no rows 900 row = nil 901 err = nil 902 } else { 903 // has error 904 row = nil 905 } 906 } 907 } 908 909 // return result 910 return row, err 911 } 912 913 // ---------------------------------------------------------------------------------------------------------------- 914 // scan single row data and marshal to 'slice' or 'struct' or specific fields, or scan columns helpers 915 // ---------------------------------------------------------------------------------------------------------------- 916 917 // ScanSliceByRow takes in *sqlx.Row as parameter, and marshals current row's column values into a pointer reference to a slice, 918 // 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) 919 // [ Parameters ] 920 // 921 // row = *sqlx.Row 922 // dest = pointer or address to slice, such as: variable to "*[]string", or variable to "&cList for declaration cList []string" 923 // 924 // [ Return Values ] 925 // 1. notFound = true if no row is found in current scan 926 // 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) 927 func (svr *SQLServer) ScanSliceByRow(row *sqlx.Row, dest []interface{}) (notFound bool, err error) { 928 // if row is nil, treat as no row and not an error 929 if row == nil { 930 dest = nil 931 return true, nil 932 } 933 934 // perform slice scan on the given row 935 dest, err = row.SliceScan() 936 937 // if err is sql.ErrNoRows then treat as no error 938 if err != nil && err == sql.ErrNoRows { 939 dest = nil 940 return true, nil 941 } 942 943 if err != nil { 944 // has error 945 dest = nil 946 return false, err // although error but may not be not found 947 } 948 949 // slice scan success 950 return false, nil 951 } 952 953 // ScanStructByRow takes in *sqlx.Row, and marshals current row's column values into a pointer reference to a struct, 954 // the struct fields and row columns must match for both name and sequence position, 955 // this enables us to quickly convert the row's columns into a defined struct automatically, 956 // the dest is nil if no columns found; the dest is pointer of struct when mapping is complete 957 // [ Parameters ] 958 // 959 // row = *sqlx.Row 960 // dest = pointer or address to struct, such as: variable to "*Customer", or variable to "&c for declaration c Customer" 961 // 962 // [ Return Values ] 963 // 1. notFound = true if no row is found in current scan 964 // 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) 965 func (svr *SQLServer) ScanStructByRow(row *sqlx.Row, dest interface{}) (notFound bool, err error) { 966 // if row is nil, treat as no row and not an error 967 if row == nil { 968 dest = nil 969 return true, nil 970 } 971 972 // now struct scan 973 err = row.StructScan(dest) 974 975 // if err is sql.ErrNoRows then treat as no error 976 if err != nil && err == sql.ErrNoRows { 977 dest = nil 978 return true, nil 979 } 980 981 if err != nil { 982 // has error 983 dest = nil 984 return false, err // although error but may not be not found 985 } 986 987 // struct scan successful 988 return false, nil 989 } 990 991 // ScanColumnsByRow accepts a *sqlx row, and scans specific columns into dest outputs, 992 // this is different than ScanSliceByRow or ScanStructByRow because this function allows specific extraction of column values into target fields, 993 // (note: this function must extra all row column values to dest variadic parameters as present in the row parameter) 994 // [ Parameters ] 995 // 996 // row = *sqlx.Row representing the row containing columns to extract, note that this function MUST extract all columns from this row 997 // 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 998 // 999 // [ Return Values ] 1000 // 1. notFound = true if no row is found in current scan 1001 // 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) 1002 // 1003 // [ Example ] 1004 // 1. assuming: Select CustomerID, CustomerName, Address FROM Customer Where CustomerPhone='123'; 1005 // 2. assuming: row // *sqlx.Row derived from GetSingleRow() or specific row from GetRowsByOrdinalParams() / GetRowsByNamedMapParam() / GetRowsByStructParam() 1006 // 3. assuming: var CustomerID int64 1007 // var CustomerName string 1008 // var Address string 1009 // 4. notFound, err := svr.ScanColumnsByRow(row, &CustomerID, &CustomerName, &Address) 1010 func (svr *SQLServer) ScanColumnsByRow(row *sqlx.Row, dest ...interface{}) (notFound bool, err error) { 1011 // if row is nil, treat as no row and not an error 1012 if row == nil { 1013 return true, nil 1014 } 1015 1016 // now scan columns from row 1017 err = row.Scan(dest...) 1018 1019 // if err is sql.ErrNoRows then treat as no error 1020 if err != nil && err == sql.ErrNoRows { 1021 return true, nil 1022 } 1023 1024 if err != nil { 1025 // has error 1026 return false, err // although error but may not be not found 1027 } 1028 1029 // scan columns successful 1030 return false, nil 1031 } 1032 1033 // ---------------------------------------------------------------------------------------------------------------- 1034 // query for single value in single row helpers 1035 // ---------------------------------------------------------------------------------------------------------------- 1036 1037 // GetScalarString performs query with optional variadic parameters, and returns the first row and first column value in string data type 1038 // [ Parameters ] 1039 // 1040 // query = sql query, optionally having parameters marked as @p1, @p2, ... @pN, where each represents a parameter position 1041 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 1042 // 1043 // [ Return Values ] 1044 // 1. retVal = string value of scalar result, if no value, blank is returned 1045 // 2. retNotFound = now row found 1046 // 3. if error != nil, then error is encountered (if error == sql.ErrNoRows, then error is treated as nil, and retVal is returned as blank) 1047 func (svr *SQLServer) GetScalarString(query string, args ...interface{}) (retVal string, retNotFound bool, retErr error) { 1048 // verify if the database connection is good 1049 if err := svr.Ping(); err != nil { 1050 return "", false, err 1051 } 1052 1053 // get row using query string and parameters 1054 var row *sqlx.Row 1055 1056 if svr.tx == nil { 1057 // not in transaction 1058 // use db object 1059 row = svr.db.QueryRowx(query, args...) 1060 } else { 1061 // in transaction 1062 // use tx object 1063 row = svr.tx.QueryRowx(query, args...) 1064 } 1065 1066 if row == nil { 1067 return "", false, errors.New("Scalar Query Yielded Empty Row") 1068 } else { 1069 retErr = row.Err() 1070 1071 if retErr != nil { 1072 if retErr == sql.ErrNoRows { 1073 // no rows 1074 return "", true, nil 1075 } else { 1076 // has error 1077 return "", false, retErr 1078 } 1079 } 1080 } 1081 1082 // get value via scan 1083 retErr = row.Scan(&retVal) 1084 1085 if retErr == sql.ErrNoRows { 1086 // no row 1087 return "", true, nil 1088 } 1089 1090 // return value 1091 return retVal, false, retErr 1092 } 1093 1094 // GetScalarNullString performs query with optional variadic parameters, and returns the first row and first column value in sql.NullString{} data type 1095 // [ Parameters ] 1096 // 1097 // query = sql query, optionally having parameters marked as @p1, @p2, ... @pN, where each represents a parameter position 1098 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 1099 // 1100 // [ Return Values ] 1101 // 1. retVal = string value of scalar result, if no value, sql.NullString{} is returned 1102 // 2. retNotFound = now row found 1103 // 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{}) 1104 func (svr *SQLServer) GetScalarNullString(query string, args ...interface{}) (retVal sql.NullString, retNotFound bool, retErr error) { 1105 // verify if the database connection is good 1106 if err := svr.Ping(); err != nil { 1107 return sql.NullString{}, false, err 1108 } 1109 1110 // get row using query string and parameters 1111 var row *sqlx.Row 1112 1113 if svr.tx == nil { 1114 // not in transaction 1115 // use db object 1116 row = svr.db.QueryRowx(query, args...) 1117 } else { 1118 // in transaction 1119 // use tx object 1120 row = svr.tx.QueryRowx(query, args...) 1121 } 1122 1123 if row == nil { 1124 return sql.NullString{}, false, errors.New("Scalar Query Yielded Empty Row") 1125 } else { 1126 retErr = row.Err() 1127 1128 if retErr != nil { 1129 if retErr == sql.ErrNoRows { 1130 // no rows 1131 return sql.NullString{}, true, nil 1132 } else { 1133 // has error 1134 return sql.NullString{}, false, retErr 1135 } 1136 } 1137 } 1138 1139 // get value via scan 1140 retErr = row.Scan(&retVal) 1141 1142 if retErr == sql.ErrNoRows { 1143 // no row 1144 return sql.NullString{}, true, nil 1145 } 1146 1147 // return value 1148 return retVal, false, retErr 1149 } 1150 1151 // ---------------------------------------------------------------------------------------------------------------- 1152 // execute helpers 1153 // ---------------------------------------------------------------------------------------------------------------- 1154 1155 // ExecByOrdinalParams executes action query string and parameters to return result, if error, returns error object within result 1156 // [ Parameters ] 1157 // 1158 // actionQuery = sql action query, optionally having parameters marked as @p1, @p2, ... @pN, where each represents a parameter position 1159 // args = conditionally required if positioned parameters are specified, must appear in the same order as the positional parameters 1160 // 1161 // [ Return Values ] 1162 // 1. SQLResult = represents the sql action result received (including error info if applicable) 1163 func (svr *SQLServer) ExecByOrdinalParams(query string, args ...interface{}) SQLResult { 1164 // verify if the database connection is good 1165 if err := svr.Ping(); err != nil { 1166 return SQLResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 1167 } 1168 1169 // keep query trimmed 1170 query = util.Trim(query) 1171 1172 // is insert? 1173 isInsert := strings.ToUpper(query[:6]) == "INSERT" 1174 1175 // append action result query 1176 if query[len(query)-1:] != ";" { 1177 query += ";" 1178 } 1179 1180 if isInsert { 1181 query += ";SELECT PKID=SCOPE_IDENTITY(), RowsAffected=@@ROWCOUNT;" 1182 } else { 1183 query += ";SELECT RowsAffected=@@ROWCOUNT;" 1184 } 1185 1186 // perform exec action, and return to caller 1187 var rows *sqlx.Rows 1188 var err error 1189 1190 if svr.tx == nil { 1191 // not in transaction mode, 1192 // action using db object 1193 rows, err = svr.db.Queryx(query, args...) 1194 } else { 1195 // in transaction mode, 1196 // action using tx object 1197 rows, err = svr.tx.Queryx(query, args...) 1198 } 1199 1200 if err != nil { 1201 return SQLResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 1202 } 1203 1204 // no row - error 1205 if rows == nil { 1206 return SQLResult{RowsAffected: 0, NewlyInsertedID: 0, Err: errors.New("ExecByOrdinalParams() Error: No Row Summary Returned")} 1207 } 1208 1209 if rows.Next() == false { 1210 return SQLResult{RowsAffected: 0, NewlyInsertedID: 0, Err: errors.New("ExecByOrdinalParams() Error: Rows.Next() Yielded No Data")} 1211 } 1212 1213 // evaluate rows affected 1214 var affected int64 1215 var newID int64 1216 1217 var returnErr error 1218 returnErr = nil 1219 1220 if isInsert { 1221 if err = rows.Scan(&newID, &affected); err != nil { 1222 if err != sql.ErrNoRows { 1223 returnErr = err 1224 } 1225 } 1226 } else { 1227 if err = rows.Scan(&affected); err != nil { 1228 if err != sql.ErrNoRows { 1229 returnErr = err 1230 } 1231 } 1232 } 1233 1234 // return result 1235 return SQLResult{RowsAffected: affected, NewlyInsertedID: newID, Err: returnErr} 1236 } 1237 1238 // ExecByNamedMapParam executes action query string with named map containing parameters to return result, if error, returns error object within result 1239 // [ Syntax ] 1240 // 1. in sql = instead of defining ordinal parameters @p1..@pN, 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 1241 // 2. in go = setup a map variable: var p = make(map[string]interface{}) 1242 // 3. in go = to set values into map variable: p["xyz"] = abc 1243 // where xyz is the parameter name matching the sql :xyz (do not include : in go map "xyz") 1244 // where abc is the value of the parameter value, whether string or other data types 1245 // note: in using map, just add additional map elements using the p["xyz"] = abc syntax 1246 // note: if parameter value can be a null, such as nullint, nullstring, use util.ToNullTime(), ToNullInt(), ToNullString(), etc. 1247 // 4. in go = when calling this function passing the map variable, simply pass the map variable p into the args parameter 1248 // 1249 // [ Parameters ] 1250 // 1251 // actionQuery = sql action query, with named parameters using :xyz syntax 1252 // args = required, the map variable of the named parameters 1253 // 1254 // [ Return Values ] 1255 // 1. SQLResult = represents the sql action result received (including error info if applicable) 1256 func (svr *SQLServer) ExecByNamedMapParam(query string, args map[string]interface{}) SQLResult { 1257 // verify if the database connection is good 1258 if err := svr.Ping(); err != nil { 1259 return SQLResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 1260 } 1261 1262 // keep query trimmed 1263 query = util.Trim(query) 1264 1265 // is insert? 1266 isInsert := strings.ToUpper(query[:6]) == "INSERT" 1267 1268 // append action result query 1269 if query[len(query)-1:] != ";" { 1270 query += ";" 1271 } 1272 1273 if isInsert { 1274 query += ";SELECT PKID=SCOPE_IDENTITY(), RowsAffected=@@ROWCOUNT;" 1275 } else { 1276 query += ";SELECT RowsAffected=@@ROWCOUNT;" 1277 } 1278 1279 // perform exec action, and return to caller 1280 var rows *sqlx.Rows 1281 var err error 1282 1283 if svr.tx == nil { 1284 // not in transaction mode, 1285 // action using db object 1286 rows, err = svr.db.NamedQuery(query, args) 1287 } else { 1288 // in transaction mode, 1289 // action using tx object 1290 rows, err = svr.tx.NamedQuery(query, args) 1291 } 1292 1293 if err != nil { 1294 return SQLResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 1295 } 1296 1297 // no row - error 1298 if rows == nil { 1299 return SQLResult{RowsAffected: 0, NewlyInsertedID: 0, Err: errors.New("ExecByNamedMapParam() Error: No Row Summary Returned")} 1300 } 1301 1302 if rows.Next() == false { 1303 return SQLResult{RowsAffected: 0, NewlyInsertedID: 0, Err: errors.New("ExecByNamedMapParam() Error: Rows.Next() Yielded No Data")} 1304 } 1305 1306 // evaluate rows affected 1307 var affected int64 1308 var newID int64 1309 1310 var returnErr error 1311 returnErr = nil 1312 1313 if isInsert { 1314 if err = rows.Scan(&newID, &affected); err != nil { 1315 if err != sql.ErrNoRows { 1316 returnErr = err 1317 } 1318 } 1319 } else { 1320 if err = rows.Scan(&affected); err != nil { 1321 if err != sql.ErrNoRows { 1322 returnErr = err 1323 } 1324 } 1325 } 1326 1327 // return result 1328 return SQLResult{RowsAffected: affected, NewlyInsertedID: newID, Err: returnErr} 1329 } 1330 1331 // ExecByStructParam executes action query string with struct containing parameters to return result, if error, returns error object within result, 1332 // the struct fields' struct tags must match the parameter names, such as: struct tag `db:"customerID"` must match parameter name in sql as ":customerID" 1333 // [ Syntax ] 1334 // 1. in sql = instead of defining ordinal parameters @p1..@pN, 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 1335 // 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) 1336 // 1337 // [ Parameters ] 1338 // 1339 // actionQuery = sql action query, with named parameters using :xyz syntax 1340 // args = required, the struct variable, whose fields having struct tags matching sql parameter names 1341 // 1342 // [ Return Values ] 1343 // 1. SQLResult = represents the sql action result received (including error info if applicable) 1344 func (svr *SQLServer) ExecByStructParam(query string, args interface{}) SQLResult { 1345 // verify if the database connection is good 1346 if err := svr.Ping(); err != nil { 1347 return SQLResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 1348 } 1349 1350 // keep query trimmed 1351 query = util.Trim(query) 1352 1353 // is insert? 1354 isInsert := strings.ToUpper(query[:6]) == "INSERT" 1355 1356 // append action result query 1357 if query[len(query)-1:] != ";" { 1358 query += ";" 1359 } 1360 1361 if isInsert { 1362 query += ";SELECT PKID=SCOPE_IDENTITY(), RowsAffected=@@ROWCOUNT;" 1363 } else { 1364 query += ";SELECT RowsAffected=@@ROWCOUNT;" 1365 } 1366 1367 // perform exec action, and return to caller 1368 var rows *sqlx.Rows 1369 var err error 1370 1371 if svr.tx == nil { 1372 // not in transaction mode, 1373 // action using db object 1374 rows, err = svr.db.NamedQuery(query, args) 1375 } else { 1376 // in transaction mode, 1377 // action using tx object 1378 rows, err = svr.tx.NamedQuery(query, args) 1379 } 1380 1381 if err != nil { 1382 return SQLResult{RowsAffected: 0, NewlyInsertedID: 0, Err: err} 1383 } 1384 1385 // no row - error 1386 if rows == nil { 1387 return SQLResult{RowsAffected: 0, NewlyInsertedID: 0, Err: errors.New("ExecByStructParam() Error: No Row Summary Returned")} 1388 } 1389 1390 if rows.Next() == false { 1391 return SQLResult{RowsAffected: 0, NewlyInsertedID: 0, Err: errors.New("ExecByStructParam() Error: Rows.Next() Yielded No Data")} 1392 } 1393 1394 // evaluate rows affected 1395 var affected int64 1396 var newID int64 1397 1398 var returnErr error 1399 returnErr = nil 1400 1401 if isInsert { 1402 if err = rows.Scan(&newID, &affected); err != nil { 1403 if err != sql.ErrNoRows { 1404 returnErr = err 1405 } 1406 } 1407 } else { 1408 if err = rows.Scan(&affected); err != nil { 1409 if err != sql.ErrNoRows { 1410 returnErr = err 1411 } 1412 } 1413 } 1414 1415 // return result 1416 return SQLResult{RowsAffected: affected, NewlyInsertedID: newID, Err: returnErr} 1417 }