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  }