Skip to content

Commit

Permalink
Support temporal types in Testkit backend, inc. UTC DateTime
Browse files Browse the repository at this point in the history
Signed-off-by: Florent Biville <florent.biville@neo4j.com>
  • Loading branch information
bigmontz authored and fbiville committed Jun 17, 2022
1 parent be34f02 commit 6022ace
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 15 deletions.
17 changes: 17 additions & 0 deletions testkit-backend/2cypher.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package main

import (
"fmt"
"time"

"github.com/neo4j/neo4j-go-driver/v5/neo4j"
)
Expand All @@ -39,6 +40,22 @@ func nativeToCypher(v interface{}) map[string]interface{} {
return valueResponse("CypherBool", x)
case float64:
return valueResponse("CypherFloat", x)
case time.Time:
tzName, offset := x.Zone()
values := map[string]any{
"year": x.Year(),
"month": x.Month(),
"day": x.Day(),
"hour": x.Hour(),
"minute": x.Minute(),
"second": x.Second(),
"nanosecond": x.Nanosecond(),
"utc_offset_s": offset,
}
if tzName != "Offset" {
values["timezone_id"] = tzName
}
return valueResponse("CypherList", values)
case []interface{}:
values := make([]interface{}, len(x))
for i, y := range x {
Expand Down
49 changes: 39 additions & 10 deletions testkit-backend/2native.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,38 +21,67 @@ package main

import (
"fmt"
"time"
)

// Converts received proxied "cypher" types to Go native types.
func cypherToNative(c interface{}) interface{} {
func cypherToNative(c interface{}) (any, error) {
m := c.(map[string]interface{})
d := m["data"].(map[string]interface{})
n := m["name"]
switch n {
case "CypherDateTime":
year := d["year"].(float64)
month := d["month"].(float64)
day := d["day"].(float64)
hour := d["hour"].(float64)
minute := d["minute"].(float64)
second := d["second"].(float64)
nanosecond := d["nanosecond"].(float64)
offset := d["utc_offset_s"].(float64)
var timezone *time.Location
var err error
if timezoneId, found := d["timezone_id"]; !found || timezoneId == nil {
timezone = time.FixedZone("Offset", int(offset))
} else {
timezone, err = time.LoadLocation(timezoneId.(string))
if err != nil {
return nil, err
}
}
// TODO: check whether the offset (possibly from the named time zone) matches
// the offset sent by Testkit, if Testkit sends one along the tz name
return time.Date(int(year), time.Month(month), int(day), int(hour), int(minute), int(second), int(nanosecond), timezone), nil
case "CypherString":
return d["value"].(string)
return d["value"].(string), nil
case "CypherInt":
return int64(d["value"].(float64))
return int64(d["value"].(float64)), nil
case "CypherBool":
return d["value"].(bool)
return d["value"].(bool), nil
case "CypherFloat":
return d["value"].(float64)
return d["value"].(float64), nil
case "CypherNull":
return nil
return nil, nil
case "CypherList":
lc := d["value"].([]interface{})
ln := make([]interface{}, len(lc))
var err error
for i, x := range lc {
ln[i] = cypherToNative(x)
if ln[i], err = cypherToNative(x); err != nil {
return nil, err
}
}
return ln
return ln, nil
case "CypherMap":
mc := d["value"].(map[string]interface{})
mn := make(map[string]interface{})
var err error
for k, x := range mc {
mn[k] = cypherToNative(x)
if mn[k], err = cypherToNative(x); err != nil {
return nil, err
}
}
return mn
return mn, nil
}
panic(fmt.Sprintf("Don't know how to convert %s to native", n))
}
23 changes: 18 additions & 5 deletions testkit-backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,13 +256,16 @@ func (b *backend) toTransactionConfigApply(data map[string]interface{}) func(*ne
}
}

func (b *backend) toCypherAndParams(data map[string]interface{}) (string, map[string]interface{}) {
func (b *backend) toCypherAndParams(data map[string]interface{}) (string, map[string]interface{}, error) {
cypher := data["cypher"].(string)
params, _ := data["params"].(map[string]interface{})
var err error
for i, p := range params {
params[i] = cypherToNative(p)
if params[i], err = cypherToNative(p); err != nil {
return "", nil, err
}
}
return cypher, params
return cypher, params, nil
}

func (b *backend) handleTransactionFunc(isRead bool, data map[string]interface{}) {
Expand Down Expand Up @@ -500,7 +503,11 @@ func (b *backend) handleRequest(req map[string]interface{}) {

case "SessionRun":
sessionState := b.sessionStates[data["sessionId"].(string)]
cypher, params := b.toCypherAndParams(data)
cypher, params, err := b.toCypherAndParams(data)
if err != nil {
b.writeError(err)
return
}
result, err := sessionState.session.Run(ctx, cypher, params, b.toTransactionConfigApply(data))
if err != nil {
b.writeError(err)
Expand Down Expand Up @@ -543,7 +550,11 @@ func (b *backend) handleRequest(req map[string]interface{}) {
if tx, found = b.explicitTransactions[transactionId]; !found {
tx = b.managedTransactions[transactionId]
}
cypher, params := b.toCypherAndParams(data)
cypher, params, err := b.toCypherAndParams(data)
if err != nil {
b.writeError(err)
return
}
result, err := tx.Run(ctx, cypher, params)
if err != nil {
b.writeError(err)
Expand Down Expand Up @@ -735,6 +746,7 @@ func (b *backend) handleRequest(req map[string]interface{}) {
"Feature:API:Liveness.Check",
"Feature:API:Result.List",
"Feature:API:Result.Peek",
"Feature:API:Type.Temporal",
"Feature:Auth:Custom",
"Feature:Auth:Bearer",
"Feature:Auth:Kerberos",
Expand All @@ -744,6 +756,7 @@ func (b *backend) handleRequest(req map[string]interface{}) {
"Feature:Bolt:4.3",
"Feature:Bolt:4.4",
"Feature:Bolt:5.0",
"Feature:Bolt:Patch:UTC",
"Feature:Impersonation",
"Feature:TLS:1.1",
"Feature:TLS:1.2",
Expand Down

0 comments on commit 6022ace

Please sign in to comment.