396 lines
10 KiB
Go
396 lines
10 KiB
Go
|
package gorqlite
|
||
|
|
||
|
import "errors"
|
||
|
import "fmt"
|
||
|
import "encoding/json"
|
||
|
|
||
|
/* *****************************************************************
|
||
|
|
||
|
method: Connection.Query()
|
||
|
|
||
|
This is the JSON we get back:
|
||
|
|
||
|
{
|
||
|
"results": [
|
||
|
{
|
||
|
"columns": [
|
||
|
"id",
|
||
|
"name"
|
||
|
],
|
||
|
"types": [
|
||
|
"integer",
|
||
|
"text"
|
||
|
],
|
||
|
"values": [
|
||
|
[
|
||
|
1,
|
||
|
"fiona"
|
||
|
],
|
||
|
[
|
||
|
2,
|
||
|
"sinead"
|
||
|
]
|
||
|
],
|
||
|
"time": 0.0150043
|
||
|
}
|
||
|
],
|
||
|
"time": 0.0220043
|
||
|
}
|
||
|
|
||
|
or
|
||
|
|
||
|
{
|
||
|
"results": [
|
||
|
{
|
||
|
"columns": [
|
||
|
"id",
|
||
|
"name"
|
||
|
],
|
||
|
"types": [
|
||
|
"number",
|
||
|
"text"
|
||
|
],
|
||
|
"values": [
|
||
|
[
|
||
|
null,
|
||
|
"Hulk"
|
||
|
]
|
||
|
],
|
||
|
"time": 4.8958e-05
|
||
|
},
|
||
|
{
|
||
|
"columns": [
|
||
|
"id",
|
||
|
"name"
|
||
|
],
|
||
|
"types": [
|
||
|
"number",
|
||
|
"text"
|
||
|
],
|
||
|
"time": 1.8460000000000003e-05
|
||
|
}
|
||
|
],
|
||
|
"time": 0.000134776
|
||
|
}
|
||
|
|
||
|
or
|
||
|
|
||
|
{
|
||
|
"results": [
|
||
|
{
|
||
|
"error": "near \"nonsense\": syntax error"
|
||
|
}
|
||
|
],
|
||
|
"time": 2.478862
|
||
|
}
|
||
|
|
||
|
* *****************************************************************/
|
||
|
|
||
|
/*
|
||
|
QueryOne() is a convenience method that wraps Query() into a single-statement
|
||
|
method.
|
||
|
*/
|
||
|
func (conn *Connection) QueryOne(sqlStatement string) (qr QueryResult, err error) {
|
||
|
if conn.hasBeenClosed {
|
||
|
qr.Err = errClosed
|
||
|
return qr, errClosed
|
||
|
}
|
||
|
sqlStatements := make([]string, 0)
|
||
|
sqlStatements = append(sqlStatements, sqlStatement)
|
||
|
qra, err := conn.Query(sqlStatements)
|
||
|
return qra[0], err
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Query() is used to perform SELECT operations in the database.
|
||
|
|
||
|
It takes an array of SQL statements and executes them in a single transaction, returning an array of QueryResult vars.
|
||
|
*/
|
||
|
func (conn *Connection) Query(sqlStatements []string) (results []QueryResult, err error) {
|
||
|
results = make([]QueryResult, 0)
|
||
|
|
||
|
if conn.hasBeenClosed {
|
||
|
var errResult QueryResult
|
||
|
errResult.Err = errClosed
|
||
|
results = append(results, errResult)
|
||
|
return results, errClosed
|
||
|
}
|
||
|
trace("%s: Query() for %d statements", conn.ID, len(sqlStatements))
|
||
|
|
||
|
// if we get an error POSTing, that's a showstopper
|
||
|
response, err := conn.rqliteApiPost(api_QUERY, sqlStatements)
|
||
|
if err != nil {
|
||
|
trace("%s: rqliteApiCall() ERROR: %s", conn.ID, err.Error())
|
||
|
var errResult QueryResult
|
||
|
errResult.Err = err
|
||
|
results = append(results, errResult)
|
||
|
return results, err
|
||
|
}
|
||
|
trace("%s: rqliteApiCall() OK", conn.ID)
|
||
|
|
||
|
// if we get an error Unmarshaling, that's a showstopper
|
||
|
var sections map[string]interface{}
|
||
|
err = json.Unmarshal(response, §ions)
|
||
|
if err != nil {
|
||
|
trace("%s: json.Unmarshal() ERROR: %s", conn.ID, err.Error())
|
||
|
var errResult QueryResult
|
||
|
errResult.Err = err
|
||
|
results = append(results, errResult)
|
||
|
return results, err
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
at this point, we have a "results" section and
|
||
|
a "time" section. we can igore the latter.
|
||
|
*/
|
||
|
|
||
|
resultsArray := sections["results"].([]interface{})
|
||
|
trace("%s: I have %d result(s) to parse", conn.ID, len(resultsArray))
|
||
|
|
||
|
numStatementErrors := 0
|
||
|
for n, r := range resultsArray {
|
||
|
trace("%s: parsing result %d", conn.ID, n)
|
||
|
var thisQR QueryResult
|
||
|
thisQR.conn = conn
|
||
|
|
||
|
// r is a hash with columns, types, values, and time
|
||
|
thisResult := r.(map[string]interface{})
|
||
|
|
||
|
// did we get an error?
|
||
|
_, ok := thisResult["error"]
|
||
|
if ok {
|
||
|
trace("%s: have an error on this result: %s", conn.ID, thisResult["error"].(string))
|
||
|
thisQR.Err = errors.New(thisResult["error"].(string))
|
||
|
results = append(results, thisQR)
|
||
|
numStatementErrors++
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// time is a float64
|
||
|
thisQR.Timing = thisResult["time"].(float64)
|
||
|
|
||
|
// column & type are an array of strings
|
||
|
c := thisResult["columns"].([]interface{})
|
||
|
t := thisResult["types"].([]interface{})
|
||
|
for i := 0; i < len(c); i++ {
|
||
|
thisQR.columns = append(thisQR.columns, c[i].(string))
|
||
|
thisQR.types = append(thisQR.types, t[i].(string))
|
||
|
}
|
||
|
|
||
|
// and values are an array of arrays
|
||
|
if thisResult["values"] != nil {
|
||
|
thisQR.values = thisResult["values"].([]interface{})
|
||
|
} else {
|
||
|
trace("%s: fyi, no values this query", conn.ID)
|
||
|
}
|
||
|
|
||
|
thisQR.rowNumber = -1
|
||
|
|
||
|
trace("%s: this result (#col,time) %d %f", conn.ID, len(thisQR.columns), thisQR.Timing)
|
||
|
results = append(results, thisQR)
|
||
|
}
|
||
|
|
||
|
trace("%s: finished parsing, returning %d results", conn.ID, len(results))
|
||
|
|
||
|
if numStatementErrors > 0 {
|
||
|
return results, errors.New(fmt.Sprintf("there were %d statement errors", numStatementErrors))
|
||
|
} else {
|
||
|
return results, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* *****************************************************************
|
||
|
|
||
|
type: QueryResult
|
||
|
|
||
|
* *****************************************************************/
|
||
|
|
||
|
/*
|
||
|
A QueryResult type holds the results of a call to Query(). You could think of it as a rowset.
|
||
|
|
||
|
So if you were to query:
|
||
|
|
||
|
SELECT id, name FROM some_table;
|
||
|
|
||
|
then a QueryResult would hold any errors from that query, a list of columns and types, and the actual row values.
|
||
|
|
||
|
Query() returns an array of QueryResult vars, while QueryOne() returns a single variable.
|
||
|
*/
|
||
|
type QueryResult struct {
|
||
|
conn *Connection
|
||
|
Err error
|
||
|
columns []string
|
||
|
types []string
|
||
|
Timing float64
|
||
|
values []interface{}
|
||
|
rowNumber int64
|
||
|
}
|
||
|
|
||
|
// these are done as getters rather than as public
|
||
|
// variables to prevent monkey business by the user
|
||
|
// that would put us in an inconsistent state
|
||
|
|
||
|
/* *****************************************************************
|
||
|
|
||
|
method: QueryResult.Columns()
|
||
|
|
||
|
* *****************************************************************/
|
||
|
|
||
|
/*
|
||
|
Columns returns a list of the column names for this QueryResult.
|
||
|
*/
|
||
|
func (qr *QueryResult) Columns() []string {
|
||
|
return qr.columns
|
||
|
}
|
||
|
|
||
|
/* *****************************************************************
|
||
|
|
||
|
method: QueryResult.Map()
|
||
|
|
||
|
* *****************************************************************/
|
||
|
|
||
|
/*
|
||
|
Map() returns the current row (as advanced by Next()) as a map[string]interface{}
|
||
|
|
||
|
The key is a string corresponding to a column name.
|
||
|
The value is the corresponding column.
|
||
|
|
||
|
Note that only json values are supported, so you will need to type the interface{} accordingly.
|
||
|
*/
|
||
|
func (qr *QueryResult) Map() (map[string]interface{}, error) {
|
||
|
trace("%s: Map() called for row %d", qr.conn.ID, qr.rowNumber)
|
||
|
ans := make(map[string]interface{})
|
||
|
|
||
|
if qr.rowNumber == -1 {
|
||
|
return ans, errors.New("you need to Next() before you Map(), sorry, it's complicated")
|
||
|
}
|
||
|
|
||
|
thisRowValues := qr.values[qr.rowNumber].([]interface{})
|
||
|
for i := 0; i < len(qr.columns); i++ {
|
||
|
ans[qr.columns[i]] = thisRowValues[i]
|
||
|
}
|
||
|
|
||
|
return ans, nil
|
||
|
}
|
||
|
|
||
|
/* *****************************************************************
|
||
|
|
||
|
method: QueryResult.Next()
|
||
|
|
||
|
* *****************************************************************/
|
||
|
|
||
|
/*
|
||
|
Next() positions the QueryResult result pointer so that Scan() or Map() is ready.
|
||
|
|
||
|
You should call Next() first, but gorqlite will fix it if you call Map() or Scan() before
|
||
|
the initial Next().
|
||
|
|
||
|
A common idiom:
|
||
|
|
||
|
rows := conn.Write(something)
|
||
|
for rows.Next() {
|
||
|
// your Scan/Map and processing here.
|
||
|
}
|
||
|
*/
|
||
|
func (qr *QueryResult) Next() bool {
|
||
|
if qr.rowNumber >= int64(len(qr.values)-1) {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
qr.rowNumber += 1
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
/* *****************************************************************
|
||
|
|
||
|
method: QueryResult.NumRows()
|
||
|
|
||
|
* *****************************************************************/
|
||
|
|
||
|
/*
|
||
|
NumRows() returns the number of rows returned by the query.
|
||
|
*/
|
||
|
func (qr *QueryResult) NumRows() int64 {
|
||
|
return int64(len(qr.values))
|
||
|
}
|
||
|
|
||
|
/* *****************************************************************
|
||
|
|
||
|
method: QueryResult.RowNumber()
|
||
|
|
||
|
* *****************************************************************/
|
||
|
|
||
|
/*
|
||
|
RowNumber() returns the current row number as Next() iterates through the result's rows.
|
||
|
*/
|
||
|
func (qr *QueryResult) RowNumber() int64 {
|
||
|
return qr.rowNumber
|
||
|
}
|
||
|
|
||
|
/* *****************************************************************
|
||
|
|
||
|
method: QueryResult.Scan()
|
||
|
|
||
|
* *****************************************************************/
|
||
|
|
||
|
/*
|
||
|
Scan() takes a list of pointers and then updates them to reflect he current row's data.
|
||
|
|
||
|
Note that only the following data types are used, and they
|
||
|
are a subset of the types JSON uses:
|
||
|
string, for JSON strings
|
||
|
float64, for JSON numbers
|
||
|
int64, as a convenient extension
|
||
|
nil for JSON null
|
||
|
|
||
|
booleans, JSON arrays, and JSON objects are not supported,
|
||
|
since sqlite does not support them.
|
||
|
*/
|
||
|
func (qr *QueryResult) Scan(dest ...interface{}) error {
|
||
|
trace("%s: Scan() called for %d vars", qr.conn.ID, len(dest))
|
||
|
|
||
|
if qr.rowNumber == -1 {
|
||
|
return errors.New("you need to Next() before you Scan(), sorry, it's complicated")
|
||
|
}
|
||
|
|
||
|
if len(dest) != len(qr.columns) {
|
||
|
return errors.New(fmt.Sprintf("expected %d columns but got %d vars\n", len(qr.columns), len(dest)))
|
||
|
}
|
||
|
|
||
|
thisRowValues := qr.values[qr.rowNumber].([]interface{})
|
||
|
for n, d := range dest {
|
||
|
switch d.(type) {
|
||
|
case *int64:
|
||
|
f := int64(thisRowValues[n].(float64))
|
||
|
*d.(*int64) = f
|
||
|
case *float64:
|
||
|
f := float64(thisRowValues[n].(float64))
|
||
|
*d.(*float64) = f
|
||
|
case *string:
|
||
|
s := string(thisRowValues[n].(string))
|
||
|
*d.(*string) = s
|
||
|
default:
|
||
|
return errors.New(fmt.Sprintf("unknown destination type to scan into in variable #%d", n))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
/* *****************************************************************
|
||
|
|
||
|
method: QueryResult.Types()
|
||
|
|
||
|
* *****************************************************************/
|
||
|
|
||
|
/*
|
||
|
Types() returns an array of the column's types.
|
||
|
|
||
|
Note that sqlite will repeat the type you tell it, but in many cases, it's ignored. So you can initialize a column as CHAR(3) but it's really TEXT. See https://www.sqlite.org/datatype3.html
|
||
|
|
||
|
This info may additionally conflict with the reality that your data is being JSON encoded/decoded.
|
||
|
*/
|
||
|
func (qr *QueryResult) Types() []string {
|
||
|
return qr.types
|
||
|
}
|