diff --git a/.gitignore b/.gitignore
index de685d7..e4a6004 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,9 +24,10 @@
# RFC traces
*.trc
+*.log
# sapnwrfc.ini example
-gorfc/sapnwrfc.ini
+# gorfc/sapnwrfc.ini
# Packages
GoRFC/pkg/
@@ -44,3 +45,7 @@ Thumbs.db
*.iml
.idea
+### VSCode
+.vscode
+*.code-workspace
+
diff --git a/CHANGES b/CHANGES
new file mode 100644
index 0000000..da71c84
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,17 @@
+CHANGES
+=======
+
+0.0.1 (2016-02-01)
+------------------
+- Initial release
+
+0.1.0 (2020-05-04)
+------------------
+- Darwin support
+- New connection parameters added, type changed to map
+- New connection attributes added, type changed to map
+- Table parameter accepts also array of variables
+- ABAP datatypes conversions fixed:
+ - Bytes: RFCTYPE_BYTE, RFCTYPE_XSTRING
+ - BCD: RFCTYPE_BCD
+- ABAP INT8 type added: RFCTYPE_INT8
\ No newline at end of file
diff --git a/README.md b/README.md
index 9fd9c5a..39441f9 100644
--- a/README.md
+++ b/README.md
@@ -1,31 +1,66 @@
-# SAP NW RFC Connector for [Go](https://golang.org)
+[SAP NetWeawer RFC SDK](https://support.sap.com/en/product/connectors/nwrfcsdk.html) client bindings for GO.
-[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/SAP/gorfc/gorfc)
-[![Go Report Card](https://goreportcard.com/badge/github.com/SAP/gorfc)](https://goreportcard.com/report/github.com/SAP/gorfc)
-[![license](https://img.shields.io/badge/license-Apache-blue.svg)](https://github.com/SAP/gorfc/blob/master/LICENSE)
+[![license](https://img.shields.io/badge/license-Apache-blue.svg)](https://github.com/SAP/gorfc/blob/master/LICENSE)
+[![Go Report Card](https://goreportcard.com/badge/github.com/SAP/gorfc)](https://goreportcard.com/report/github.com/SAP/gorfc)
+[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/SAP/gorfc/gorfc)
-The *gorfc* package provides bindings for *SAP NW RFC Library*, for a comfortable way of calling remote enabled ABAP function modules (RFMs) from [Go](https://golang.org).
+## Features
-The current release is fully functional on Linux and experimental on Windows and macOS see the [Issue #1](https://github.com/SAP/gorfc/issues/1).
+- Stateless and stateful connections (multiple function calls in the same ABAP session / same context)
+- Automatic conversion between GO and ABAP datatypes
-## Table of contents
+## Supported Platforms
-* [Platforms and Prerequisites](#platforms-and-prerequisites)
-* [Install](#install)
- * [GO](#install-and-configure-go)
- * [SAP NW RFC Library](#install-sap-nw-rfc-library)
- * [gorfc](#install-gorfc)
-* [Getting Started](#getting-started)
-* [To Do](#to-do)
-* [References](#references)
+- Windows 10, macOS, Linux
-## Platforms and Prerequisites
+## Prerequisites
-The SAP NW RFC Library is a prerequisite for using the Go RFC connector and must be installed on the same system. It is available on many platforms supported by Go, except Plan 9 and BSD.
+### All platforms
-A prerequisite to download *SAP NW RFC Library* is having a **customer or partner account** on *SAP Service Marketplace* . If you are SAP employee please check SAP OSS note [1037575 - Software download authorizations for SAP employees](http://service.sap.com/sap/support/notes/1037575).
+- GOLANG [requirements](https://golang.org/doc/install#requirements)
-The _SAP NW RFC Library_ is fully backwards compatible, supporting all NetWeaver systems, from today, down to release R/3 4.0. You can and should always use the newest version released on Service Marketplace and connect to older systems as well.
+- SAP NWRFC SDK 7.50 PL3 or later must be [downloaded](https://launchpad.support.sap.com/#/softwarecenter/template/products/_APP=00200682500000001943&_EVENT=DISPHIER&HEADER=Y&FUNCTIONBAR=N&EVENT=TREE&NE=NAVIGATE&ENR=01200314690100002214&V=MAINT) (SAP partner or customer account required) and [locally installed](http://sap.github.io/node-rfc/install.html#sap-nw-rfc-library-installation)
+
+ - Using the latest version is recommended as SAP NWRFC SDK is fully backwards compatible, supporting all NetWeaver systems, from today S4, down to R/3 release 4.6C.
+ - SAP NWRFC SDK [overview](https://support.sap.com/en/product/connectors/nwrfcsdk.html) and [release notes](https://launchpad.support.sap.com/#/softwarecenter/object/0020000000340702020)
+
+- Build from source on macOS and older Linux systems, may require `uchar.h` file, attached to [SAP OSS Note 2573953](https://launchpad.support.sap.com/#/notes/2573953), to be copied to SAP NWRFC SDK include directory: [documentation](http://sap.github.io/PyRFC/install.html#macos)
+
+### Windows
+
+- [Visual C++ Redistributable Package for Visual Studio 2013](https://www.microsoft.com/en-US/download/details.aspx?id=40784) is required for runtime, see [SAP Note 2573790 - Installation, Support and Availability of the SAP NetWeaver RFC Library 7.50](https://launchpad.support.sap.com/#/notes/2573790)
+
+- Build toolchain requires GCC and [MinGW](http://mingw-w64.org). Using TDM_GCC may lead to issues: https://stackoverflow.com/questions/35004744/golang-oci8-error-adding-symbols-file-in-wrong-format
+
+### macOS
+
+- Build toolchain requires GCC and Xcode Command Line Tools:
+
+```shell
+$ xcode-select --install
+```
+
+- The macOS firewall stealth mode must be disabled ([Can't ping a machine - why?](https://discussions.apple.com/thread/2554739)):
+
+```shell
+sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setstealthmode off
+```
+
+- Remote paths must be set in SAP NWRFC SDK for macOS: [documentation](http://sap.github.io/PyRFC/install.html#macos)
+
+- Build from source requires `uchar.h` file, attached to [SAP OSS Note 2573953](https://launchpad.support.sap.com/#/notes/2573953), to be copied to SAP NWRFC SDK include directory: [documentation](http://sap.github.io/PyRFC/install.html#macos)
+
+- Optionally: [valgrind](https://stackoverflow.com/questions/58360093/how-to-install-valgrind-on-macos-catalina-10-15-with-homebrew)
+
+## SPJ articles
+
+Highly recommended reading about RFC communication and SAP NW RFC Library, published in the SAP Professional Journal (SPJ)
+
+- [Part I RFC Client Programming](https://wiki.scn.sap.com/wiki/x/zz27Gg)
+
+- [Part II RFC Server Programming](https://wiki.scn.sap.com/wiki/x/9z27Gg)
+
+- [Part III Advanced Topics](https://wiki.scn.sap.com/wiki/x/FD67Gg)
## Install
@@ -119,7 +154,7 @@ func abapSystem() gorfc.ConnectionParameter {
Ashost: "11.111.11.111",
Sysnr: "00",
Saprouter: "/H/222.22.222.22/S/2222/W/xxxxx/H/222.22.222.222/H/",
- }
+ }
}
func main() {
@@ -141,7 +176,7 @@ func main() {
"RFCDATA1": "HELLÖ SÄP",
"RFCDATA2": "DATA222",
},
- }
+ }
r, _ := c.Call("STFC_STRUCTURE", params)
assert.NotNil(t, r["ECHOSTRUCT"])
@@ -154,7 +189,7 @@ func main() {
assert.Equal(t, importStruct["RFCINT1"], echoStruct["RFCINT1"])
assert.Equal(t, importStruct["RFCINT2"], echoStruct["RFCINT2"])
assert.Equal(t, importStruct["RFCINT4"], echoStruct["RFCINT4"])
- // assert.Equal(t, importStruct["RFCHEX3"], echoStruct["RFCHEX3"])
+ // assert.Equal(t, importStruct["RFCHEX3"], echoStruct["RFCHEX3"])
assert.Equal(t, importStruct["RFCTIME"].(time.Time).Format("150405"), echoStruct["RFCTIME"].(time.Time).Format("15.
assert.Equal(t, importStruct["RFCDATE"].(time.Time).Format("20060102"), e/Users/d037732/Downloads/gorfc/README.mdchoStruct["RFCDATE"].(time.Time).Format(".
assert.Equal(t, importStruct["RFCDATA1"], echoStruct["RFCDATA1"])
@@ -166,15 +201,10 @@ func main() {
c.Close()
```
-## To Do
-
-* Improve the documentation
-* Fix Windows compiler flags
-
## References
-* [GO Installation](https://golang.org/doc/install)
-* [GO Configuration](https://golang.org/doc/code.html)
-* [GO Environment Variables](https://golang.org/cmd/go/#hdr-Environment_variables)
-* [GO on Windows Example](http://www.wadewegner.com/2014/12/easy-go-programming-setup-for-windows/)
-* [Another GO on Windows Example](https://github.com/abourget/getting-started-with-golang/blob/master/Getting_Started_for_Windows.md)
+- [GO Installation](https://golang.org/doc/install)
+- [GO Configuration](https://golang.org/doc/code.html)
+- [GO Environment Variables](https://golang.org/cmd/go/#hdr-Environment_variables)
+- [GO on Windows Example](http://www.wadewegner.com/2014/12/easy-go-programming-setup-for-windows/)
+- [Another GO on Windows Example](https://github.com/abourget/getting-started-with-golang/blob/master/Getting_Started_for_Windows.md)
diff --git a/example/hello_gorfc.go b/example/hello_gorfc.go
index f55967f..2118856 100644
--- a/example/hello_gorfc.go
+++ b/example/hello_gorfc.go
@@ -3,29 +3,32 @@ package main
import (
"fmt"
"reflect"
- "testing"
"time"
"github.com/sap/gorfc/gorfc"
- "github.com/stretchr/testify/assert"
)
-func abapSystem() gorfc.ConnectionParameter {
- return gorfc.ConnectionParameter{
- Dest: "I64",
- Client: "800",
- User: "demo",
- Passwd: "welcome",
- Lang: "EN",
- Ashost: "10.117.24.158",
- Sysnr: "00",
- Saprouter: "/H/203.13.155.17/S/3299/W/xjkb3d/H/172.19.137.194/H/",
+func abapSystem() gorfc.ConnectionParameters {
+ return gorfc.ConnectionParameters{
+ "user": "demo",
+ "passwd": "welcome",
+ "ashost": "10.68.110.51",
+ "sysnr": "00",
+ "client": "620",
+ "lang": "EN",
}
}
func main() {
- c, _ := gorfc.ConnectionFromParams(abapSystem())
- var t *testing.T
+ c, err := gorfc.ConnectionFromParams(abapSystem())
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ fmt.Println(c.Alive())
+
+ attrs, _ := c.GetConnectionAttributes()
+ fmt.Println("Connection attributes", attrs)
params := map[string]interface{}{
"IMPORTSTRUCT": map[string]interface{}{
@@ -39,30 +42,22 @@ func main() {
"RFCHEX3": []byte{255, 254, 253},
"RFCTIME": time.Now(),
"RFCDATE": time.Now(),
- "RFCDATA1": "Hellö SÄP",
+ "RFCDATA1": "HELLÖ SÄP",
"RFCDATA2": "DATA222",
},
}
r, _ := c.Call("STFC_STRUCTURE", params)
- assert.NotNil(t, r["ECHOSTRUCT"])
+ fmt.Println(r["ECHOSTRUCT"])
importStruct := params["IMPORTSTRUCT"].(map[string]interface{})
echoStruct := r["ECHOSTRUCT"].(map[string]interface{})
- assert.Equal(t, importStruct["RFCFLOAT"], echoStruct["RFCFLOAT"])
- assert.Equal(t, importStruct["RFCCHAR1"], echoStruct["RFCCHAR1"])
- assert.Equal(t, importStruct["RFCCHAR2"], echoStruct["RFCCHAR2"])
- assert.Equal(t, importStruct["RFCCHAR4"], echoStruct["RFCCHAR4"])
- assert.Equal(t, importStruct["RFCINT1"], echoStruct["RFCINT1"])
- assert.Equal(t, importStruct["RFCINT2"], echoStruct["RFCINT2"])
- assert.Equal(t, importStruct["RFCINT4"], echoStruct["RFCINT4"])
- // assert.Equal(t, importStruct["RFCHEX3"], echoStruct["RFCHEX3"])
- assert.Equal(t, importStruct["RFCTIME"].(time.Time).Format("150405"), echoStruct["RFCTIME"].(time.Time).Format("150405"))
- assert.Equal(t, importStruct["RFCDATE"].(time.Time).Format("20060102"), echoStruct["RFCDATE"].(time.Time).Format("20060102"))
- assert.Equal(t, importStruct["RFCDATA1"], echoStruct["RFCDATA1"])
- assert.Equal(t, importStruct["RFCDATA2"], echoStruct["RFCDATA2"])
-
- fmt.Println(reflect.TypeOf(importStruct["RFCDATE"]))
- fmt.Println(reflect.TypeOf(importStruct["RFCTIME"]))
+ fmt.Println(echoStruct)
+ // empty time
+ fmt.Println(importStruct["RFCDATE"], reflect.TypeOf(importStruct["RFCDATE"]))
+ fmt.Println(echoStruct["RFCDATE"], reflect.TypeOf(echoStruct["RFCDATE"]))
+ // empty date
+ fmt.Println(importStruct["RFCTIME"], reflect.TypeOf(importStruct["RFCTIME"]))
+ fmt.Println(echoStruct["RFCTIME"], reflect.TypeOf(echoStruct["RFCTIME"]))
c.Close()
}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..24151f3
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module github.com/sap/gorfc
+
+go 1.14
+
+require github.com/stretchr/testify v1.5.1
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..c0565d7
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,12 @@
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/gorfc/.editorconfig b/gorfc/.editorconfig
new file mode 100644
index 0000000..7b8d0aa
--- /dev/null
+++ b/gorfc/.editorconfig
@@ -0,0 +1,8 @@
+root = true
+
+[*]
+indent_style = tab
+indent_size = 4
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = false
\ No newline at end of file
diff --git a/gorfc/gorfc.go b/gorfc/gorfc.go
index edb5101..69f1eb1 100644
--- a/gorfc/gorfc.go
+++ b/gorfc/gorfc.go
@@ -1,60 +1,61 @@
-// +build linux,cgo amd64,cgo
+// +build linux,cgo amd64,cgo darwin,cgo
-// gorfc wraps the SAP NetWeaver RFC library written in C.
-// Its provides methods for maintaining a connection to an ABAP backend and calling remote enabled functions from Go.
-// The functions of the library take and return Go data types.
+// Package gorfc provides SAP NetWeawer RFC SDK client bindings for GO
package gorfc
/*
+#cgo windows CFLAGS: -DSAPonNT -D_CRT_NON_CONFORMING_SWPRINTFS -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE -D_CONSOLE
+#cgo windows CFLAGS: -D_AFXDLL -DWIN32 -D_WIN32_WINNT=0x0502 -DWIN64 -D_AMD64_ -DNDEBUG -DSAPwithUNICODE -DUNICODE -D_UNICODE
+#cgo windows CFLAGS: -DSAPwithTHREADS -D_ATL_ALLOW_CHAR_UNSIGNED -DSAP_PLATFORM_MAKENAME=ntintel
+#cgo windows CFLAGS: -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D__NO_MATH_INLINES
+#cgo windows CFLAGS: -O2 -g -pipe -m64 -mwindows -pthread -march=x86-64
+#cgo windows CFLAGS: -fno-strict-aliasing -fno-omit-frame-pointer -fexceptions -funsigned-char
+#cgo windows CFLAGS: -Wall -Wno-uninitialized -Wno-long-long
+#cgo windows CFLAGS: -Wcast-align -Wunused-variable
+// todo -EHs ?
+// todo -Gy ? -ffunction-sections -fdata-sections
+// todo MD ? -lpthread -lm
+// todo -nologo -W3 -Z7 -GL -O2 -Oy- /we4552 /we4700 /we4789
+
+#cgo windows CFLAGS: -IC:/Tools/nwrfcsdk/include/
+#cgo windows LDFLAGS: -LC:/Tools/nwrfcsdk/lib/ -lsapnwrfc -llibsapucum
+
+#cgo windows LDFLAGS: -O2 -g -pthread -pie -fPIE
+#cgo windows LDFLAGS: -OPT:REF -LTCG
+// todo -NXCOMPAT -STACK:0x2000000 -SWAPRUN:NET -DEBUG -DEBUGTYPE:CV,FIXUP -MACHINE:amd64 -nologo
+
#cgo linux CFLAGS: -DNDEBUG -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DSAPonUNIX
#cgo linux CFLAGS: -DSAPwithUNICODE -D__NO_MATH_INLINES -DSAPwithTHREADS -DSAPonLIN
-#cgo linux CFLAGS: -O2 -minline-all-stringops -g -fno-strict-aliasing -fno-omit-frame-pointer
+#cgo linux CFLAGS: -O2 -g -fno-strict-aliasing -fno-omit-frame-pointer
#cgo linux CFLAGS: -m64 -fexceptions -funsigned-char -Wall -Wno-uninitialized -Wno-long-long
#cgo linux CFLAGS: -Wcast-align -pthread -pipe -Wno-unused-variable
#cgo linux CFLAGS: -I/usr/local/sap/nwrfcsdk/include
#cgo linux LDFLAGS: -L/usr/local/sap/nwrfcsdk/lib -lsapnwrfc -lsapucum
-#cgo linux LDFLAGS: -O2 -minline-all-stringops -g -fno-strict-aliasing -fno-omit-frame-pointer
-#cgo linux LDFLAGS: -m64 -fexceptions -funsigned-char -Wall -Wno-uninitialized -Wno-long-long
-#cgo linux LDFLAGS: -Wcast-align -pthread
-
-#cgo windows CFLAGS: -DNDEBUG -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DSAPonWIN
-#cgo windows CFLAGS: -DSAPwithUNICODE -D__NO_MATH_INLINES -DSAPwithTHREADS
-#cgo windows CFLAGS: -O2 -minline-all-stringops -g -fno-strict-aliasing -fno-omit-frame-pointer
-#cgo windows CFLAGS: -m64 -fexceptions -funsigned-char -Wall -Wno-uninitialized -Wno-long-long
-#cgo windows CFLAGS: -Wcast-align -pipe -Wunused-variable
+#cgo linux LDFLAGS: -O2 -g -pthread
-#cgo windows CFLAGS: -IC:/nwrfcsdk/include/
-#cgo windows LDFLAGS: -LC:/nwrfcsdk/lib/ -lsapnwrfc -llibsapucum
+#cgo darwin CFLAGS: -Wall -O2 -Wno-uninitialized -Wcast-align
+#cgo darwin CFLAGS: -DSAP_UC_is_wchar -DSAPwithUNICODE -D__NO_MATH_INLINES -DSAPwithTHREADS -DSAPonLIN
+#cgo darwin CFLAGS: -fexceptions -funsigned-char -fno-strict-aliasing -fPIC -pthread -std=c17 -mmacosx-version-min=10.15
+#cgo darwin CFLAGS: -fno-omit-frame-pointer
-#cgo windows LDFLAGS: -O2 -minline-all-stringops -g -fno-strict-aliasing -fno-omit-frame-pointer
-#cgo windows LDFLAGS: -m64 -fexceptions -funsigned-char -Wall -Wno-uninitialized -Wno-long-long
-#cgo windows LDFLAGS: -Wcast-align
+#cgo darwin CFLAGS: -I/usr/local/sap/nwrfcsdk/include
+#cgo darwin LDFLAGS: -L/usr/local/sap/nwrfcsdk/lib -lsapnwrfc -lsapucum
+#cgo darwin LDFLAGS: -Wl,-rpath,/usr/local/sap/nwrfcsdk/lib
-#cgo darwin LDFLAGS: -lsapnwrfc -lsapucum
-#cgo darwin LDFLAGS: -O2 -minline-all-stringops -g -fno-strict-aliasing -fno-omit-frame-pointer
-#cgo darwin LDFLAGS: -m64 -fexceptions -funsigned-char -Wall -Wno-uninitialized -Wno-long-long
-#cgo darwin LDFLAGS: -Wcast-align -pthread
-
-#cgo darwin CFLAGS: -DNDEBUG -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DSAPonUNIX
-#cgo darwin CFLAGS: -DSAPwithUNICODE -D__NO_MATH_INLINES -DSAPwithTHREADS -DSAPonDARW
-#cgo darwin CFLAGS: -O2 -minline-all-stringops -g -fno-strict-aliasing -fno-omit-frame-pointer
-#cgo darwin CFLAGS: -m64 -fexceptions -Wall -Wno-uninitialized -Wno-long-long
-#cgo darwin CFLAGS: -Wcast-align -pthread -pipe -Wno-unused-variable
+#cgo darwin LDFLAGS: -O2 -g -pthread
+#cgo darwin LDFLAGS: -stdlib=libc++
+#cgo darwin LDFLAGS: -mmacosx-version-min=10.15
#include
static SAP_UC* GoMallocU(unsigned size) {
- return mallocU(size);
+ return (SAP_UC*)(mallocU(size));
}
-//static SAP_UC* GoMemsetU(SAP_UTF16 * s, int c, size_t n) {
-// return memsetU(s, c, n);
-//}
-
-static int GoStrlenU(SAP_UTF16 *str) {
+static unsigned GoStrlenU(SAP_UTF16 *str) {
return strlenU(str);
}
@@ -70,10 +71,29 @@ import (
"unsafe"
)
+/*
+static uint GoStrlenU(SAP_UTF16 *str) {
+ return strlenU(str);
+}
+
+static uint GoStrlenU16(SAP_UTF16 *str) {
+ return strlenU16(str);
+}
+
+static SAP_UC* GoMemsetU(SAP_UTF16 * s, int c, size_t n) {
+ return (SAP_UC *)memsetU(s, c, n);
+}
+
+static uint GoStrlenU(SAP_UTF16 *str) {
+ return strlenU(str);
+}
+*/
+
//################################################################################
//# RFC ERROR #
//################################################################################
+// RfcError is returned by SAP NWRFC SDK
type RfcError struct {
Description string
ErrorInfo rfcSDKError
@@ -96,28 +116,19 @@ func rfcError(errorInfo C.RFC_ERROR_INFO, format string, a ...interface{}) *RfcE
func fillString(gostr string) (sapuc *C.SAP_UC, err error) {
var rc C.RFC_RC
var errorInfo C.RFC_ERROR_INFO
- var result_len C.uint
- sapuc_size := C.uint(len(gostr) + 1)
- sapuc = C.GoMallocU(sapuc_size)
- cStr := (*C.uchar)(unsafe.Pointer(C.CString(gostr)))
- defer C.free(unsafe.Pointer(cStr))
+ var resultLen C.uint
+ sapucSize := C.uint(len(gostr) + 1)
+ sapuc = C.GoMallocU(sapucSize)
*sapuc = 0
-
- rc = C.RfcUTF8ToSAPUC((*C.RFC_BYTE)(cStr), C.uint(len(gostr)), sapuc, &sapuc_size, &result_len, &errorInfo)
+ cStr := C.CString(gostr)
+ defer C.free(unsafe.Pointer(cStr))
+ rc = C.RfcUTF8ToSAPUC((*C.RFC_BYTE)(cStr), C.uint(len(gostr)), sapuc, &sapucSize, &resultLen, &errorInfo)
if rc != C.RFC_OK {
err = rfcError(errorInfo, "Could not fill the string \"%v\"", gostr)
}
return
}
-// fillByte allocates memory for the return value that has to be freed
-func fillBytes(gobytes []byte) (bytes *C.SAP_RAW) {
- size := C.size_t(len(gobytes))
- bytes = (*C.SAP_RAW)(C.malloc(size))
- C.memcpy(unsafe.Pointer(bytes), unsafe.Pointer(&gobytes[0]), size)
- return
-}
-
func fillFunctionParameter(funcDesc C.RFC_FUNCTION_DESC_HANDLE, container C.RFC_FUNCTION_HANDLE, goName string, value interface{}) (err error) {
var rc C.RFC_RC
var errorInfo C.RFC_ERROR_INFO
@@ -161,29 +172,40 @@ func fillVariable(cType C.RFCTYPE, container C.RFC_FUNCTION_HANDLE, cName *C.SAP
return rfcError(errorInfo, "Could not get table")
}
err = fillTable(typeDesc, table, value)
- case C.RFCTYPE_CHAR:
- cValue, err = fillString(reflect.ValueOf(value).String())
- rc = C.RfcSetChars(container, cName, (*C.RFC_CHAR)(cValue), C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue))), &errorInfo)
case C.RFCTYPE_BYTE:
- bValue = fillBytes(reflect.ValueOf(value).Bytes())
- rc = C.RfcSetBytes(container, cName, bValue, C.uint(len(reflect.ValueOf(value).Bytes())), &errorInfo)
+ bValue = (*C.SAP_RAW)(C.CBytes(reflect.ValueOf(value).Bytes()))
+ cLen := C.uint(len(reflect.ValueOf(value).Bytes()))
+ rc = C.RfcSetBytes(container, cName, bValue, cLen, &errorInfo)
case C.RFCTYPE_XSTRING:
- bValue = fillBytes(reflect.ValueOf(value).Bytes())
- rc = C.RfcSetXString(container, cName, bValue, C.uint(len(reflect.ValueOf(value).Bytes())), &errorInfo)
+ bValue = (*C.SAP_RAW)(C.CBytes(reflect.ValueOf(value).Bytes()))
+ cLen := C.uint(len(reflect.ValueOf(value).Bytes()))
+ rc = C.RfcSetXString(container, cName, bValue, cLen, &errorInfo)
+ case C.RFCTYPE_CHAR:
+ cValue, err = fillString(reflect.ValueOf(value).String())
+ //cLen := (C.uint)(len(reflect.ValueOf(value).String()))
+ cLen := C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue)))
+ rc = C.RfcSetChars(container, cName, (*C.RFC_CHAR)(cValue), cLen, &errorInfo)
case C.RFCTYPE_STRING:
cValue, err = fillString(reflect.ValueOf(value).String())
- rc = C.RfcSetString(container, cName, cValue, C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue))), &errorInfo)
+ //cLen := (C.uint)(len(reflect.ValueOf(value).String()))
+ cLen := C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue)))
+ rc = C.RfcSetString(container, cName, cValue, cLen, &errorInfo)
case C.RFCTYPE_NUM:
cValue, err = fillString(reflect.ValueOf(value).String())
- rc = C.RfcSetNum(container, cName, (*C.RFC_NUM)(cValue), C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue))), &errorInfo)
+ //cLen := (C.uint)(len(reflect.ValueOf(value).String()))
+ cLen := C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue)))
+ rc = C.RfcSetNum(container, cName, (*C.RFC_NUM)(cValue), cLen, &errorInfo)
case C.RFCTYPE_BCD:
- // support for float missing
cValue, err = fillString(reflect.ValueOf(value).String())
- rc = C.RfcSetString(container, cName, cValue, C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue))), &errorInfo)
+ //cLen := (C.uint)(len(reflect.ValueOf(value).String()))
+ cLen := C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue)))
+ rc = C.RfcSetString(container, cName, cValue, cLen, &errorInfo)
case C.RFCTYPE_FLOAT:
rc = C.RfcSetFloat(container, cName, C.RFC_FLOAT(reflect.ValueOf(value).Float()), &errorInfo)
case C.RFCTYPE_INT, C.RFCTYPE_INT1, C.RFCTYPE_INT2:
rc = C.RfcSetInt(container, cName, C.RFC_INT(reflect.ValueOf(value).Int()), &errorInfo)
+ case C.RFCTYPE_INT8:
+ rc = C.RfcSetInt8(container, cName, C.RFC_INT8(reflect.ValueOf(value).Int()), &errorInfo)
case C.RFCTYPE_DATE:
cValue, err = fillString(value.(time.Time).Format("20060102"))
rc = C.RfcSetDate(container, cName, (*C.RFC_CHAR)(cValue), &errorInfo)
@@ -208,13 +230,13 @@ func fillStructure(typeDesc C.RFC_TYPE_DESC_HANDLE, container C.RFC_STRUCTURE_HA
s := reflect.ValueOf(value)
if s.Type().Kind() == reflect.Map {
+ // Table passed as array of maps
keys := s.MapKeys()
if len(keys) > 0 {
if keys[0].Kind() == reflect.String {
for _, nameValue := range keys {
fieldName := nameValue.String()
fieldValue := s.MapIndex(nameValue).Interface()
-
err = fillStructureField(typeDesc, container, fieldName, fieldValue)
}
} else {
@@ -222,14 +244,15 @@ func fillStructure(typeDesc C.RFC_TYPE_DESC_HANDLE, container C.RFC_STRUCTURE_HA
}
}
} else if s.Type().Kind() == reflect.Struct {
+ // Table passed as array of structures
for i := 0; i < s.NumField(); i++ {
fieldName := s.Type().Field(i).Name
fieldValue := s.Field(i).Interface()
-
err = fillStructureField(typeDesc, container, fieldName, fieldValue)
}
} else {
- return rfcError(errorInfo, "Structures can only be passed as types map[string]interface{} or go-structures")
+ // Table passed as array of variables
+ err = fillStructureField(typeDesc, container, "", s.Interface())
}
return
}
@@ -258,7 +281,6 @@ func fillTable(typeDesc C.RFC_TYPE_DESC_HANDLE, container C.RFC_TABLE_HANDLE, li
if lineHandle == nil {
return rfcError(errorInfo, "Could not append new row to table")
}
-
err = fillStructure(typeDesc, lineHandle, line.Interface())
}
return
@@ -269,35 +291,32 @@ func fillTable(typeDesc C.RFC_TYPE_DESC_HANDLE, container C.RFC_TABLE_HANDLE, li
//################################################################################
//# Wrapper functions take C values and return Go values
-func wrapString(uc *C.SAP_UC, strip bool) (result string, err error) {
- return nWrapString(uc, -1, strip)
+func wrapString(sapuc *C.SAP_UC, strip bool) (string, error) {
+ return nWrapString(sapuc, C.uint(C.strlenU((*C.ushort)(sapuc))), strip)
}
-func nWrapString(uc *C.SAP_UC, length C.int, strip bool) (result string, err error) {
- var rc C.RFC_RC
+func nWrapString(sapuc *C.SAP_UC, sapucLength C.uint, strip bool) (string, error) {
var errorInfo C.RFC_ERROR_INFO
- if length == -1 {
- length = C.int(C.GoStrlenU((*C.SAP_UTF16)(uc)))
- }
- if length == 0 {
- return "", err
+ var rc C.RFC_RC
+ var resultLength C.uint
+
+ if sapucLength == 0 {
+ return "", nil
}
- utf8Size := C.uint(length*3) + 1
- utf8str := (*C.char)(unsafe.Pointer(C.malloc((C.size_t)(utf8Size))))
- defer C.free(unsafe.Pointer(utf8str)) // _todo: Memory access error on Windows only, when trying to free RFCCHAR1 of RFCTABLE in function call test
- *utf8str = 0
- resultLen := C.uint(0)
- rc = C.RfcSAPUCToUTF8(uc, (C.uint)(length), (*C.RFC_BYTE)(unsafe.Pointer(utf8str)), &utf8Size, &resultLen, &errorInfo)
+ utf8size := C.uint(3*sapucLength + 1)
+ utf8Str := (*C.RFC_BYTE)(C.malloc((C.size_t)(utf8size)))
+ defer C.free(unsafe.Pointer(utf8Str))
+
+ rc = C.RfcSAPUCToUTF8(sapuc, C.uint(sapucLength), utf8Str, &utf8size, &resultLength, &errorInfo)
if rc != C.RFC_OK {
- return result, rfcError(errorInfo, "Failed wrapping a C string")
+ return "", fmt.Errorf("wrapString sapucLength %v utf8size %v", sapucLength, utf8size)
}
- result = C.GoString(utf8str)
+ result := C.GoString((*C.char)(unsafe.Pointer(utf8Str)))
if strip {
result = strings.Trim(result, "\x00 ")
- return
}
- return
+ return result, nil
}
type rfcSDKError struct {
@@ -332,40 +351,12 @@ func (err rfcSDKError) String() string {
return fmt.Sprintf("rfcSDKError[%v, %v, %v, %v, %v, %v, %v, %v, %v, %v]", err.Message, err.Code, err.Key, err.AbapMsgClass, err.AbapMsgType, err.AbapMsgNumber, err.AbapMsgV1, err.AbapMsgV2, err.AbapMsgV3, err.AbapMsgV4)
}
-type ConnectionAttributes struct {
- Dest string // RFC destination
- Host string // Own host name
- PartnerHost string // Partner host name
- SysNumber string // R/3 system number
- SysId string // R/3 system ID
- Client string // Client ("Mandant")
- User string // User
- Language string // Language
- Trace string // Trace level (0-3)
- IsoLanguage string // 2-byte ISO-Language
- Codepage string // Own code page
- PartnerCodepage string // Partner code page
- RfcRole string // C/S: RFC Client / RFC Server
- Type string // 2/3/E/R: R/2,R/3,Ext,Reg.Ext
- PartnerType string // 2/3/E/R: R/2,R/3,Ext,Reg.Ext
- Rel string // My system release
- PartnerRel string // Partner system release
- KernelRel string // Partner kernel release
- CpicConvId string // CPI-C Conversation ID
- ProgName string // Name of the calling APAB program (report, module pool)
- PartnerBytesPerChar string // Number of bytes per character in the backend's current codepage. Note this is different from the semantics of the PCS parameter.
- PartnerSystemCodepage string // Partner system code page
- Reserved string // Reserved for later use
-}
-
-func (connAttr ConnectionAttributes) String() string {
- return fmt.Sprintf("ConnectionAttributes:\n dest= %v\n host= %v\n partnerHost= %v\n sysNumber= %v\n sysID= %v\n client= %v\n user= %v\n lang= %v\n trace= %v\n isoLang= %v\n codePage= %v\n partnerCodepage= %v\n RFCRole= %v\n partnerType= %v\n rel= %v\n partnerRel= %v\n kernalRel= %v\n CPI-CConvId= %v\n progName= %v\n partnerBytesPerChar= %v\n partnerSystemCodepage= %v\n reserved= %v",
- connAttr.Dest, connAttr.Host, connAttr.PartnerHost, connAttr.SysNumber, connAttr.SysId, connAttr.Client, connAttr.User, connAttr.Language,
- connAttr.Trace, connAttr.IsoLanguage, connAttr.Codepage, connAttr.PartnerCodepage, connAttr.RfcRole, connAttr.PartnerType, connAttr.Rel,
- connAttr.PartnerRel, connAttr.KernelRel, connAttr.CpicConvId, connAttr.ProgName, connAttr.PartnerBytesPerChar, connAttr.PartnerSystemCodepage, connAttr.Reserved)
-}
+// ConnectionAttributes returned by getConnectionInfo() method
+type ConnectionAttributes map[string]string
func wrapConnectionAttributes(attributes C.RFC_ATTRIBUTES, strip bool) (connAttr ConnectionAttributes, err error) {
+ connAttr = make(map[string]string)
+
dest, err := nWrapString(&attributes.dest[0], 64, strip)
host, err := nWrapString(&attributes.host[0], 100, strip)
partnerHost, err := nWrapString(&attributes.partnerHost[0], 100, strip)
@@ -388,10 +379,35 @@ func wrapConnectionAttributes(attributes C.RFC_ATTRIBUTES, strip bool) (connAttr
progName, err := nWrapString(&attributes.progName[0], 128, strip)
partnerBytesPerChar, err := nWrapString(&attributes.partnerBytesPerChar[0], 1, strip)
partnerSystemCodepage, err := nWrapString(&attributes.partnerSystemCodepage[0], 4, strip)
- reserved, err := nWrapString(&attributes.reserved[0], 78, strip)
+ partnerIP, err := nWrapString(&attributes.partnerIP[0], 15, strip)
+ partnerIPv6, err := nWrapString(&attributes.partnerIPv6[0], 45, strip)
+ //reserved, err := nWrapString(&attributes.reserved[0], 17, strip)
+
+ connAttr["dest"] = dest
+ connAttr["host"] = host
+ connAttr["partnerHost"] = partnerHost
+ connAttr["sysNumber"] = sysNumber
+ connAttr["sysId"] = sysId
+ connAttr["client"] = client
+ connAttr["user"] = user
+ connAttr["language"] = language
+ connAttr["trace"] = trace
+ connAttr["isoLanguage"] = isoLanguage
+ connAttr["codepage"] = codepage
+ connAttr["partnerCodepage"] = partnerCodepage
+ connAttr["rfcRole"] = rfcRole
+ connAttr["type"] = _type
+ connAttr["partnerType"] = partnerType
+ connAttr["rel"] = rel
+ connAttr["partnerRel"] = partnerRel
+ connAttr["kernelRel"] = kernelRel
+ connAttr["cpicConvId"] = cpicConvId
+ connAttr["progName"] = progName
+ connAttr["partnerBytesPerChar"] = partnerBytesPerChar
+ connAttr["partnerSystemCodepage"] = partnerSystemCodepage
+ connAttr["partnerIP"] = partnerIP
+ connAttr["partnerIPv6"] = partnerIPv6
- connAttr = ConnectionAttributes{dest, host, partnerHost, sysNumber, sysId, client, user, language, trace, isoLanguage, codepage, partnerCodepage, rfcRole,
- _type, partnerType, rel, partnerRel, kernelRel, cpicConvId, progName, partnerBytesPerChar, partnerSystemCodepage, reserved}
return
}
@@ -601,6 +617,7 @@ func wrapVariable(cType C.RFCTYPE, container C.RFC_FUNCTION_HANDLE, cName *C.SAP
var intValue C.RFC_INT
var int1Value C.RFC_INT1
var int2Value C.RFC_INT2
+ var int8Value C.RFC_INT8
var dateValue *C.RFC_CHAR
var timeValue *C.RFC_CHAR
@@ -627,7 +644,7 @@ func wrapVariable(cType C.RFCTYPE, container C.RFC_FUNCTION_HANDLE, cName *C.SAP
if rc != C.RFC_OK {
return result, rfcError(errorInfo, "Failed getting chars")
}
- return nWrapString((*C.SAP_UC)(charValue), C.int(cLen), strip)
+ return nWrapString((*C.SAP_UC)(charValue), cLen, strip)
case C.RFCTYPE_STRING:
rc = C.RfcGetStringLength(container, cName, &strLen, &errorInfo)
if rc != C.RFC_OK {
@@ -650,31 +667,28 @@ func wrapVariable(cType C.RFCTYPE, container C.RFC_FUNCTION_HANDLE, cName *C.SAP
if rc != C.RFC_OK {
return result, rfcError(errorInfo, "Failed getting num")
}
- return nWrapString((*C.SAP_UC)(numValue), C.int(cLen), strip)
+ return nWrapString((*C.SAP_UC)(numValue), cLen, strip)
case C.RFCTYPE_BYTE:
byteValue = (*C.SAP_RAW)(C.malloc(C.size_t(cLen)))
defer C.free(unsafe.Pointer(byteValue))
-
rc = C.RfcGetBytes(container, cName, byteValue, cLen, &errorInfo)
if rc != C.RFC_OK {
return result, rfcError(errorInfo, "Failed getting bytes")
}
- return (*[1 << 30]byte)(unsafe.Pointer(byteValue))[:cLen:cLen], err
+ return C.GoBytes(unsafe.Pointer(byteValue), C.int(cLen)), err
case C.RFCTYPE_XSTRING:
rc = C.RfcGetStringLength(container, cName, &strLen, &errorInfo)
if rc != C.RFC_OK {
return result, rfcError(errorInfo, "Failed getting xstring length")
}
- byteValue = (*C.SAP_RAW)(C.malloc(C.size_t(strLen + 1)))
+ byteValue = (*C.SAP_RAW)(C.malloc(C.size_t(strLen)))
defer C.free(unsafe.Pointer(byteValue))
- *byteValue = 0
-
rc = C.RfcGetXString(container, cName, byteValue, strLen, &resultLen, &errorInfo)
if rc != C.RFC_OK {
return result, rfcError(errorInfo, "Failed getting xstring")
}
- return (*[1 << 30]byte)(unsafe.Pointer(byteValue))[:resultLen:resultLen], err
+ return C.GoBytes(unsafe.Pointer(byteValue), C.int(cLen)), err
case C.RFCTYPE_BCD:
// An upper bound for the length of the _string representation_
// of the BCD is given by (2*cLen)-1 (each digit is encoded in 4bit,
@@ -684,20 +698,18 @@ func wrapVariable(cType C.RFCTYPE, container C.RFC_FUNCTION_HANDLE, cName *C.SAP
strLen = 2*cLen + 1
stringValue = C.GoMallocU(strLen + 1)
defer C.free(unsafe.Pointer(stringValue))
-
rc = C.RfcGetString(container, cName, stringValue, strLen+1, &resultLen, &errorInfo)
- /*if rc == 23: # Buffer too small, use returned requried result length
- print("Warning: Buffer for BCD (cLen={}, buffer={}) too small: "
- "trying with {}".format(cLen, strLen, resultLen))
- free(stringValue)
- strLen = resultLen
- stringValue = mallocU(strLen+1)
- rc = RfcGetString(container, cName, stringValue, strLen+1, &resultLen, &errorInfo)*/
- if rc != C.RFC_OK {
- return result, rfcError(errorInfo, "Failed getting BCD")
+ if rc == 23 {
+ //Buffer too small, use returned requried result length
+ C.free(unsafe.Pointer(stringValue))
+ strLen = resultLen
+ stringValue = C.GoMallocU(strLen + 1)
+ rc = C.RfcGetString(container, cName, stringValue, strLen+1, &resultLen, &errorInfo)
+ if rc != C.RFC_OK {
+ return result, rfcError(errorInfo, "Failed getting BCD")
+ }
}
return wrapString(stringValue, strip)
- //return Decimal(wrapString(stringValue))
case C.RFCTYPE_FLOAT:
rc = C.RfcGetFloat(container, cName, &floatValue, &errorInfo)
if rc != C.RFC_OK {
@@ -722,6 +734,12 @@ func wrapVariable(cType C.RFCTYPE, container C.RFC_FUNCTION_HANDLE, cName *C.SAP
return result, rfcError(errorInfo, "Failed getting int2")
}
return int(int2Value), err
+ case C.RFCTYPE_INT8:
+ rc = C.RfcGetInt8(container, cName, &int8Value, &errorInfo)
+ if rc != C.RFC_OK {
+ return result, rfcError(errorInfo, "Failed getting int8")
+ }
+ return int(int8Value), err
case C.RFCTYPE_DATE:
dateValue = (*C.RFC_CHAR)(C.malloc(8))
defer C.free(unsafe.Pointer(dateValue))
@@ -859,27 +877,7 @@ func GetNWRFCLibVersion() (major, minor, patchlevel uint) {
//# CONNECTION #
//################################################################################
-// ConnectionParameter holds all the connection parameters possible (at the moment).
-type ConnectionParameter struct {
- Dest string
- Client string
- User string // Username
- Passwd string // Password
- Lang string // Language
- Trace string
- Ashost string
- Sysnr string
- Mshost string
- Msserv string
- Sysid string
- Group string
- Snc_qop string
- Snc_myname string
- Snc_partnername string
- Snc_lib string
- Mysapsso2 string
- Saprouter string
-}
+type ConnectionParameters map[string]string
type Connection struct {
handle C.RFC_CONNECTION_HANDLE
@@ -887,7 +885,8 @@ type Connection struct {
returnImportParams bool
alive bool
paramCount C.uint
- connectionParams []C.RFC_CONNECTION_PARAMETER
+ connParams []C.RFC_CONNECTION_PARAMETER
+ connectionParams ConnectionParameters
// tHandle C.RFC_TRANSACTION_HANDLE
// active_transaction bool
// uHandle C.RFC_UNIT_HANDLE
@@ -895,40 +894,47 @@ type Connection struct {
}
func connectionFinalizer(conn *Connection) {
- for _, connParam := range conn.connectionParams {
+ for _, connParam := range conn.connParams {
C.free(unsafe.Pointer(connParam.name))
C.free(unsafe.Pointer(connParam.value))
}
}
-// ConnectionFromParams creates a new connection with the given connection parameters and tries to open it. If this is successfull it returns the connection else it returns nil.
-func ConnectionFromParams(connParams ConnectionParameter) (conn *Connection, err error) {
+// ConnectionFromParams creates a new connection with the given connection parameters and tries to open it.
+// Returns the connection if successfull, otherwise nil.
+func ConnectionFromParams(connectionParams ConnectionParameters) (conn *Connection, err error) {
conn = new(Connection)
- runtime.SetFinalizer(conn, connectionFinalizer)
- p := reflect.ValueOf(&connParams).Elem()
+
conn.handle = nil
- conn.paramCount = C.uint(p.NumField())
- conn.connectionParams = make([]C.RFC_CONNECTION_PARAMETER, conn.paramCount, conn.paramCount)
conn.rstrip = true
conn.returnImportParams = false
conn.alive = false
- for i := 0; i < p.NumField(); i++ {
- conn.connectionParams[i].name, err = fillString(p.Type().Field(i).Name)
- conn.connectionParams[i].value, err = fillString(p.Field(i).String())
+
+ runtime.SetFinalizer(conn, connectionFinalizer)
+ conn.paramCount = C.uint(len(connectionParams))
+ conn.connectionParams = connectionParams
+ conn.connParams = make([]C.RFC_CONNECTION_PARAMETER, conn.paramCount, conn.paramCount)
+ i := 0
+ for name, value := range conn.connectionParams {
+ conn.connParams[i].name, err = fillString(name)
+ conn.connParams[i].value, err = fillString(value)
+ i++
}
if err != nil {
return nil, err
}
+
err = conn.Open()
if err != nil {
return nil, err
}
+
return
}
// ConnectionFromDest creates a new connection with just the dest system id.
func ConnectionFromDest(dest string) (conn *Connection, err error) {
- return ConnectionFromParams(ConnectionParameter{Dest: dest})
+ return ConnectionFromParams(ConnectionParameters{"dest": dest})
}
// RStrip sets rstrip of the given connection to the passed parameter and returns the connection
@@ -966,8 +972,8 @@ func (conn *Connection) Close() (err error) {
// Open opens the connection and sets alive to true.
func (conn *Connection) Open() (err error) {
var errorInfo C.RFC_ERROR_INFO
- conn.handle = C.RfcOpenConnection(&conn.connectionParams[0], conn.paramCount, &errorInfo)
- if conn.handle == nil {
+ conn.handle = C.RfcOpenConnection(&conn.connParams[0], conn.paramCount, &errorInfo)
+ if errorInfo.code != C.RFC_OK {
return rfcError(errorInfo, "Connection could not be opened")
} else {
conn.alive = true
@@ -1005,15 +1011,10 @@ func (conn *Connection) Ping() (err error) {
func (conn *Connection) GetConnectionAttributes() (connAttr ConnectionAttributes, err error) {
var errorInfo C.RFC_ERROR_INFO
var attributes C.RFC_ATTRIBUTES
- if !conn.alive {
- err = conn.Open()
- if err != nil {
- return
- }
- }
+
rc := C.RfcGetConnectionAttributes(conn.handle, &attributes, &errorInfo)
- if rc != C.RFC_OK {
- return connAttr, rfcError(errorInfo, "Could not get connection attributes")
+ if rc != C.RFC_OK || errorInfo.code != C.RFC_OK {
+ return nil, rfcError(errorInfo, "Could not get connection attributes")
}
return wrapConnectionAttributes(attributes, conn.rstrip)
}
@@ -1105,6 +1106,7 @@ func (conn *Connection) Call(goFuncName string, params interface{}) (result map[
}
rc := C.RfcInvoke(conn.handle, funcCont, &errorInfo)
+
if rc != C.RFC_OK {
return result, rfcError(errorInfo, "Could not invoke function \"%v\"", goFuncName)
}
diff --git a/gorfc/gorfc_test.go b/gorfc/gorfc_test.go
index fff798d..99f8075 100644
--- a/gorfc/gorfc_test.go
+++ b/gorfc/gorfc_test.go
@@ -2,7 +2,10 @@ package gorfc
import (
"fmt"
+ "os"
"reflect"
+
+ //"reflect"
"strings"
"testing"
"time"
@@ -10,25 +13,14 @@ import (
"github.com/stretchr/testify/assert"
)
-//
-// Helper Functions
-//
-
-func isValueInList(value string, list []string) bool {
- for _, v := range list {
- if v == value {
- return true
- }
- }
- return false
-}
-
//
// NW RFC Lib Version
//
func TestNWRFCLibVersion(t *testing.T) {
major, minor, patchlevel := GetNWRFCLibVersion()
- assert.Equal(t, "7500.0.5", fmt.Sprintf("%d.%d.%d", major, minor, patchlevel)) // adapt to your NW RFC Lib version
+ assert.Equal(t, uint(7500), major) // adapt to your NW RFC Lib version
+ assert.Equal(t, uint(0), minor)
+ assert.Greater(t, patchlevel, uint(4))
}
//
@@ -42,100 +34,96 @@ func TestConnect(t *testing.T) {
}
assert.NotNil(t, c)
assert.Nil(t, err)
+ assert.True(t, c.Alive())
c.Close()
+ assert.False(t, c.Alive())
}
func TestConnectionAttributes(t *testing.T) {
- fmt.Println("Connection test: Parameters")
+ fmt.Println("Connection test: Attributes")
c, err := ConnectionFromParams(abapSystem())
- if err != nil {
- t.SkipNow()
- }
+ assert.Equal(t, err, nil)
a, err := c.GetConnectionAttributes()
- paramNames := []string{
- "Dest",
- "Host",
- "PartnerHost",
- "SysNumber",
- "SysId",
- "Client",
- "User",
- "Language",
- "Trace",
- "IsoLanguage",
- "Codepage",
- "PartnerCodepage",
- "RfcRole",
- "Type",
- "PartnerType",
- "Rel",
- "PartnerRel",
- "KernelRel",
- "CpicConvId",
- "ProgName",
- "PartnerBytesPerChar",
- "PartnerSystemCodepage",
- "Reserved"}
-
- s := reflect.ValueOf(&a).Elem()
- // check if all parameters returned
- assert.Equal(t, 23, s.NumField())
- for i := 0; i < s.NumField(); i++ {
- pname := s.Type().Field(i).Name
- assert.Equal(t, true, isValueInList(pname, paramNames), pname)
- //f := s.Field(i)
- //fmt.Println(i, f.Type(), f, s.Type().Field(i).Name)
+ paramNames := map[string]struct{}{
+ "Dest": struct{}{},
+ "Host": struct{}{},
+ "PartnerHost": struct{}{},
+ "SysNumber": struct{}{},
+ "SysId": struct{}{},
+ "Client": struct{}{},
+ "User": struct{}{},
+ "Language": struct{}{},
+ "Trace": struct{}{},
+ "IsoLanguage": struct{}{},
+ "Codepage": struct{}{},
+ "PartnerCodepage": struct{}{},
+ "RfcRole": struct{}{},
+ "Type": struct{}{},
+ "PartnerType": struct{}{},
+ "Rel": struct{}{},
+ "PartnerRel": struct{}{},
+ "KernelRel": struct{}{},
+ "CpicConvId": struct{}{},
+ "ProgName": struct{}{},
+ "PartnerBytesPerChar": struct{}{},
+ "PartnerSystemCodepage": struct{}{},
+ "partnerIP": struct{}{},
+ "partnerIPv6": struct{}{},
}
- // check some parameters
- assert.Equal(t, strings.ToUpper(abapSystem().User), a.User)
- assert.Equal(t, abapSystem().Sysnr, a.SysNumber)
- assert.Equal(t, abapSystem().Client, a.Client)
+
+ // check if all parameters returned
+ assert.Equal(t, len(a), len(paramNames))
+ // and the content of some
+ assert.Equal(t, strings.ToUpper(abapSystem()["user"]), a["user"])
+ assert.Equal(t, abapSystem()["sysnr"], a["sysNumber"])
+ assert.Equal(t, abapSystem()["client"], a["client"])
c.Close()
}
func TestPing(t *testing.T) {
fmt.Println("Connection test: Ping")
c, err := ConnectionFromParams(abapSystem())
- if err != nil {
- return
- }
+ assert.Nil(t, err)
err = c.Ping()
assert.Nil(t, err)
c.Close()
}
-func TestAlive(t *testing.T) {
- fmt.Println("Connection test: Alive")
- c, err := ConnectionFromParams(abapSystem())
- if err != nil {
- return
- }
- a := c.Alive()
- assert.True(t, a)
- c.Close()
- a = c.Alive()
- assert.False(t, a)
-}
-
func TestReopen(t *testing.T) {
fmt.Println("Connection test: Reopen")
c, err := ConnectionFromParams(abapSystem())
- if err != nil {
- return
- }
+ assert.Nil(t, err)
err = c.Reopen()
assert.Nil(t, err)
c.Close()
}
func TestConnectFromDest(t *testing.T) {
- c, err := ConnectionFromDest("I64_2")
- if err != nil {
- return
- }
+ fmt.Println("Connection test: Destination")
+ assert.Greater(t, len(os.Getenv("RFC_INI")), 0)
+ c, err := ConnectionFromDest("MME")
+ assert.Nil(t, err)
+ assert.NotNil(t, c)
+ c.Close()
+}
+
+func TestConnectionEcho(t *testing.T) {
+ fmt.Println("connection test: Echo")
+ assert.Greater(t, len(os.Getenv("RFC_INI")), 0)
+ c, err := ConnectionFromDest("MME")
+ assert.Nil(t, err)
assert.NotNil(t, c)
+ type importStruct struct {
+ XXX string
+ }
+ params := map[string]interface{}{
+ "REQUTEXT": "Hällö",
+ }
+ r, err := c.Call("STFC_CONNECTION", params)
assert.Nil(t, err)
+ assert.NotNil(t, r["ECHOTEXT"])
+ assert.Equal(t, params["REQUTEXT"], r["ECHOTEXT"])
c.Close()
}
@@ -146,11 +134,8 @@ func TestConnectFromDest(t *testing.T) {
func TestWrongUserConnect(t *testing.T) {
fmt.Println("Connection Error: Logon")
a := abapSystem()
- a.User = "@!n0user"
+ a["user"] = "@!n0user"
c, err := ConnectionFromParams(a)
- if err != nil {
- return
- }
assert.Nil(t, c)
assert.NotNil(t, err)
assert.Equal(t, "Connection could not be opened", err.(*RfcError).Description)
@@ -162,15 +147,12 @@ func TestWrongUserConnect(t *testing.T) {
func TestMissingAshostConnect(t *testing.T) {
fmt.Println("Connection Error: Connection parameter missing")
a := abapSystem()
- a.Ashost = ""
+ a["ashost"] = ""
c, err := ConnectionFromParams(a)
- if err != nil {
- return
- }
assert.Nil(t, c)
assert.NotNil(t, err)
assert.Equal(t, "Connection could not be opened", err.(*RfcError).Description)
- assert.Equal(t, "Parameter ASHOST, GWHOST or MSHOST is missing.", err.(*RfcError).ErrorInfo.Message)
+ assert.Equal(t, "Parameter ASHOST, GWHOST, MSHOST or PORT is missing.", err.(*RfcError).ErrorInfo.Message)
assert.Equal(t, "RFC_INVALID_PARAMETER", err.(*RfcError).ErrorInfo.Code)
assert.Equal(t, "RFC_INVALID_PARAMETER", err.(*RfcError).ErrorInfo.Key)
}
@@ -181,9 +163,7 @@ func TestWrongParameter(t *testing.T) {
XXX string
}
c, err := ConnectionFromParams(abapSystem())
- if err != nil {
- return
- }
+ assert.Nil(t, err)
r, err := c.Call("STFC_CONNECTION", importStruct{"wrong param"})
assert.Equal(t, map[string]interface{}(nil), r)
assert.Equal(t, "RFC_INVALID_PARAMETER", err.(*RfcError).ErrorInfo.Code) // todo: should be "20" ??
@@ -197,10 +177,9 @@ func TestWrongParameter(t *testing.T) {
//
func TestFunctionDescription(t *testing.T) {
+ fmt.Println("STFC: Get Function Description")
c, err := ConnectionFromParams(abapSystem())
- if err != nil {
- return
- }
+ assert.Nil(t, err)
d, err := c.GetFunctionDescription("STFC_CONNECTION")
assert.Nil(t, err)
assert.Equal(t, "ECHOTEXT", d.Parameters[0].Name)
@@ -209,11 +188,10 @@ func TestFunctionDescription(t *testing.T) {
c.Close()
}
-func TestFunctionCall(t *testing.T) {
+func TestTableRowAsStructure(t *testing.T) {
+ fmt.Println("STFC: Table rows as structure")
c, err := ConnectionFromParams(abapSystem())
- if err != nil {
- return
- }
+ assert.Nil(t, err)
type importedStruct struct {
RFCFLOAT float64
RFCCHAR1 string
@@ -232,7 +210,7 @@ func TestFunctionCall(t *testing.T) {
IMPORTSTRUCT importedStruct
RFCTABLE []importedStruct
}
- importStruct := importedStruct{1.23456789, "A", "BC", "DEFG", 1, 2, 345, []byte{0, 11, 12}, time.Now(), time.Now(), "HELLÖ SÄP", "DATA222"}
+ importStruct := importedStruct{4.23456789, "A", "BC", "DEFG", 1, 2, 345, []byte{0, 11, 12}, time.Now(), time.Now(), "HELLÖ SÄP", "DATA222"}
params := parameter{importStruct, []importedStruct{importStruct}}
r, err := c.Call("STFC_STRUCTURE", params)
assert.Nil(t, err)
@@ -249,7 +227,7 @@ func TestFunctionCall(t *testing.T) {
assert.Equal(t, importStruct.RFCINT1, echoStruct["RFCINT1"])
assert.Equal(t, importStruct.RFCINT2, echoStruct["RFCINT2"])
assert.Equal(t, importStruct.RFCINT4, echoStruct["RFCINT4"])
- //assert.Equal(t, importStruct.RFCHEX3, echoStruct["RFCHEX3"])
+ assert.Equal(t, importStruct.RFCHEX3, echoStruct["RFCHEX3"])
assert.Equal(t, importStruct.RFCTIME.Format("150405"), echoStruct["RFCTIME"].(time.Time).Format("150405"))
assert.Equal(t, importStruct.RFCDATE.Format("20060102"), echoStruct["RFCDATE"].(time.Time).Format("20060102"))
assert.Equal(t, importStruct.RFCDATA1, echoStruct["RFCDATA1"])
@@ -264,7 +242,7 @@ func TestFunctionCall(t *testing.T) {
assert.Equal(t, importStruct.RFCINT1, echoTableLine["RFCINT1"])
assert.Equal(t, importStruct.RFCINT2, echoTableLine["RFCINT2"])
assert.Equal(t, importStruct.RFCINT4, echoTableLine["RFCINT4"])
- //assert.Equal(t, importStruct.RFCHEX3, echoTableLine["RFCHEX3"])
+ assert.Equal(t, importStruct.RFCHEX3, echoTableLine["RFCHEX3"])
assert.Equal(t, importStruct.RFCTIME.Format("150405"), echoTableLine["RFCTIME"].(time.Time).Format("150405"))
assert.Equal(t, importStruct.RFCDATE.Format("20060102"), echoTableLine["RFCDATE"].(time.Time).Format("20060102"))
assert.Equal(t, importStruct.RFCDATA1, echoTableLine["RFCDATA1"])
@@ -272,11 +250,10 @@ func TestFunctionCall(t *testing.T) {
c.Close()
}
-func TestStructPassedAsMap(t *testing.T) {
+func TestTableRowAsMap(t *testing.T) {
+ fmt.Println("STFC: Table rows as saps")
c, err := ConnectionFromParams(abapSystem())
- if err != nil {
- return
- }
+ assert.Nil(t, err)
params := map[string]interface{}{
"IMPORTSTRUCT": map[string]interface{}{
@@ -306,7 +283,7 @@ func TestStructPassedAsMap(t *testing.T) {
assert.Equal(t, importStruct["RFCINT1"], echoStruct["RFCINT1"])
assert.Equal(t, importStruct["RFCINT2"], echoStruct["RFCINT2"])
assert.Equal(t, importStruct["RFCINT4"], echoStruct["RFCINT4"])
- //assert.Equal(t, importStruct["RFCHEX3"], echoStruct["RFCHEX3"])
+ assert.Equal(t, importStruct["RFCHEX3"], echoStruct["RFCHEX3"])
assert.Equal(t, importStruct["RFCTIME"].(time.Time).Format("150405"), echoStruct["RFCTIME"].(time.Time).Format("150405"))
assert.Equal(t, importStruct["RFCDATE"].(time.Time).Format("20060102"), echoStruct["RFCDATE"].(time.Time).Format("20060102"))
assert.Equal(t, importStruct["RFCDATA1"], echoStruct["RFCDATA1"])
@@ -314,15 +291,54 @@ func TestStructPassedAsMap(t *testing.T) {
c.Close()
}
+func TestTableRowAsVariable(t *testing.T) {
+ fmt.Println("STFC: Table rows as single variables")
+ c, err := ConnectionFromParams(abapSystem())
+ assert.Nil(t, err)
+
+ // array of byte sequences
+ certTable := [][]byte{
+ []byte("ABC"),
+ []byte("DEF"),
+ }
+ params := map[string]interface{}{
+ "IT_CERTLIST": certTable,
+ }
+ r, err := c.Call("SSFR_PSE_CREATE", params)
+ assert.Nil(t, err)
+ bapiret := r["ET_BAPIRET2"].([]interface{})[0].(map[string]interface{})
+ assert.Equal(t, bapiret["ID"], "1S")
+ assert.Equal(t, bapiret["MESSAGE"], "Creating PSE failed (INITIAL)")
+
+ // array of maps, works as well, as a workaround
+ certTableMap := []map[string]interface{}{
+ map[string]interface{}{
+ "": []byte("ABC"),
+ },
+ map[string]interface{}{
+ "": []byte("DEF"),
+ },
+ }
+ params = map[string]interface{}{
+ "IT_CERTLIST": certTableMap,
+ }
+ r, err = c.Call("SSFR_PSE_CREATE", params)
+ assert.Nil(t, err)
+ // same error message
+ bapiret = r["ET_BAPIRET2"].([]interface{})[0].(map[string]interface{})
+ assert.Equal(t, bapiret["ID"], "1S")
+ assert.Equal(t, bapiret["MESSAGE"], "Creating PSE failed (INITIAL)")
+ c.Close()
+}
+
func TestConfigParameter(t *testing.T) {
+ fmt.Println("STFC: Connection options: rstrip, returnImportParams")
//rstrip = false
c, err := ConnectionFromParams(abapSystem())
- if err != nil {
- return
- }
+ assert.Nil(t, err)
c.RStrip(false)
r, _ := c.Call("STFC_CONNECTION", map[string]interface{}{"REQUTEXT": "HELLÖ SÄP"})
- assert.Equal(t, 255, len(reflect.ValueOf(r["ECHOTEXT"]).String()))
+ assert.Equal(t, 257, len(reflect.ValueOf(r["ECHOTEXT"]).String()))
assert.Equal(t, "HELLÖ SÄP", strings.TrimSpace(reflect.ValueOf(r["ECHOTEXT"]).String()))
//returnImportParams = true
@@ -334,11 +350,9 @@ func TestConfigParameter(t *testing.T) {
}
func TestInvalidParameterFunctionCall(t *testing.T) {
+ fmt.Println("STFC: Invalid RFM parameter")
c, err := ConnectionFromParams(abapSystem())
- if err != nil {
- return
- }
-
+ assert.Nil(t, err)
r, err := c.Call("STFC_CONNECTION", map[string]interface{}{"XXX": "wrongParameter"})
assert.Nil(t, r)
assert.NotNil(t, err)
@@ -349,11 +363,14 @@ func TestInvalidParameterFunctionCall(t *testing.T) {
c.Close()
}
+//
+// Error test
+//
+
func TestErrorFunctionCall(t *testing.T) {
+ fmt.Println("Error: ABAP message")
c, err := ConnectionFromParams(abapSystem())
- if err != nil {
- return
- }
+ assert.Nil(t, err)
r, err := c.Call("RFC_RAISE_ERROR", map[string]interface{}{"MESSAGETYPE": "A"})
assert.Nil(t, r)
@@ -369,15 +386,13 @@ func TestErrorFunctionCall(t *testing.T) {
c.Close()
}
-func abapSystem() ConnectionParameter {
- return ConnectionParameter{
- Dest: "I64",
- Client: "800",
- User: "demo",
- Passwd: "welcome",
- Lang: "EN",
- Ashost: "10.117.24.158 ",
- Sysnr: "00",
- Saprouter: "/H/203.13.155.17/W/xjkb3d/H/172.19.138.120/H/",
+func abapSystem() ConnectionParameters {
+ return ConnectionParameters{
+ "user": "demo",
+ "passwd": "welcome",
+ "ashost": "10.68.110.51",
+ "sysnr": "00",
+ "client": "620",
+ "lang": "EN",
}
}
diff --git a/gorfc/sapnwrfc.ini b/gorfc/sapnwrfc.ini
new file mode 100644
index 0000000..c973f25
--- /dev/null
+++ b/gorfc/sapnwrfc.ini
@@ -0,0 +1,7 @@
+DEST=MME
+USER=demo
+PASSWD=welcome
+ASHOST=10.68.110.51
+SYSNR=00
+CLIENT=620
+LANG=EN
\ No newline at end of file