Skip to content

Commit 025f51b

Browse files
authored
feat(sdk/go): implement mysql SDK for TinyGo (#1794)
Signed-off-by: Patrick <[email protected]>
1 parent 79496ff commit 025f51b

File tree

5 files changed

+919
-0
lines changed

5 files changed

+919
-0
lines changed

Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ build-examples: $(EXAMPLES_DIR)/tinygo-redis/main.wasm
2929
build-examples: $(EXAMPLES_DIR)/tinygo-key-value/main.wasm
3030
build-examples: $(EXAMPLES_DIR)/tinygo-sqlite/main.wasm
3131
build-examples: $(EXAMPLES_DIR)/tinygo-llm/main.wasm
32+
build-examples: $(EXAMPLES_DIR)/tinygo-mysql/main.wasm
3233

3334
$(EXAMPLES_DIR)/%/main.wasm: $(EXAMPLES_DIR)/%/main.go
3435
tinygo build -target=wasi -gc=leaking -no-debug -o $@ $<
@@ -44,6 +45,7 @@ GENERATED_SPIN_REDIS = redis/spin-redis.c redis/spin-redis.h
4445
GENERATED_KEY_VALUE = key_value/key-value.c key_value/key-value.h
4546
GENERATED_SQLITE = sqlite/sqlite.c sqlite/sqlite.h
4647
GENERATED_LLM = llm/llm.c llm/llm.h
48+
GENERATED_OUTBOUND_MYSQL = mysql/outbound-mysql.c mysql/outbound-mysql.h
4749

4850
SDK_VERSION_SOURCE_FILE = sdk_version/sdk-version-go-template.c
4951

@@ -61,6 +63,7 @@ generate: $(GENERATED_OUTBOUND_HTTP) $(GENERATED_SPIN_HTTP)
6163
generate: $(GENERATED_OUTBOUND_REDIS) $(GENERATED_SPIN_REDIS)
6264
generate: $(GENERATED_SPIN_CONFIG) $(GENERATED_KEY_VALUE)
6365
generate: $(GENERATED_SQLITE) $(GENERATED_LLM)
66+
generate: $(GENERATED_OUTBOUND_MYSQL)
6467
generate: $(SDK_VERSION_DEST_FILES)
6568

6669
$(SDK_VERSION_DEST_FILES): $(SDK_VERSION_SOURCE_FILE)
@@ -92,6 +95,9 @@ $(GENERATED_SQLITE):
9295
$(GENERATED_LLM):
9396
wit-bindgen c --import ../../wit/ephemeral/llm.wit --out-dir ./llm
9497

98+
$(GENERATED_OUTBOUND_MYSQL):
99+
wit-bindgen c --import ../../wit/ephemeral/outbound-mysql.wit --out-dir ./mysql
100+
95101
# ----------------------------------------------------------------------
96102
# Cleanup
97103
# ----------------------------------------------------------------------
@@ -102,6 +108,7 @@ clean:
102108
rm -f $(GENERATED_OUTBOUND_REDIS) $(GENERATED_SPIN_REDIS)
103109
rm -f $(GENERATED_KEY_VALUE) $(GENERATED_SQLITE)
104110
rm -f $(GENERATED_LLM)
111+
rm -f $(GENERATED_OUTBOUND_MYSQL)
105112
rm -f $(GENERATED_SDK_VERSION)
106113
rm -f http/testdata/http-tinygo/main.wasm
107114
rm -f $(EXAMPLES_DIR)/http-tinygo/main.wasm
@@ -111,4 +118,5 @@ clean:
111118
rm -f $(EXAMPLES_DIR)/tinygo-key-value/main.wasm
112119
rm -f $(EXAMPLES_DIR)/tinygo-sqlite/main.wasm
113120
rm -f $(EXAMPLES_DIR)/tinygo-llm/main.wasm
121+
rm -f $(EXAMPLES_DIR)/tinygo-outbound-mysql/main.wasm
114122
rm -f $(SDK_VERSION_DEST_FILES)

mysql/internals.go

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
package mysql
2+
3+
// #include "outbound-mysql.h"
4+
// #include<stdlib.h>
5+
import "C"
6+
import (
7+
"errors"
8+
"fmt"
9+
"reflect"
10+
"unsafe"
11+
)
12+
13+
func execute(address string, statement string, args []any) error {
14+
var ret C.outbound_mysql_expected_unit_mysql_error_t
15+
defer C.outbound_mysql_expected_unit_mysql_error_free(&ret)
16+
17+
mysqlAddress := outboundMysqlStr(address)
18+
mysqlStatement := outboundMysqlStr(statement)
19+
params := toOutboundMysqlParameterListValue(args)
20+
21+
C.outbound_mysql_execute(&mysqlAddress, &mysqlStatement, &params, &ret)
22+
23+
if ret.is_err {
24+
spinErr := (*C.outbound_mysql_mysql_error_t)(unsafe.Pointer(&ret.val))
25+
return toErr(spinErr)
26+
}
27+
28+
return nil
29+
}
30+
31+
func query(address string, statement string, args []any) (*rows, error) {
32+
var ret C.outbound_mysql_expected_row_set_mysql_error_t
33+
defer C.outbound_mysql_expected_row_set_mysql_error_free(&ret)
34+
35+
mysqlAddress := outboundMysqlStr(address)
36+
mysqlStatement := outboundMysqlStr(statement)
37+
params := toOutboundMysqlParameterListValue(args)
38+
39+
C.outbound_mysql_query(&mysqlAddress, &mysqlStatement, &params, &ret)
40+
41+
if ret.is_err {
42+
spinErr := (*C.outbound_mysql_mysql_error_t)(unsafe.Pointer(&ret.val))
43+
return nil, toErr(spinErr)
44+
}
45+
46+
qr := (*C.outbound_mysql_row_set_t)(unsafe.Pointer(&ret.val))
47+
48+
columns, columnType := fromOutboundMysqlListColoum(qr.columns)
49+
50+
rs, err := fromOutboundMysqlListRow(qr.rows)
51+
if err != nil {
52+
return nil, err
53+
}
54+
55+
result := &rows{
56+
columns: columns,
57+
columnType: columnType,
58+
rows: rs,
59+
len: int(qr.rows.len),
60+
}
61+
62+
return result, nil
63+
}
64+
65+
func fromOutboundMysqlListRow(list C.outbound_mysql_list_row_t) ([][]any, error) {
66+
var err error
67+
listLen := int(list.len)
68+
ret := make([][]any, listLen)
69+
slice := unsafe.Slice(list.ptr, listLen)
70+
for i := 0; i < listLen; i++ {
71+
row := *((*C.outbound_mysql_row_t)(unsafe.Pointer(&slice[i])))
72+
ret[i], err = fromOutboundMysqlRow(row)
73+
if err != nil {
74+
return nil, err
75+
}
76+
}
77+
return ret, nil
78+
79+
}
80+
81+
func fromOutboundMysqlRow(row C.outbound_mysql_row_t) ([]any, error) {
82+
var err error
83+
rowLen := int(row.len)
84+
ret := make([]any, rowLen)
85+
slice := unsafe.Slice(row.ptr, rowLen)
86+
for i := 0; i < rowLen; i++ {
87+
value := *((*C.outbound_mysql_db_value_t)(unsafe.Pointer(&slice[i])))
88+
ret[i], err = fromOutboundMysqlDbValue(value)
89+
if err != nil {
90+
return nil, err
91+
}
92+
}
93+
return ret, err
94+
}
95+
96+
func fromOutboundMysqlListColoum(list C.outbound_mysql_list_column_t) ([]string, []uint8) {
97+
coloumLen := int(list.len)
98+
ret := make([]string, coloumLen)
99+
retType := make([]uint8, coloumLen)
100+
slice := unsafe.Slice(list.ptr, coloumLen)
101+
for i := 0; i < coloumLen; i++ {
102+
column := *((*C.outbound_mysql_column_t)(unsafe.Pointer(&slice[i])))
103+
ret[i], retType[i] = fromOutboundMysqlDbColumn(column)
104+
}
105+
return ret, retType
106+
}
107+
108+
func fromOutboundMysqlDbColumn(c C.outbound_mysql_column_t) (string, uint8) {
109+
return C.GoStringN(c.name.ptr, C.int(c.name.len)), uint8(*(*C.uint8_t)(unsafe.Pointer(&c.data_type)))
110+
}
111+
112+
func outboundMysqlStr(x string) C.outbound_mysql_string_t {
113+
return C.outbound_mysql_string_t{ptr: C.CString(x), len: C.size_t(len(x))}
114+
}
115+
116+
func toOutboundMysqlParameterListValue(xv []any) C.outbound_mysql_list_parameter_value_t {
117+
if len(xv) == 0 {
118+
return C.outbound_mysql_list_parameter_value_t{}
119+
}
120+
cxv := make([]C.outbound_mysql_parameter_value_t, len(xv))
121+
for i := 0; i < len(xv); i++ {
122+
cxv[i] = toOutboundMysqlParameterValue(xv[i])
123+
}
124+
return C.outbound_mysql_list_parameter_value_t{ptr: &cxv[0], len: C.size_t(len(cxv))}
125+
}
126+
127+
const (
128+
dbValueBoolean uint8 = iota
129+
dbValueInt8
130+
dbValueInt16
131+
dbValueInt32
132+
dbValueInt64
133+
dbValueUint8
134+
dbValueUint16
135+
dbValueUint32
136+
dbValueUint64
137+
dbValueFloat32
138+
dbValueFloat64
139+
dbValueStr
140+
dbValueBinary
141+
dbValueNull
142+
dbValueUnsupported
143+
)
144+
145+
func fromOutboundMysqlDbValue(x C.outbound_mysql_db_value_t) (any, error) {
146+
switch x.tag {
147+
case dbValueBoolean:
148+
return *(*bool)(unsafe.Pointer(&x.val)), nil
149+
case dbValueInt8:
150+
return int8(*(*C.int8_t)(unsafe.Pointer(&x.val))), nil
151+
case dbValueInt16:
152+
return int16(*(*C.int16_t)(unsafe.Pointer(&x.val))), nil
153+
case dbValueInt32:
154+
return int32(*(*C.int32_t)(unsafe.Pointer(&x.val))), nil
155+
case dbValueInt64:
156+
return int64(*(*C.int64_t)(unsafe.Pointer(&x.val))), nil
157+
case dbValueUint8:
158+
return uint8(*(*C.uint8_t)(unsafe.Pointer(&x.val))), nil
159+
case dbValueUint16:
160+
return uint16(*(*C.uint16_t)(unsafe.Pointer(&x.val))), nil
161+
case dbValueUint32:
162+
return uint32(*(*C.uint32_t)(unsafe.Pointer(&x.val))), nil
163+
case dbValueUint64:
164+
return uint64(*(*C.uint64_t)(unsafe.Pointer(&x.val))), nil
165+
case dbValueFloat32:
166+
return float32(*(*C.float)(unsafe.Pointer(&x.val))), nil
167+
case dbValueFloat64:
168+
return float64(*(*C.double)(unsafe.Pointer(&x.val))), nil
169+
case dbValueBinary:
170+
blob := (*C.outbound_mysql_list_u8_t)(unsafe.Pointer(&x.val))
171+
return C.GoBytes(unsafe.Pointer(blob.ptr), C.int(blob.len)), nil
172+
case dbValueStr:
173+
str := (*C.outbound_mysql_string_t)(unsafe.Pointer(&x.val))
174+
return C.GoStringN(str.ptr, C.int(str.len)), nil
175+
case dbValueNull:
176+
return nil, nil
177+
case dbValueUnsupported:
178+
return nil, errors.New("db return value type unsupported")
179+
}
180+
return nil, errors.New("db return value unknown type")
181+
}
182+
183+
const (
184+
paramValueBoolean uint8 = iota
185+
paramValueInt8
186+
paramValueInt16
187+
paramValueInt32
188+
paramValueInt64
189+
paramValueUint8
190+
paramValueUint16
191+
paramValueUint32
192+
paramValueUint64
193+
paramValueFloat32
194+
paramValueFloat64
195+
paramValueStr
196+
paramValueBinary
197+
paramValueNull
198+
paramValueUnspported
199+
)
200+
201+
func toOutboundMysqlParameterValue(x any) C.outbound_mysql_parameter_value_t {
202+
var ret C.outbound_mysql_parameter_value_t
203+
switch v := x.(type) {
204+
case bool:
205+
*(*bool)(unsafe.Pointer(&ret.val)) = bool(v)
206+
ret.tag = paramValueBoolean
207+
case int8:
208+
*(*C.int8_t)(unsafe.Pointer(&ret.val)) = int8(v)
209+
ret.tag = paramValueInt8
210+
case int16:
211+
*(*C.int16_t)(unsafe.Pointer(&ret.val)) = int16(v)
212+
ret.tag = paramValueInt16
213+
case int32:
214+
*(*C.int32_t)(unsafe.Pointer(&ret.val)) = int32(v)
215+
ret.tag = paramValueInt32
216+
case int64:
217+
*(*C.int64_t)(unsafe.Pointer(&ret.val)) = int64(v)
218+
ret.tag = paramValueInt64
219+
case uint8:
220+
*(*C.uint8_t)(unsafe.Pointer(&ret.val)) = uint8(v)
221+
ret.tag = paramValueUint8
222+
case uint16:
223+
*(*C.uint16_t)(unsafe.Pointer(&ret.val)) = uint16(v)
224+
ret.tag = paramValueUint16
225+
case uint32:
226+
*(*C.uint32_t)(unsafe.Pointer(&ret.val)) = uint32(v)
227+
ret.tag = paramValueUint32
228+
case uint64:
229+
*(*C.uint64_t)(unsafe.Pointer(&ret.val)) = uint64(v)
230+
ret.tag = paramValueUint64
231+
case float32:
232+
*(*C.float)(unsafe.Pointer(&ret.val)) = float32(v)
233+
ret.tag = paramValueFloat32
234+
case float64:
235+
*(*C.double)(unsafe.Pointer(&ret.val)) = float64(v)
236+
ret.tag = paramValueFloat64
237+
case string:
238+
str := outboundMysqlStr(v)
239+
*(*C.outbound_mysql_string_t)(unsafe.Pointer(&ret.val)) = str
240+
ret.tag = paramValueStr
241+
case []byte:
242+
blob := C.outbound_mysql_list_u8_t{ptr: &v[0], len: C.size_t(len(v))}
243+
*(*C.outbound_mysql_list_u8_t)(unsafe.Pointer(&ret.val)) = blob
244+
ret.tag = paramValueBinary
245+
case nil:
246+
ret.tag = paramValueNull
247+
default:
248+
ret.tag = paramValueUnspported
249+
}
250+
return ret
251+
}
252+
253+
func toErr(err *C.outbound_mysql_mysql_error_t) error {
254+
switch err.tag {
255+
case 0:
256+
return nil
257+
case 1:
258+
str := (*C.outbound_mysql_string_t)(unsafe.Pointer(&err.val))
259+
return fmt.Errorf("connection failed: %s", C.GoStringN(str.ptr, C.int(str.len)))
260+
case 2:
261+
str := (*C.outbound_mysql_string_t)(unsafe.Pointer(&err.val))
262+
return fmt.Errorf("bad parameter: %s", C.GoStringN(str.ptr, C.int(str.len)))
263+
case 3:
264+
str := (*C.outbound_mysql_string_t)(unsafe.Pointer(&err.val))
265+
return fmt.Errorf("query failed: %s", C.GoStringN(str.ptr, C.int(str.len)))
266+
case 4:
267+
str := (*C.outbound_mysql_string_t)(unsafe.Pointer(&err.val))
268+
return fmt.Errorf(fmt.Sprintf("value conversion failed: %s", C.GoStringN(str.ptr, C.int(str.len))))
269+
case 5:
270+
str := (*C.outbound_mysql_string_t)(unsafe.Pointer(&err.val))
271+
return fmt.Errorf(fmt.Sprintf("other error: %s", C.GoStringN(str.ptr, C.int(str.len))))
272+
default:
273+
return fmt.Errorf("unrecognized error: %v", err.tag)
274+
}
275+
}
276+
277+
const (
278+
dbDataTypeBoolean uint8 = iota
279+
dbDataTypeInt8
280+
dbDataTypeInt16
281+
dbDataTypeInt32
282+
dbDataTypeInt64
283+
dbDataTypeUint8
284+
dbDataTypeUint16
285+
dbDataTypeUint32
286+
dbDataTypeUint64
287+
dbDataTypeFloating32
288+
dbDataTypeFloating64
289+
dbDataTypeStr
290+
dbDataTypeBinary
291+
dbDataTypeOther
292+
)
293+
294+
func colTypeToReflectType(typ uint8) reflect.Type {
295+
switch typ {
296+
case dbDataTypeBoolean:
297+
return reflect.TypeOf(false)
298+
case dbDataTypeInt8:
299+
return reflect.TypeOf(int8(0))
300+
case dbDataTypeInt16:
301+
return reflect.TypeOf(int16(0))
302+
case dbDataTypeInt32:
303+
return reflect.TypeOf(int32(0))
304+
case dbDataTypeInt64:
305+
return reflect.TypeOf(int64(0))
306+
case dbDataTypeUint8:
307+
return reflect.TypeOf(uint8(0))
308+
case dbDataTypeUint16:
309+
return reflect.TypeOf(uint16(0))
310+
case dbDataTypeUint32:
311+
return reflect.TypeOf(uint32(0))
312+
case dbDataTypeUint64:
313+
return reflect.TypeOf(uint64(0))
314+
case dbDataTypeStr:
315+
return reflect.TypeOf("")
316+
case dbDataTypeBinary:
317+
return reflect.TypeOf(new([]byte))
318+
case dbDataTypeOther:
319+
return reflect.TypeOf(new(any)).Elem()
320+
}
321+
panic("invalid db column type of " + string(typ))
322+
}

0 commit comments

Comments
 (0)