Go GraphQL SQL null string example
database/sql Nullstring 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.NullStringOn occasion you will encounter sql fields that are nullable, as in
CREATE TABLE persons ( id INT PRIMARY KEY, name TEXT NOT NULL, favorite_dog TEXT -- this field can have a NULL value)
For the struct
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.
main.go
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.NullStringtype NullString struct { sql.NullString}
// MarshalJSON from the json.Marshaler interfacefunc (v NullString) MarshalJSON() ([]byte, error) { if v.Valid { return json.Marshal(v.String) } return json.Marshal(nil)}
// UnmarshalJSON from the json.Unmarshaler interfacefunc (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" NullStringfunc 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 stringfunc 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 NullStringvar 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 noqatype Person struct { Name string `json:"name"` FavoriteDog *NullString `json:"favorite_dog"` // Some people don't like dogs ¯\_(ツ)_/¯}
// PersonType noqavar 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, "", " ") if err != nil { log.Fatal(err) } 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" } ] }}*/