Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added sql-nullstring example #501

Merged
merged 3 commits into from
Feb 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions examples/sql-nullstring/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Go GraphQL SQL null string example

<a target="_blank" rel="noopener noreferrer" href="https://golang.org/pkg/database/sql/#NullString">database/sql Nullstring</a> implementation, with JSON marshalling interfaces.

To run the program, go to the directory
`cd examples/sql-nullstring`

Run the example
`go run main.go`

## sql.NullString

On occasion you will encounter sql fields that are nullable, as in

```sql
CREATE TABLE persons (
id INT PRIMARY KEY,
name TEXT NOT NULL,
favorite_dog TEXT -- this field can have a NULL value
)
```

For the struct

```golang
import "database/sql"

type Person struct {
ID int `json:"id" sql:"id"`
Name string `json:"name" sql:"name"`
FavoriteDog sql.NullString `json:"favorite_dog" sql:"favorite_dog"`
}
```

But `graphql` would render said field as an object `{{ false}}` or `{{Bulldog true}}`, depending on their validity.

With this implementation, `graphql` would render the null items as an empty string (`""`), but would be saved in the database as `NULL`, appropriately.

The pattern can be extended to include other `database/sql` null types.
252 changes: 252 additions & 0 deletions examples/sql-nullstring/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
package main

import (
"database/sql"
"encoding/json"
"fmt"
"github.com/graphql-go/graphql"
"github.com/graphql-go/graphql/language/ast"
"log"
)

// NullString to be used in place of sql.NullString
type NullString struct {
sql.NullString
}

// MarshalJSON from the json.Marshaler interface
func (v NullString) MarshalJSON() ([]byte, error) {
if v.Valid {
return json.Marshal(v.String)
}
return json.Marshal(nil)
}

// UnmarshalJSON from the json.Unmarshaler interface
func (v *NullString) UnmarshalJSON(data []byte) error {
var x *string
if err := json.Unmarshal(data, &x); err != nil {
return err
}
if x != nil {
v.String = *x
v.Valid = true
} else {
v.Valid = false
}
return nil
}

// NewNullString create a new null string. Empty string evaluates to an
// "invalid" NullString
func NewNullString(value string) *NullString {
var null NullString
if value != "" {
null.String = value
null.Valid = true
return &null
}
null.Valid = false
return &null
}

// SerializeNullString serializes `NullString` to a string
func SerializeNullString(value interface{}) interface{} {
switch value := value.(type) {
case NullString:
return value.String
case *NullString:
v := *value
return v.String
default:
return nil
}
}

// ParseNullString parses GraphQL variables from `string` to `CustomID`
func ParseNullString(value interface{}) interface{} {
switch value := value.(type) {
case string:
return NewNullString(value)
case *string:
return NewNullString(*value)
default:
return nil
}
}

// ParseLiteralNullString parses GraphQL AST value to `NullString`.
func ParseLiteralNullString(valueAST ast.Value) interface{} {
switch valueAST := valueAST.(type) {
case *ast.StringValue:
return NewNullString(valueAST.Value)
default:
return nil
}
}

// NullableString graphql *Scalar type based of NullString
var NullableString = graphql.NewScalar(graphql.ScalarConfig{
Name: "NullableString",
Description: "The `NullableString` type repesents a nullable SQL string.",
Serialize: SerializeNullString,
ParseValue: ParseNullString,
ParseLiteral: ParseLiteralNullString,
})

/*
CREATE TABLE persons (
favorite_dog TEXT -- is a nullable field
);

*/

// Person noqa
type Person struct {
Name string `json:"name"`
FavoriteDog *NullString `json:"favorite_dog"` // Some people don't like dogs ¯\_(ツ)_/¯
}

// PersonType noqa
var PersonType = graphql.NewObject(graphql.ObjectConfig{
Name: "Person",
Fields: graphql.Fields{
"name": &graphql.Field{
Type: graphql.String,
},
"favorite_dog": &graphql.Field{
Type: NullableString,
},
},
})

func main() {
schema, err := graphql.NewSchema(graphql.SchemaConfig{
Query: graphql.NewObject(graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"people": &graphql.Field{
Type: graphql.NewList(PersonType),
Args: graphql.FieldConfigArgument{
"favorite_dog": &graphql.ArgumentConfig{
Type: NullableString,
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
dog, dogOk := p.Args["favorite_dog"].(*NullString)
people := []Person{
Person{Name: "Alice", FavoriteDog: NewNullString("Yorkshire Terrier")},
// `Bob`'s favorite dog will be saved as null in the database
Person{Name: "Bob", FavoriteDog: NewNullString("")},
Person{Name: "Chris", FavoriteDog: NewNullString("French Bulldog")},
}
switch {
case dogOk:
log.Printf("favorite_dog from arguments: %+v", dog)
dogPeople := make([]Person, 0)
for _, p := range people {
if p.FavoriteDog.Valid {
if p.FavoriteDog.String == dog.String {
dogPeople = append(dogPeople, p)
}
}
}
return dogPeople, nil
default:
return people, nil
}
},
},
},
}),
})
if err != nil {
log.Fatal(err)
}
query := `
query {
people {
name
favorite_dog
}
}`
queryWithArgument := `
query {
people(favorite_dog: "Yorkshire Terrier") {
name
favorite_dog
}
}`
r1 := graphql.Do(graphql.Params{
Schema: schema,
RequestString: query,
})
r2 := graphql.Do(graphql.Params{
Schema: schema,
RequestString: queryWithArgument,
})
if len(r1.Errors) > 0 {
log.Fatal(r1)
}
if len(r2.Errors) > 0 {
log.Fatal(r1)
}
b1, err := json.MarshalIndent(r1, "", " ")
b2, err := json.MarshalIndent(r2, "", " ")
if err != nil {
log.Fatal(err)

}
fmt.Printf("\nQuery: %+v\n", string(query))
fmt.Printf("\nResult: %+v\n", string(b1))
fmt.Printf("\nQuery (with arguments): %+v\n", string(queryWithArgument))
fmt.Printf("\nResult (with arguments): %+v\n", string(b2))
}

/* Output:
Query:
query {
people {
name
favorite_dog
}
}

Result: {
"data": {
"people": [
{
"favorite_dog": "Yorkshire Terrier",
"name": "Alice"
},
{
"favorite_dog": "",
"name": "Bob"
},
{
"favorite_dog": "French Bulldog",
"name": "Chris"
}
]
}
}

Query (with arguments):
query {
people(favorite_dog: "Yorkshire Terrier") {
name
favorite_dog
}
}

Result (with arguments): {
"data": {
"people": [
{
"favorite_dog": "Yorkshire Terrier",
"name": "Alice"
}
]
}
}
*/