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