-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
294 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"log" | ||
"os" | ||
|
||
"github.com/vippsas/go-querysql/querysql" | ||
|
||
_ "github.com/denisenkom/go-mssqldb" | ||
) | ||
|
||
func initdb() (*sql.DB, error) { | ||
dsn := os.Getenv("SQL_DSN") | ||
if dsn == "" { | ||
dsn = "sqlserver://127.0.0.1:1433?database=master&user id=sa&password=VippsPw1" | ||
} | ||
return sql.Open("sqlserver", dsn) | ||
} | ||
|
||
func main() { | ||
sqldb, err := initdb() | ||
if err != nil { | ||
panic(err.Error()) | ||
} | ||
|
||
logger := log.Default() | ||
ctx := querysql.WithLogger(context.Background(), querysql.StdMSSQLLogger(logger)) | ||
|
||
qry := `select _log='info', message='hello world from a query'` | ||
_, err = querysql.ExecContext(ctx, sqldb, qry, "world") | ||
if err != nil { | ||
panic(err.Error()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"os" | ||
|
||
"github.com/sirupsen/logrus" | ||
"github.com/vippsas/go-querysql/querysql" | ||
|
||
_ "github.com/denisenkom/go-mssqldb" | ||
) | ||
|
||
func initdb() (*sql.DB, error) { | ||
dsn := os.Getenv("SQL_DSN") | ||
if dsn == "" { | ||
dsn = "sqlserver://127.0.0.1:1433?database=master&user id=sa&password=VippsPw1" | ||
} | ||
return sql.Open("sqlserver", dsn) | ||
} | ||
|
||
func main() { | ||
sqldb, err := initdb() | ||
if err != nil { | ||
panic(err.Error()) | ||
} | ||
|
||
logger := logrus.StandardLogger() | ||
ctx := querysql.WithLogger(context.Background(), querysql.LogrusMSSQLLogger(logger, logrus.InfoLevel)) | ||
|
||
qry := `select _log='info', message='hello world from a query'` | ||
_, err = querysql.ExecContext(ctx, sqldb, qry, "world") | ||
if err != nil { | ||
panic(err.Error()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"fmt" | ||
"os" | ||
|
||
_ "github.com/denisenkom/go-mssqldb" | ||
"github.com/sirupsen/logrus" | ||
"github.com/vippsas/go-querysql/querysql" | ||
) | ||
|
||
func initdb() (*sql.DB, error) { | ||
dsn := os.Getenv("SQL_DSN") | ||
if dsn == "" { | ||
dsn = "sqlserver://127.0.0.1:1433?database=master&user id=sa&password=VippsPw1" | ||
} | ||
return sql.Open("sqlserver", dsn) | ||
} | ||
|
||
func populatedb(ctx context.Context, sqldb *sql.DB) error { | ||
qry := `if object_id('dbo.MyUsers', 'U') is not null drop table MyUsers | ||
create table MyUsers ( | ||
Id int identity(1,1) primary key, | ||
UserName nvarchar(50) not null, | ||
UserAge int | ||
); | ||
insert into MyUsers (UserName, UserAge) values ('Bob Doe', 42); | ||
insert into MyUsers (UserName, UserAge) values ('Johny Doe', 10); | ||
` | ||
_, err := querysql.ExecContext(ctx, sqldb, qry, "world") | ||
return err | ||
} | ||
|
||
func processUsers(ctx context.Context, sqldb *sql.DB) error { | ||
qry := `select * from MyUsers; | ||
select _function='UserMetrics', label='size.MyUsers', numProcessed=@@rowcount; | ||
` | ||
type rowType struct { | ||
Id int | ||
UserName string | ||
UserAge int | ||
} | ||
users, err := querysql.Slice[rowType](ctx, sqldb, qry, "world") | ||
fmt.Println(users) | ||
return err | ||
} | ||
|
||
// This function is going to be called when the query in processUsers is run | ||
func UserMetrics(label string, count int) { | ||
fmt.Println(label, count) | ||
} | ||
|
||
func main() { | ||
sqldb, err := initdb() | ||
if err != nil { | ||
panic(err.Error()) | ||
} | ||
|
||
logger := logrus.StandardLogger() | ||
ctx := querysql.WithLogger(context.Background(), querysql.LogrusMSSQLLogger(logger, logrus.InfoLevel)) | ||
ctx = querysql.WithDispatcher(ctx, querysql.GoMSSQLDispatcher([]interface{}{ | ||
UserMetrics, | ||
})) | ||
|
||
err = populatedb(ctx, sqldb) | ||
if err != nil { | ||
panic(err.Error()) | ||
} | ||
|
||
err = processUsers(ctx, sqldb) | ||
if err != nil { | ||
panic(err.Error()) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package querysql | ||
|
||
import ( | ||
"database/sql" | ||
"encoding/hex" | ||
"fmt" | ||
"log" | ||
|
||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
func StdMSSQLLogger(logger *log.Logger) RowsLogger { | ||
defaultLogLevel := logrus.InfoLevel | ||
return func(rows *sql.Rows) error { | ||
var logLevel string | ||
|
||
cols, err := rows.Columns() | ||
if err != nil { | ||
return err | ||
} | ||
colTypes, err := rows.ColumnTypes() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// For logging just scan *everything* into a string type straight from SQL driver to make things simple here... | ||
// The first column is the log level by protocol of RowsLogger. | ||
fields := make([]interface{}, len(cols)) | ||
scanPointers := make([]interface{}, len(cols)) | ||
scanPointers[0] = &logLevel | ||
for i := 1; i < len(cols); i++ { | ||
scanPointers[i] = &fields[i] | ||
} | ||
|
||
hadRow := false | ||
for rows.Next() { | ||
hadRow = true | ||
if err = rows.Scan(scanPointers...); err != nil { | ||
return err | ||
} | ||
parsedLogLevel, err := logrus.ParseLevel(logLevel) | ||
if err != nil { | ||
emitLogEntry(logger, logrus.Fields{ | ||
"event": "invalid.log.level", | ||
"invalid.level": logLevel, | ||
}, logrus.ErrorLevel) | ||
parsedLogLevel = defaultLogLevel | ||
} | ||
|
||
logrusFields := logrus.Fields{} | ||
for i, value := range fields { | ||
if i == 0 { | ||
continue | ||
} | ||
// we post-process the types of the values a bit to make some types more readable in logs | ||
switch typedValue := value.(type) { | ||
case []uint8: | ||
switch colTypes[i].DatabaseTypeName() { | ||
case "MONEY": | ||
value = string(typedValue) | ||
case "UNIQUEIDENTIFIER": | ||
value, err = ParseSQLUUIDBytes(typedValue) | ||
if err != nil { | ||
return fmt.Errorf("could not decode UUID from SQL: %w", err) | ||
} | ||
default: | ||
value = "0x" + hex.EncodeToString(typedValue) | ||
} | ||
} | ||
logrusFields[cols[i]] = value | ||
} | ||
emitLogEntry(logger, logrusFields, parsedLogLevel) | ||
} | ||
if err = rows.Err(); err != nil { | ||
return err | ||
} | ||
if !hadRow { | ||
// it can be quite annoying to have logging of empty tables turn into nothing, so log | ||
// an indication that the log statement was there, with an empty table | ||
// in this case loglevel is unreachable, and we really can only log the keys, | ||
// but let's hope INFO isn't overboard | ||
logrusFields := logrus.Fields{} | ||
logrusFields["_norows"] = true | ||
for _, col := range cols[1:] { | ||
logrusFields[col] = "" | ||
} | ||
emitLogEntry(logger, logrusFields, defaultLogLevel) | ||
} | ||
return nil | ||
} | ||
} | ||
|
||
func emitLogEntry(logger *log.Logger, fields logrus.Fields, level logrus.Level) { | ||
switch level { | ||
case logrus.PanicLevel: | ||
logger.Panic() | ||
case logrus.FatalLevel: | ||
logger.Fatal() | ||
case logrus.ErrorLevel, logrus.WarnLevel, logrus.DebugLevel, logrus.TraceLevel, logrus.InfoLevel: | ||
logger.Print() // TODO(dsf): Add level into the message | ||
default: | ||
panic(fmt.Sprintf("Log level %d not handled in emitLogEntry", level)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package querysql | ||
|
||
import ( | ||
"errors" | ||
|
||
"github.com/google/uuid" | ||
) | ||
|
||
func ParseSQLUUIDBytes(v []uint8) (uuid.UUID, error) { | ||
if len(v) != 16 { | ||
return uuid.UUID{}, errors.New("ParseSQLUUIDBytes: did not get 16 bytes") | ||
} | ||
var shuffled [16]uint8 | ||
// This: select convert(uniqueidentifier, '00010203-0405-0607-0809-0a0b0c0d0e0f') | ||
// Returns this when passed to uuid.FromBytes: | ||
// 03020100-0504-0706-0809-0a0b0c0d0e0f | ||
// So, shuffling first | ||
shuffled[0x0] = v[0x3] | ||
shuffled[0x1] = v[0x2] | ||
shuffled[0x2] = v[0x1] | ||
shuffled[0x3] = v[0x0] | ||
|
||
shuffled[0x4] = v[0x5] | ||
shuffled[0x5] = v[0x4] | ||
|
||
shuffled[0x6] = v[0x7] | ||
shuffled[0x7] = v[0x6] | ||
|
||
// The rest are not shuffled :shrug: | ||
shuffled[0x8] = v[0x8] | ||
shuffled[0x9] = v[0x9] | ||
|
||
shuffled[0xa] = v[0xa] | ||
shuffled[0xb] = v[0xb] | ||
shuffled[0xc] = v[0xc] | ||
shuffled[0xd] = v[0xd] | ||
shuffled[0xe] = v[0xe] | ||
shuffled[0xf] = v[0xf] | ||
|
||
return uuid.FromBytes(shuffled[:]) | ||
} |