Skip to content

Commit

Permalink
introduce query variables in update query (#12)
Browse files Browse the repository at this point in the history
* save

* Revert "save"

This reverts commit 73357c4.

* query vars

* types

* types

* fix test

* fix test

* build

* update update signature
  • Loading branch information
imperfect-fourth committed Jun 13, 2024
1 parent 00e87fd commit bfddcea
Show file tree
Hide file tree
Showing 10 changed files with 356 additions and 20 deletions.
5 changes: 3 additions & 2 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ func NewClient(gqlEndpoint string, opt *ClientOpts) *Client {
return c
}

func (c *Client) do(q string) (*bytes.Buffer, error) {
func (c *Client) do(q Queryable) (*bytes.Buffer, error) {
reqObj := graphqlRequest{
Query: q,
Query: q.Query(),
Variables: q.Variables(),
}

var reqBytes bytes.Buffer
Expand Down
65 changes: 65 additions & 0 deletions cmd/eywagen/eywatest/eywa_fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ func testTable_NameField(val string) eywa.ModelField[testTable] {
Value: val,
}
}

func testTable_NameVar(val string) eywa.ModelField[testTable] {
return eywa.ModelField[testTable]{
Name: "name",
Value: eywa.QueryVar("testTable_Name", eywa.StringVar[string](val)),
}
}
const testTable_Age eywa.ModelFieldName[testTable] = "age"

func testTable_AgeField(val *int) eywa.ModelField[testTable] {
Expand All @@ -24,6 +31,13 @@ func testTable_AgeField(val *int) eywa.ModelField[testTable] {
Value: val,
}
}

func testTable_AgeVar(val *int) eywa.ModelField[testTable] {
return eywa.ModelField[testTable]{
Name: "age",
Value: eywa.QueryVar("testTable_Age", eywa.NullableIntVar[*int](val)),
}
}
const testTable_ID eywa.ModelFieldName[testTable] = "id"

func testTable_IDField(val int) eywa.ModelField[testTable] {
Expand All @@ -32,6 +46,28 @@ func testTable_IDField(val int) eywa.ModelField[testTable] {
Value: val,
}
}

func testTable_IDVar(val int) eywa.ModelField[testTable] {
return eywa.ModelField[testTable]{
Name: "id",
Value: eywa.QueryVar("testTable_ID", eywa.IntVar[int](val)),
}
}
const testTable_iD eywa.ModelFieldName[testTable] = "idd"

func testTable_iDField(val int32) eywa.ModelField[testTable] {
return eywa.ModelField[testTable]{
Name: "idd",
Value: val,
}
}

func testTable_iDVar(val int32) eywa.ModelField[testTable] {
return eywa.ModelField[testTable]{
Name: "idd",
Value: eywa.QueryVar("testTable_iD", eywa.IntVar[int32](val)),
}
}
const testTable_custom eywa.ModelFieldName[testTable] = "custom"

func testTable_customField(val *customType) eywa.ModelField[testTable] {
Expand All @@ -41,6 +77,13 @@ func testTable_customField(val *customType) eywa.ModelField[testTable] {
}
}

func testTable_customVar[T interface{eywa.JSONValue | eywa.JSONBValue;eywa.TypedValue}](val *customType) eywa.ModelField[testTable] {
return eywa.ModelField[testTable]{
Name: "custom",
Value: eywa.QueryVar("testTable_custom", T{val}),
}
}

func testTable_testTable2(subField eywa.ModelFieldName[testTable2], subFields ...eywa.ModelFieldName[testTable2]) string {
buf := bytes.NewBuffer([]byte("testTable2 {"))
buf.WriteString(string(subField))
Expand All @@ -60,6 +103,28 @@ func testTable_JsonBColField(val jsonbcol) eywa.ModelField[testTable] {
}
}

func testTable_JsonBColVar[T interface{eywa.JSONValue | eywa.JSONBValue;eywa.TypedValue}](val jsonbcol) eywa.ModelField[testTable] {
return eywa.ModelField[testTable]{
Name: "jsonb_col",
Value: eywa.QueryVar("testTable_JsonBCol", T{val}),
}
}
const testTable_RR eywa.ModelFieldName[testTable] = "r"

func testTable_RRField(val R) eywa.ModelField[testTable] {
return eywa.ModelField[testTable]{
Name: "r",
Value: val,
}
}

func testTable_RRVar(val R) eywa.ModelField[testTable] {
return eywa.ModelField[testTable]{
Name: "r",
Value: eywa.QueryVar("testTable_RR", eywa.StringVar[R](val)),
}
}

const testTable2_ID eywa.ModelFieldName[testTable2] = "id"

func testTable2_IDField(val uuid.UUID) eywa.ModelField[testTable2] {
Expand Down
16 changes: 12 additions & 4 deletions cmd/eywagen/eywatest/eywa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func TestUpdateQuery(t *testing.T) {
eywa.Eq[testTable](testTable_IDField(3)),
).Set(
testTable_NameField("updatetest"),
testTable_JsonBColField(jsonbcol{
testTable_JsonBColVar[eywa.JSONBValue](jsonbcol{
StrField: "abcd",
IntField: 2,
BoolField: false,
Expand All @@ -53,15 +53,23 @@ func TestUpdateQuery(t *testing.T) {
testTable_ID,
)

expected := `mutation update_test_table {
update_test_table(where: {id: {_eq: 3}}, _set: {name: "updatetest", jsonb_col: "{\"str_field\":\"abcd\",\"int_field\":2,\"bool_field\":false,\"arr_field\":[1,2,3]}"}) {
expected := `mutation update_test_table($testTable_JsonBCol: jsonb) {
update_test_table(where: {id: {_eq: 3}}, _set: {name: "updatetest", jsonb_col: $testTable_JsonBCol}) {
returning {
id
name
}
}
}`
if assert.Equal(t, expected, q.Query()) {
expectedVars := map[string]interface{}{
"testTable_JsonBCol": jsonbcol{
StrField: "abcd",
IntField: 2,
BoolField: false,
ArrField: []int{1, 2, 3},
},
}
if assert.Equal(t, expected, q.Query()) && assert.Equal(t, expectedVars, q.Variables()) {
accessKey := os.Getenv("TEST_HGE_ACCESS_KEY")
c := eywa.NewClient("https://aware-cowbird-80.hasura.app/v1/graphql", &eywa.ClientOpts{
Headers: map[string]string{
Expand Down
6 changes: 5 additions & 1 deletion cmd/eywagen/eywatest/eywatest.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@ package eywatest

import "github.com/google/uuid"

//go:generate eywagen -types testTable,testTable2
//go:generate ../eywagen -types testTable,testTable2
type testTable struct {
Name string `json:"name"`
Age *int `json:"age"`
ID int `json:"id,omitempty"`
iD int32 `json:"idd,omitempty"`
custom *customType `json:"custom"`
testTable2 *testTable2 `json:"testTable2"`
JsonBCol jsonbcol `json:"jsonb_col"`
RR R `json:"r"`
}

type R string

func (t testTable) ModelName() string {
return "test_table"
}
Expand Down
96 changes: 93 additions & 3 deletions cmd/eywagen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,23 @@ func %sField(val %s) eywa.ModelField[%s] {
}
}
`
modelScalarVarFunc = `
func %sVar(val %s) eywa.ModelField[%s] {
return eywa.ModelField[%s]{
Name: "%s",
Value: eywa.QueryVar("%s", %s[%s](val)),
}
}
`
modelVarFunc = `
func %sVar[T interface{%s;eywa.TypedValue}](val %s) eywa.ModelField[%s] {
return eywa.ModelField[%s]{
Name: "%s",
Value: eywa.QueryVar("%s", T{val}),
}
}
`

modelRelationshipNameFunc = `
func %s(subField eywa.ModelFieldName[%s], subFields ...eywa.ModelFieldName[%s]) string {
buf := bytes.NewBuffer([]byte("%s {"))
Expand Down Expand Up @@ -143,8 +160,9 @@ func parseType(typeName string, pkg *types.Package, contents *fileContent) {
if fieldTypeNameFull[0] == '*' {
fieldTypeName = fieldTypeNameFull[1:]
}
fieldScalarGqlType := gqlType(fieldType.Underlying().String())

// *struct -> struct, *[] -> []
// *struct -> struct, *[] -> [], *int -> int, etc
if ptr, ok := fieldType.(*types.Pointer); ok {
fieldType = ptr.Elem()
}
Expand All @@ -154,15 +172,19 @@ func parseType(typeName string, pkg *types.Package, contents *fileContent) {
} else if array, ok := fieldType.(*types.Array); ok {
fieldType = array.Elem()
}

// struct -> *struct
var fieldGqlType string
if _, ok := fieldType.Underlying().(*types.Struct); ok {
fieldType = types.NewPointer(fieldType)
fieldGqlType = "eywa.JSONValue | eywa.JSONBValue"
} else if _, ok := fieldType.Underlying().(*types.Map); ok {
fieldGqlType = "eywa.JSONValue | eywa.JSONBValue"
}

switch fieldType := fieldType.(type) {
case *types.Pointer:
if types.NewMethodSet(fieldType).Lookup(pkg, "ModelName") != nil {
fieldMethodSet := types.NewMethodSet(fieldType)
if m := fieldMethodSet.Lookup(pkg, "ModelName"); m != nil && m.Type().String() == "func() string" {
contents.importsMap["bytes"] = true
contents.content.WriteString(fmt.Sprintf(
modelRelationshipNameFunc,
Expand All @@ -187,6 +209,30 @@ func parseType(typeName string, pkg *types.Package, contents *fileContent) {
typeName,
fieldName,
))
if fieldScalarGqlType != "" {
contents.content.WriteString(fmt.Sprintf(
modelScalarVarFunc,
fmt.Sprintf("%s_%s", typeName, field.Name()),
fieldTypeNameFull,
typeName,
typeName,
fieldName,
fmt.Sprintf("%s_%s", typeName, field.Name()),
fmt.Sprintf("eywa.%s", fieldScalarGqlType),
fieldTypeNameFull,
))
} else if fieldGqlType != "" {
contents.content.WriteString(fmt.Sprintf(
modelVarFunc,
fmt.Sprintf("%s_%s", typeName, field.Name()),
fieldGqlType,
fieldTypeNameFull,
typeName,
typeName,
fieldName,
fmt.Sprintf("%s_%s", typeName, field.Name()),
))
}
}
default:
contents.content.WriteString(fmt.Sprintf(
Expand All @@ -203,6 +249,30 @@ func parseType(typeName string, pkg *types.Package, contents *fileContent) {
typeName,
fieldName,
))
if fieldScalarGqlType != "" {
contents.content.WriteString(fmt.Sprintf(
modelScalarVarFunc,
fmt.Sprintf("%s_%s", typeName, field.Name()),
fieldTypeNameFull,
typeName,
typeName,
fieldName,
fmt.Sprintf("%s_%s", typeName, field.Name()),
fmt.Sprintf("eywa.%sVar", fieldScalarGqlType),
fieldTypeNameFull,
))
} else if fieldGqlType != "" {
contents.content.WriteString(fmt.Sprintf(
modelVarFunc,
fmt.Sprintf("%s_%s", typeName, field.Name()),
fieldGqlType,
fieldTypeNameFull,
typeName,
typeName,
fieldName,
fmt.Sprintf("%s_%s", typeName, field.Name()),
))
}
}
}
for _, t := range recurseParse {
Expand Down Expand Up @@ -252,3 +322,23 @@ func parseFieldTypeName(name, rootPkgPath string) (sourcePkgPath, typeName strin
}
return matches[2], fmt.Sprintf("%s%s.%s", matches[1], matches[3], matches[4])
}

var gqlTypes = map[string]string{
"bool": "Boolean",
"*bool": "NullableBoolean",
"int": "Int",
"*int": "NullableInt",
"float": "Float",
"*float": "NullableFloat",
"string": "String",
"*string": "NullableString",
}

func gqlType(fieldType string) string {
for k, v := range gqlTypes {
if strings.HasPrefix(fieldType, k) {
return v
}
}
return ""
}
23 changes: 20 additions & 3 deletions eywa.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (f RawField) GetName() string {
return f.Name
}
func (f RawField) GetValue() string {
if val, ok := f.Value.(gqlMarshaller); ok {
if val, ok := f.Value.(gqlMarshaler); ok {
return val.marshalGQL()
}
val, _ := json.Marshal(f.Value)
Expand All @@ -66,6 +66,9 @@ func (f RawField) GetValue() string {
}
return string(val)
}
func (f RawField) GetRawValue() interface{} {
return f.Value
}

type ModelField[M Model] struct {
Name string
Expand All @@ -76,7 +79,11 @@ func (f ModelField[M]) GetName() string {
return f.Name
}
func (f ModelField[M]) GetValue() string {
if val, ok := f.Value.(gqlMarshaller); ok {
if var_, ok := f.Value.(queryVar); ok {
return fmt.Sprintf("$%s", var_.name)
}

if val, ok := f.Value.(gqlMarshaler); ok {
return val.marshalGQL()
}

Expand All @@ -90,11 +97,15 @@ func (f ModelField[M]) GetValue() string {
}
return string(val)
}
func (f ModelField[M]) GetRawValue() interface{} {
return f.Value
}

type Field[M Model] interface {
RawField | ModelField[M]
GetName() string
GetValue() string
GetRawValue() interface{}
}

type fieldArr[M Model, F Field[M]] []F
Expand All @@ -118,10 +129,12 @@ func (fs fieldArr[M, MF]) marshalGQL() string {

type Queryable interface {
Query() string
Variables() map[string]interface{}
}

type QuerySkeleton[M Model, FN FieldName[M], F Field[M]] struct {
ModelName string
queryVars queryVarArr
// fields ModelFieldArr[M, FN, F]
queryArgs[M, FN, F]
}
Expand Down Expand Up @@ -195,8 +208,12 @@ func (sq GetQuery[M, FN, F]) Query() string {
)
}

func (sq GetQuery[M, FN, F]) Variables() map[string]interface{} {
return nil
}

func (sq GetQuery[M, FN, F]) Exec(client *Client) ([]M, error) {
respBytes, err := client.do(sq.Query())
respBytes, err := client.do(sq)
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit bfddcea

Please sign in to comment.