From 7b550cacde61d103466269e2c9167a3826e313fa Mon Sep 17 00:00:00 2001 From: t_max <1172915550@qq.com> Date: Mon, 2 Dec 2024 14:13:10 +0800 Subject: [PATCH] refactor: remove driver-go dependency --- controller/rest/configcontroller.go | 4 +- controller/rest/restful.go | 8 +- controller/rest/table_vgid.go | 4 +- controller/ws/query/ws.go | 6 +- controller/ws/schemaless/schemaless.go | 6 +- controller/ws/schemaless/schemaless_test.go | 10 +- controller/ws/stmt/convert.go | 8 +- controller/ws/stmt/convert_test.go | 6 +- controller/ws/stmt/stmt.go | 12 +- controller/ws/tmq/tmq.go | 10 +- controller/ws/tmq/tmq_test.go | 4 +- controller/ws/ws/fetch.go | 4 +- controller/ws/ws/handler.go | 2 +- controller/ws/ws/misc.go | 2 +- controller/ws/ws/query.go | 6 +- controller/ws/ws/query_result.go | 4 +- controller/ws/ws/query_test.go | 2 +- controller/ws/ws/raw.go | 4 +- controller/ws/ws/schemaless.go | 2 +- controller/ws/ws/schemaless_test.go | 14 +- controller/ws/ws/stmt.go | 12 +- controller/ws/ws/stmt2.go | 6 +- controller/ws/ws/stmt2_test.go | 4 +- controller/ws/ws/stmt_test.go | 10 +- controller/ws/wstool/error.go | 2 +- controller/ws/wstool/error_test.go | 2 +- db/async/handlerpool.go | 2 +- db/async/row.go | 4 +- db/async/row_test.go | 2 +- db/async/stmt2pool.go | 2 +- db/asynctmq/tmq.go | 2 +- db/asynctmq/tmq_windows.go | 2 +- db/asynctmq/tmqcb.go | 2 +- db/asynctmq/tmqhandle/handler.go | 2 +- db/commonpool/pool.go | 6 +- db/commonpool/pool_test.go | 2 +- db/init.go | 6 +- db/syncinterface/wrapper.go | 6 +- db/syncinterface/wrapper_test.go | 14 +- db/tool/createdb.go | 4 +- db/tool/createdb_test.go | 4 +- db/tool/notify.go | 8 +- db/tool/notify_test.go | 6 +- driver/common/change.go | 34 + driver/common/change_test.go | 101 + driver/common/column.go | 46 + driver/common/const.go | 73 + driver/common/datatype.go | 89 + driver/common/param/column.go | 220 + driver/common/param/column_test.go | 435 ++ driver/common/param/param.go | 337 ++ driver/common/param/param_test.go | 654 +++ driver/common/parser/block.go | 374 ++ driver/common/parser/block_test.go | 797 +++ driver/common/parser/mem.go | 12 + driver/common/parser/mem.s | 0 driver/common/parser/mem_test.go | 20 + driver/common/parser/raw.go | 184 + driver/common/parser/raw_test.go | 1049 ++++ driver/common/serializer/block.go | 552 ++ driver/common/serializer/block_test.go | 397 ++ driver/common/stmt/field.go | 73 + driver/common/stmt/field_test.go | 143 + driver/common/stmt/stmt2.go | 580 +++ driver/common/stmt/stmt2_test.go | 2437 +++++++++ driver/common/tmq/config.go | 34 + driver/common/tmq/config_test.go | 52 + driver/common/tmq/event.go | 204 + driver/common/tmq/event_test.go | 352 ++ driver/common/tmq/tmq.go | 87 + driver/common/tmq/tmq_test.go | 197 + driver/errors/errors.go | 30 + driver/errors/errors_test.go | 52 + driver/types/taostype.go | 54 + driver/types/types.go | 492 ++ driver/types/types_test.go | 2122 ++++++++ driver/wrapper/asynccb.go | 38 + driver/wrapper/block.go | 49 + driver/wrapper/block_test.go | 924 ++++ driver/wrapper/cgo/README.md | 1 + driver/wrapper/cgo/handle.go | 81 + driver/wrapper/cgo/handle_test.go | 107 + driver/wrapper/field.go | 67 + driver/wrapper/field_test.go | 483 ++ driver/wrapper/notify.go | 22 + driver/wrapper/notify_test.go | 100 + driver/wrapper/notifycb.go | 36 + driver/wrapper/row.go | 77 + driver/wrapper/row_test.go | 628 +++ driver/wrapper/schemaless.go | 233 + driver/wrapper/schemaless_test.go | 744 +++ driver/wrapper/setconfig.go | 42 + driver/wrapper/setconfig_test.go | 41 + driver/wrapper/stmt.go | 756 +++ driver/wrapper/stmt2.go | 857 ++++ driver/wrapper/stmt2_test.go | 5076 +++++++++++++++++++ driver/wrapper/stmt2async.go | 26 + driver/wrapper/stmt_test.go | 1367 +++++ driver/wrapper/taosc.go | 289 ++ driver/wrapper/taosc_test.go | 607 +++ driver/wrapper/tmq.go | 334 ++ driver/wrapper/tmq_test.go | 2012 ++++++++ driver/wrapper/tmqcb.go | 49 + driver/wrapper/whitelist.go | 29 + driver/wrapper/whitelist_test.go | 21 + driver/wrapper/whitelistcb.go | 35 + driver/wrapper/whitelistcb_test.go | 57 + go.mod | 1 - go.sum | 2 - plugin/collectd/config.go | 2 +- plugin/collectd/plugin_test.go | 82 +- plugin/influxdb/plugin.go | 2 +- plugin/influxdb/plugin_test.go | 104 +- plugin/nodeexporter/config.go | 2 +- plugin/nodeexporter/plugin.go | 2 +- plugin/nodeexporter/plugin_test.go | 91 +- plugin/opentsdb/plugin.go | 2 +- plugin/opentsdb/plugin_test.go | 129 +- plugin/opentsdbtelnet/config.go | 2 +- plugin/opentsdbtelnet/plugin_test.go | 85 +- plugin/prometheus/plugin.go | 2 +- plugin/prometheus/plugin_test.go | 2 +- plugin/prometheus/process.go | 6 +- plugin/statsd/config.go | 2 +- plugin/statsd/plugin_test.go | 85 +- schemaless/capi/influxdb.go | 4 +- schemaless/capi/influxdb_test.go | 4 +- schemaless/capi/opentsdb.go | 4 +- schemaless/capi/opentsdb_test.go | 4 +- tools/ctools/block.go | 4 +- tools/ctools/block_test.go | 4 +- tools/parseblock/parse.go | 2 +- tools/parseblock/parse_test.go | 2 +- version/version.go | 2 +- 134 files changed, 27933 insertions(+), 399 deletions(-) create mode 100644 driver/common/change.go create mode 100644 driver/common/change_test.go create mode 100644 driver/common/column.go create mode 100644 driver/common/const.go create mode 100644 driver/common/datatype.go create mode 100644 driver/common/param/column.go create mode 100644 driver/common/param/column_test.go create mode 100644 driver/common/param/param.go create mode 100644 driver/common/param/param_test.go create mode 100644 driver/common/parser/block.go create mode 100644 driver/common/parser/block_test.go create mode 100644 driver/common/parser/mem.go create mode 100644 driver/common/parser/mem.s create mode 100644 driver/common/parser/mem_test.go create mode 100644 driver/common/parser/raw.go create mode 100644 driver/common/parser/raw_test.go create mode 100644 driver/common/serializer/block.go create mode 100644 driver/common/serializer/block_test.go create mode 100644 driver/common/stmt/field.go create mode 100644 driver/common/stmt/field_test.go create mode 100644 driver/common/stmt/stmt2.go create mode 100644 driver/common/stmt/stmt2_test.go create mode 100644 driver/common/tmq/config.go create mode 100644 driver/common/tmq/config_test.go create mode 100644 driver/common/tmq/event.go create mode 100644 driver/common/tmq/event_test.go create mode 100644 driver/common/tmq/tmq.go create mode 100644 driver/common/tmq/tmq_test.go create mode 100644 driver/errors/errors.go create mode 100644 driver/errors/errors_test.go create mode 100644 driver/types/taostype.go create mode 100644 driver/types/types.go create mode 100644 driver/types/types_test.go create mode 100644 driver/wrapper/asynccb.go create mode 100644 driver/wrapper/block.go create mode 100644 driver/wrapper/block_test.go create mode 100644 driver/wrapper/cgo/README.md create mode 100644 driver/wrapper/cgo/handle.go create mode 100644 driver/wrapper/cgo/handle_test.go create mode 100644 driver/wrapper/field.go create mode 100644 driver/wrapper/field_test.go create mode 100644 driver/wrapper/notify.go create mode 100644 driver/wrapper/notify_test.go create mode 100644 driver/wrapper/notifycb.go create mode 100644 driver/wrapper/row.go create mode 100644 driver/wrapper/row_test.go create mode 100644 driver/wrapper/schemaless.go create mode 100644 driver/wrapper/schemaless_test.go create mode 100644 driver/wrapper/setconfig.go create mode 100644 driver/wrapper/setconfig_test.go create mode 100644 driver/wrapper/stmt.go create mode 100644 driver/wrapper/stmt2.go create mode 100644 driver/wrapper/stmt2_test.go create mode 100644 driver/wrapper/stmt2async.go create mode 100644 driver/wrapper/stmt_test.go create mode 100644 driver/wrapper/taosc.go create mode 100644 driver/wrapper/taosc_test.go create mode 100644 driver/wrapper/tmq.go create mode 100644 driver/wrapper/tmq_test.go create mode 100644 driver/wrapper/tmqcb.go create mode 100644 driver/wrapper/whitelist.go create mode 100644 driver/wrapper/whitelist_test.go create mode 100644 driver/wrapper/whitelistcb.go create mode 100644 driver/wrapper/whitelistcb_test.go diff --git a/controller/rest/configcontroller.go b/controller/rest/configcontroller.go index e5490213..a87e5d7e 100644 --- a/controller/rest/configcontroller.go +++ b/controller/rest/configcontroller.go @@ -7,11 +7,11 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" - taoserrors "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/controller" "github.com/taosdata/taosadapter/v3/db/commonpool" "github.com/taosdata/taosadapter/v3/db/tool" + taoserrors "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/tools/iptool" ) diff --git a/controller/rest/restful.go b/controller/rest/restful.go index d7667e52..8b56bd32 100644 --- a/controller/rest/restful.go +++ b/controller/rest/restful.go @@ -13,14 +13,14 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" - "github.com/taosdata/driver-go/v3/common" - "github.com/taosdata/driver-go/v3/common/parser" - tErrors "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/controller" "github.com/taosdata/taosadapter/v3/db/async" "github.com/taosdata/taosadapter/v3/db/commonpool" + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + tErrors "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/httperror" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/monitor" diff --git a/controller/rest/table_vgid.go b/controller/rest/table_vgid.go index 42f135fb..6ea6dc91 100644 --- a/controller/rest/table_vgid.go +++ b/controller/rest/table_vgid.go @@ -6,10 +6,10 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" - tErrors "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/db/commonpool" "github.com/taosdata/taosadapter/v3/db/syncinterface" + tErrors "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/tools/connectpool" "github.com/taosdata/taosadapter/v3/tools/iptool" diff --git a/controller/ws/query/ws.go b/controller/ws/query/ws.go index f1e2ea04..ab1d4f9f 100644 --- a/controller/ws/query/ws.go +++ b/controller/ws/query/ws.go @@ -16,14 +16,14 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" - "github.com/taosdata/driver-go/v3/common/parser" - "github.com/taosdata/driver-go/v3/wrapper" - "github.com/taosdata/driver-go/v3/wrapper/cgo" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/controller" "github.com/taosdata/taosadapter/v3/controller/ws/wstool" "github.com/taosdata/taosadapter/v3/db/async" "github.com/taosdata/taosadapter/v3/db/tool" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + "github.com/taosdata/taosadapter/v3/driver/wrapper" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" "github.com/taosdata/taosadapter/v3/httperror" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/monitor" diff --git a/controller/ws/schemaless/schemaless.go b/controller/ws/schemaless/schemaless.go index 26cdcb99..6129a75c 100644 --- a/controller/ws/schemaless/schemaless.go +++ b/controller/ws/schemaless/schemaless.go @@ -10,14 +10,14 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" - tErrors "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" - "github.com/taosdata/driver-go/v3/wrapper/cgo" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/controller" "github.com/taosdata/taosadapter/v3/controller/ws/wstool" "github.com/taosdata/taosadapter/v3/db/syncinterface" "github.com/taosdata/taosadapter/v3/db/tool" + tErrors "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/tools/generator" "github.com/taosdata/taosadapter/v3/tools/iptool" diff --git a/controller/ws/schemaless/schemaless_test.go b/controller/ws/schemaless/schemaless_test.go index c2f4e25b..1e54c2ae 100644 --- a/controller/ws/schemaless/schemaless_test.go +++ b/controller/ws/schemaless/schemaless_test.go @@ -14,12 +14,12 @@ import ( "github.com/gorilla/websocket" "github.com/spf13/viper" "github.com/stretchr/testify/assert" - "github.com/taosdata/driver-go/v3/ws/schemaless" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/controller" _ "github.com/taosdata/taosadapter/v3/controller/rest" "github.com/taosdata/taosadapter/v3/controller/ws/wstool" "github.com/taosdata/taosadapter/v3/db" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/log" ) @@ -68,7 +68,7 @@ func TestRestful_InitSchemaless(t *testing.T) { }{ { name: "influxdb", - protocol: schemaless.InfluxDBLineProtocol, + protocol: wrapper.InfluxDBLineProtocol, precision: "ms", data: "measurement,host=host1 field1=2i,field2=2.0 1577837300000\n" + "measurement,host=host1 field1=2i,field2=2.0 1577837400000\n" + @@ -81,7 +81,7 @@ func TestRestful_InitSchemaless(t *testing.T) { }, { name: "opentsdb_telnet", - protocol: schemaless.OpenTSDBTelnetLineProtocol, + protocol: wrapper.OpenTSDBTelnetLineProtocol, precision: "ms", data: "meters.current 1648432611249 10.3 location=California.SanFrancisco group=2\n" + "meters.current 1648432611250 12.6 location=California.SanFrancisco group=2\n" + @@ -98,7 +98,7 @@ func TestRestful_InitSchemaless(t *testing.T) { }, { name: "opentsdb_json", - protocol: schemaless.OpenTSDBJsonFormatProtocol, + protocol: wrapper.OpenTSDBJsonFormatProtocol, precision: "ms", data: `[ { @@ -202,7 +202,7 @@ func TestRestful_InitSchemaless(t *testing.T) { assert.NoError(t, err, string(msg)) assert.Equal(t, reqID, schemalessResp.ReqID) assert.Equal(t, 0, schemalessResp.Code, schemalessResp.Message) - if c.protocol != schemaless.OpenTSDBJsonFormatProtocol { + if c.protocol != wrapper.OpenTSDBJsonFormatProtocol { assert.Equal(t, c.totalRows, schemalessResp.TotalRows) } assert.Equal(t, c.affectedRows, schemalessResp.AffectedRows) diff --git a/controller/ws/stmt/convert.go b/controller/ws/stmt/convert.go index 17537dab..7791c186 100644 --- a/controller/ws/stmt/convert.go +++ b/controller/ws/stmt/convert.go @@ -10,10 +10,10 @@ import ( "unsafe" jsoniter "github.com/json-iterator/go" - "github.com/taosdata/driver-go/v3/common" - "github.com/taosdata/driver-go/v3/common/parser" - stmtCommon "github.com/taosdata/driver-go/v3/common/stmt" - "github.com/taosdata/driver-go/v3/types" + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + stmtCommon "github.com/taosdata/taosadapter/v3/driver/common/stmt" + "github.com/taosdata/taosadapter/v3/driver/types" "github.com/taosdata/taosadapter/v3/tools" ) diff --git a/controller/ws/stmt/convert_test.go b/controller/ws/stmt/convert_test.go index 9d07e170..a6fd9721 100644 --- a/controller/ws/stmt/convert_test.go +++ b/controller/ws/stmt/convert_test.go @@ -9,9 +9,9 @@ import ( "unsafe" "github.com/stretchr/testify/assert" - "github.com/taosdata/driver-go/v3/common" - stmtCommon "github.com/taosdata/driver-go/v3/common/stmt" - "github.com/taosdata/driver-go/v3/types" + "github.com/taosdata/taosadapter/v3/driver/common" + stmtCommon "github.com/taosdata/taosadapter/v3/driver/common/stmt" + "github.com/taosdata/taosadapter/v3/driver/types" ) func Test_stmtParseColumn(t *testing.T) { diff --git a/controller/ws/stmt/stmt.go b/controller/ws/stmt/stmt.go index 201dbc45..2df4857e 100644 --- a/controller/ws/stmt/stmt.go +++ b/controller/ws/stmt/stmt.go @@ -14,17 +14,17 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" - "github.com/taosdata/driver-go/v3/common/parser" - stmtCommon "github.com/taosdata/driver-go/v3/common/stmt" - tErrors "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/types" - "github.com/taosdata/driver-go/v3/wrapper" - "github.com/taosdata/driver-go/v3/wrapper/cgo" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/controller" "github.com/taosdata/taosadapter/v3/controller/ws/wstool" "github.com/taosdata/taosadapter/v3/db/syncinterface" "github.com/taosdata/taosadapter/v3/db/tool" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + stmtCommon "github.com/taosdata/taosadapter/v3/driver/common/stmt" + tErrors "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/types" + "github.com/taosdata/taosadapter/v3/driver/wrapper" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" "github.com/taosdata/taosadapter/v3/httperror" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/tools" diff --git a/controller/ws/tmq/tmq.go b/controller/ws/tmq/tmq.go index beba3711..cdbcb18e 100644 --- a/controller/ws/tmq/tmq.go +++ b/controller/ws/tmq/tmq.go @@ -13,11 +13,6 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" - "github.com/taosdata/driver-go/v3/common" - "github.com/taosdata/driver-go/v3/common/parser" - taoserrors "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" - "github.com/taosdata/driver-go/v3/wrapper/cgo" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/controller" "github.com/taosdata/taosadapter/v3/controller/ws/wstool" @@ -25,6 +20,11 @@ import ( "github.com/taosdata/taosadapter/v3/db/asynctmq/tmqhandle" "github.com/taosdata/taosadapter/v3/db/syncinterface" "github.com/taosdata/taosadapter/v3/db/tool" + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + taoserrors "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" "github.com/taosdata/taosadapter/v3/httperror" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/thread" diff --git a/controller/ws/tmq/tmq_test.go b/controller/ws/tmq/tmq_test.go index 4e50128b..69fabf70 100644 --- a/controller/ws/tmq/tmq_test.go +++ b/controller/ws/tmq/tmq_test.go @@ -22,14 +22,14 @@ import ( jsoniter "github.com/json-iterator/go" "github.com/spf13/viper" "github.com/stretchr/testify/assert" - "github.com/taosdata/driver-go/v3/common/parser" - "github.com/taosdata/driver-go/v3/common/tmq" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/controller" _ "github.com/taosdata/taosadapter/v3/controller/rest" "github.com/taosdata/taosadapter/v3/controller/ws/query" "github.com/taosdata/taosadapter/v3/controller/ws/wstool" "github.com/taosdata/taosadapter/v3/db" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + "github.com/taosdata/taosadapter/v3/driver/common/tmq" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/tools/parseblock" ) diff --git a/controller/ws/ws/fetch.go b/controller/ws/ws/fetch.go index 9f52a0cf..dd63301a 100644 --- a/controller/ws/ws/fetch.go +++ b/controller/ws/ws/fetch.go @@ -5,10 +5,10 @@ import ( "encoding/binary" "github.com/sirupsen/logrus" - "github.com/taosdata/driver-go/v3/common/parser" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/controller/ws/wstool" "github.com/taosdata/taosadapter/v3/db/async" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/tools/bytesutil" "github.com/taosdata/taosadapter/v3/tools/melody" diff --git a/controller/ws/ws/handler.go b/controller/ws/ws/handler.go index ad319761..4e758b08 100644 --- a/controller/ws/ws/handler.go +++ b/controller/ws/ws/handler.go @@ -11,11 +11,11 @@ import ( jsoniter "github.com/json-iterator/go" "github.com/sirupsen/logrus" - "github.com/taosdata/driver-go/v3/wrapper/cgo" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/controller/ws/wstool" "github.com/taosdata/taosadapter/v3/db/syncinterface" "github.com/taosdata/taosadapter/v3/db/tool" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/tools" "github.com/taosdata/taosadapter/v3/tools/iptool" diff --git a/controller/ws/ws/misc.go b/controller/ws/ws/misc.go index 98f78322..da4e6342 100644 --- a/controller/ws/ws/misc.go +++ b/controller/ws/ws/misc.go @@ -4,9 +4,9 @@ import ( "context" "github.com/sirupsen/logrus" - errors2 "github.com/taosdata/driver-go/v3/errors" "github.com/taosdata/taosadapter/v3/controller/ws/wstool" "github.com/taosdata/taosadapter/v3/db/syncinterface" + errors2 "github.com/taosdata/taosadapter/v3/driver/errors" "github.com/taosdata/taosadapter/v3/tools/melody" ) diff --git a/controller/ws/ws/query.go b/controller/ws/ws/query.go index 08256725..e0679d74 100644 --- a/controller/ws/ws/query.go +++ b/controller/ws/ws/query.go @@ -7,13 +7,13 @@ import ( "fmt" "github.com/sirupsen/logrus" - "github.com/taosdata/driver-go/v3/common" - errors2 "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/controller/ws/wstool" "github.com/taosdata/taosadapter/v3/db/async" "github.com/taosdata/taosadapter/v3/db/syncinterface" "github.com/taosdata/taosadapter/v3/db/tool" + "github.com/taosdata/taosadapter/v3/driver/common" + errors2 "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/monitor" "github.com/taosdata/taosadapter/v3/tools/bytesutil" diff --git a/controller/ws/ws/query_result.go b/controller/ws/ws/query_result.go index 27028489..9a1a8d24 100644 --- a/controller/ws/ws/query_result.go +++ b/controller/ws/ws/query_result.go @@ -8,10 +8,10 @@ import ( "unsafe" "github.com/sirupsen/logrus" - "github.com/taosdata/driver-go/v3/wrapper" - "github.com/taosdata/driver-go/v3/wrapper/cgo" "github.com/taosdata/taosadapter/v3/db/async" "github.com/taosdata/taosadapter/v3/db/syncinterface" + "github.com/taosdata/taosadapter/v3/driver/wrapper" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" "github.com/taosdata/taosadapter/v3/log" ) diff --git a/controller/ws/ws/query_test.go b/controller/ws/ws/query_test.go index 825ca8d4..93c58bb4 100644 --- a/controller/ws/ws/query_test.go +++ b/controller/ws/ws/query_test.go @@ -14,8 +14,8 @@ import ( "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" - "github.com/taosdata/driver-go/v3/common/parser" "github.com/taosdata/taosadapter/v3/controller/ws/wstool" + "github.com/taosdata/taosadapter/v3/driver/common/parser" "github.com/taosdata/taosadapter/v3/tools/parseblock" ) diff --git a/controller/ws/ws/raw.go b/controller/ws/ws/raw.go index 89111c02..a2045196 100644 --- a/controller/ws/ws/raw.go +++ b/controller/ws/ws/raw.go @@ -5,9 +5,9 @@ import ( "unsafe" "github.com/sirupsen/logrus" - "github.com/taosdata/driver-go/v3/common/parser" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/db/syncinterface" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/tools" "github.com/taosdata/taosadapter/v3/tools/melody" ) diff --git a/controller/ws/ws/schemaless.go b/controller/ws/ws/schemaless.go index 093464b4..4c3f9090 100644 --- a/controller/ws/ws/schemaless.go +++ b/controller/ws/ws/schemaless.go @@ -4,9 +4,9 @@ import ( "context" "github.com/sirupsen/logrus" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/controller/ws/wstool" "github.com/taosdata/taosadapter/v3/db/syncinterface" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/tools/melody" ) diff --git a/controller/ws/ws/schemaless_test.go b/controller/ws/ws/schemaless_test.go index e925a2d6..e6c04a2c 100644 --- a/controller/ws/ws/schemaless_test.go +++ b/controller/ws/ws/schemaless_test.go @@ -8,7 +8,7 @@ import ( "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" - "github.com/taosdata/driver-go/v3/ws/schemaless" + "github.com/taosdata/taosadapter/v3/driver/wrapper" ) func TestWsSchemaless(t *testing.T) { @@ -43,7 +43,7 @@ func TestWsSchemaless(t *testing.T) { }{ { name: "influxdb", - protocol: schemaless.InfluxDBLineProtocol, + protocol: wrapper.InfluxDBLineProtocol, precision: "ms", data: "measurement,host=host1 field1=2i,field2=2.0 1577837300000\n" + "measurement,host=host1 field1=2i,field2=2.0 1577837400000\n" + @@ -55,7 +55,7 @@ func TestWsSchemaless(t *testing.T) { }, { name: "opentsdb_telnet", - protocol: schemaless.OpenTSDBTelnetLineProtocol, + protocol: wrapper.OpenTSDBTelnetLineProtocol, precision: "ms", data: "meters.current 1648432611249 10.3 location=California.SanFrancisco group=2\n" + "meters.current 1648432611250 12.6 location=California.SanFrancisco group=2\n" + @@ -71,7 +71,7 @@ func TestWsSchemaless(t *testing.T) { }, { name: "opentsdb_json", - protocol: schemaless.OpenTSDBJsonFormatProtocol, + protocol: wrapper.OpenTSDBJsonFormatProtocol, precision: "ms", data: `[ { @@ -116,7 +116,7 @@ func TestWsSchemaless(t *testing.T) { }, { name: "influxdb_tbnamekey", - protocol: schemaless.InfluxDBLineProtocol, + protocol: wrapper.InfluxDBLineProtocol, precision: "ms", data: "measurement,host=host1 field1=2i,field2=2.0 1577837300000\n" + "measurement,host=host1 field1=2i,field2=2.0 1577837400000\n" + @@ -158,7 +158,7 @@ func TestWsSchemaless(t *testing.T) { assert.NoError(t, err, string(resp)) assert.Equal(t, reqID, schemalessResp.ReqID) assert.Equal(t, 0, schemalessResp.Code, schemalessResp.Message) - if c.protocol != schemaless.OpenTSDBJsonFormatProtocol { + if c.protocol != wrapper.OpenTSDBJsonFormatProtocol { assert.Equal(t, c.totalRows, schemalessResp.TotalRows) } assert.Equal(t, c.affectedRows, schemalessResp.AffectedRows) @@ -203,7 +203,7 @@ func TestWsSchemalessError(t *testing.T) { }, { name: "wrong timestamp", - protocol: schemaless.InfluxDBLineProtocol, + protocol: wrapper.InfluxDBLineProtocol, precision: "ms", data: "measurement,host=host1 field1=2i,field2=2.0 10", }, diff --git a/controller/ws/ws/stmt.go b/controller/ws/ws/stmt.go index 7387bb71..58488c74 100644 --- a/controller/ws/ws/stmt.go +++ b/controller/ws/ws/stmt.go @@ -8,15 +8,15 @@ import ( "unsafe" "github.com/sirupsen/logrus" - "github.com/taosdata/driver-go/v3/common" - "github.com/taosdata/driver-go/v3/common/parser" - stmtCommon "github.com/taosdata/driver-go/v3/common/stmt" - errors2 "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/types" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/controller/ws/stmt" "github.com/taosdata/taosadapter/v3/controller/ws/wstool" "github.com/taosdata/taosadapter/v3/db/syncinterface" + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + stmtCommon "github.com/taosdata/taosadapter/v3/driver/common/stmt" + errors2 "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/types" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/tools" "github.com/taosdata/taosadapter/v3/tools/jsontype" diff --git a/controller/ws/ws/stmt2.go b/controller/ws/ws/stmt2.go index b06ff7b3..f5648547 100644 --- a/controller/ws/ws/stmt2.go +++ b/controller/ws/ws/stmt2.go @@ -8,12 +8,12 @@ import ( "unsafe" "github.com/sirupsen/logrus" - stmtCommon "github.com/taosdata/driver-go/v3/common/stmt" - errors2 "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/controller/ws/wstool" "github.com/taosdata/taosadapter/v3/db/async" "github.com/taosdata/taosadapter/v3/db/syncinterface" + stmtCommon "github.com/taosdata/taosadapter/v3/driver/common/stmt" + errors2 "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/tools/jsontype" "github.com/taosdata/taosadapter/v3/tools/melody" diff --git a/controller/ws/ws/stmt2_test.go b/controller/ws/ws/stmt2_test.go index e018ae3c..cd83fde6 100644 --- a/controller/ws/ws/stmt2_test.go +++ b/controller/ws/ws/stmt2_test.go @@ -13,9 +13,9 @@ import ( "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" - "github.com/taosdata/driver-go/v3/common" - stmtCommon "github.com/taosdata/driver-go/v3/common/stmt" "github.com/taosdata/taosadapter/v3/controller/ws/wstool" + "github.com/taosdata/taosadapter/v3/driver/common" + stmtCommon "github.com/taosdata/taosadapter/v3/driver/common/stmt" "github.com/taosdata/taosadapter/v3/tools/parseblock" ) diff --git a/controller/ws/ws/stmt_test.go b/controller/ws/ws/stmt_test.go index 8c752814..fcef5483 100644 --- a/controller/ws/ws/stmt_test.go +++ b/controller/ws/ws/stmt_test.go @@ -14,12 +14,12 @@ import ( "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" - "github.com/taosdata/driver-go/v3/common" - "github.com/taosdata/driver-go/v3/common/param" - "github.com/taosdata/driver-go/v3/common/serializer" - stmtCommon "github.com/taosdata/driver-go/v3/common/stmt" - "github.com/taosdata/driver-go/v3/types" "github.com/taosdata/taosadapter/v3/controller/ws/wstool" + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/common/param" + "github.com/taosdata/taosadapter/v3/driver/common/serializer" + stmtCommon "github.com/taosdata/taosadapter/v3/driver/common/stmt" + "github.com/taosdata/taosadapter/v3/driver/types" "github.com/taosdata/taosadapter/v3/tools/generator" "github.com/taosdata/taosadapter/v3/tools/parseblock" ) diff --git a/controller/ws/wstool/error.go b/controller/ws/wstool/error.go index 5551d126..7768bfd7 100644 --- a/controller/ws/wstool/error.go +++ b/controller/ws/wstool/error.go @@ -4,7 +4,7 @@ import ( "context" "github.com/sirupsen/logrus" - tErrors "github.com/taosdata/driver-go/v3/errors" + tErrors "github.com/taosdata/taosadapter/v3/driver/errors" "github.com/taosdata/taosadapter/v3/tools/melody" ) diff --git a/controller/ws/wstool/error_test.go b/controller/ws/wstool/error_test.go index fd85f964..88b6f63d 100644 --- a/controller/ws/wstool/error_test.go +++ b/controller/ws/wstool/error_test.go @@ -12,7 +12,7 @@ import ( "github.com/gin-gonic/gin" "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" - tErrors "github.com/taosdata/driver-go/v3/errors" + tErrors "github.com/taosdata/taosadapter/v3/driver/errors" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/tools/melody" ) diff --git a/db/async/handlerpool.go b/db/async/handlerpool.go index 79b37877..3ba5e540 100644 --- a/db/async/handlerpool.go +++ b/db/async/handlerpool.go @@ -5,7 +5,7 @@ import ( "sync" "unsafe" - "github.com/taosdata/driver-go/v3/wrapper/cgo" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" ) type Result struct { diff --git a/db/async/row.go b/db/async/row.go index c012c2fa..dc3c77ce 100644 --- a/db/async/row.go +++ b/db/async/row.go @@ -8,9 +8,9 @@ import ( "unsafe" "github.com/sirupsen/logrus" - tErrors "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/config" + tErrors "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/httperror" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/thread" diff --git a/db/async/row_test.go b/db/async/row_test.go index 548a2864..2b7572d9 100644 --- a/db/async/row_test.go +++ b/db/async/row_test.go @@ -6,8 +6,8 @@ import ( "unsafe" "github.com/sirupsen/logrus" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/config" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/log" ) diff --git a/db/async/stmt2pool.go b/db/async/stmt2pool.go index 5e863f07..91630ca9 100644 --- a/db/async/stmt2pool.go +++ b/db/async/stmt2pool.go @@ -3,7 +3,7 @@ package async import ( "unsafe" - "github.com/taosdata/driver-go/v3/wrapper/cgo" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" ) type Stmt2Result struct { diff --git a/db/asynctmq/tmq.go b/db/asynctmq/tmq.go index 4582c849..5687f8b9 100644 --- a/db/asynctmq/tmq.go +++ b/db/asynctmq/tmq.go @@ -643,7 +643,7 @@ import "C" import ( "unsafe" - "github.com/taosdata/driver-go/v3/wrapper/cgo" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" ) // InitTMQThread tmq_thread *init_tmq_thread() diff --git a/db/asynctmq/tmq_windows.go b/db/asynctmq/tmq_windows.go index 71c0b65c..eb4331f4 100644 --- a/db/asynctmq/tmq_windows.go +++ b/db/asynctmq/tmq_windows.go @@ -656,7 +656,7 @@ import "C" import ( "unsafe" - "github.com/taosdata/driver-go/v3/wrapper/cgo" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" ) // InitTMQThread tmq_thread *init_tmq_thread() diff --git a/db/asynctmq/tmqcb.go b/db/asynctmq/tmqcb.go index efbc071f..67e134ae 100644 --- a/db/asynctmq/tmqcb.go +++ b/db/asynctmq/tmqcb.go @@ -10,8 +10,8 @@ import "C" import ( "unsafe" - "github.com/taosdata/driver-go/v3/wrapper/cgo" "github.com/taosdata/taosadapter/v3/db/asynctmq/tmqhandle" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" ) //export AdapterTMQPollCallback diff --git a/db/asynctmq/tmqhandle/handler.go b/db/asynctmq/tmqhandle/handler.go index b915a723..4927e0b1 100644 --- a/db/asynctmq/tmqhandle/handler.go +++ b/db/asynctmq/tmqhandle/handler.go @@ -6,7 +6,7 @@ import ( "sync" "unsafe" - "github.com/taosdata/driver-go/v3/wrapper/cgo" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" ) type FetchRawBlockResult struct { diff --git a/db/commonpool/pool.go b/db/commonpool/pool.go index 499f24af..9af5d545 100644 --- a/db/commonpool/pool.go +++ b/db/commonpool/pool.go @@ -11,12 +11,12 @@ import ( "unsafe" "github.com/sirupsen/logrus" - tErrors "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" - "github.com/taosdata/driver-go/v3/wrapper/cgo" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/db/syncinterface" "github.com/taosdata/taosadapter/v3/db/tool" + tErrors "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" "github.com/taosdata/taosadapter/v3/httperror" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/tools/connectpool" diff --git a/db/commonpool/pool_test.go b/db/commonpool/pool_test.go index 4d7b4e03..838415c4 100644 --- a/db/commonpool/pool_test.go +++ b/db/commonpool/pool_test.go @@ -8,8 +8,8 @@ import ( "unsafe" "github.com/stretchr/testify/assert" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/config" + "github.com/taosdata/taosadapter/v3/driver/wrapper" ) func TestMain(m *testing.M) { diff --git a/db/init.go b/db/init.go index b8600039..8d8638ff 100644 --- a/db/init.go +++ b/db/init.go @@ -3,11 +3,11 @@ package db import ( "sync" - "github.com/taosdata/driver-go/v3/common" - "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/db/async" + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/log" ) diff --git a/db/syncinterface/wrapper.go b/db/syncinterface/wrapper.go index 96e57c65..00b508e1 100644 --- a/db/syncinterface/wrapper.go +++ b/db/syncinterface/wrapper.go @@ -6,9 +6,9 @@ import ( "unsafe" "github.com/sirupsen/logrus" - "github.com/taosdata/driver-go/v3/types" - "github.com/taosdata/driver-go/v3/wrapper" - "github.com/taosdata/driver-go/v3/wrapper/cgo" + "github.com/taosdata/taosadapter/v3/driver/types" + "github.com/taosdata/taosadapter/v3/driver/wrapper" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/thread" ) diff --git a/db/syncinterface/wrapper_test.go b/db/syncinterface/wrapper_test.go index a22ca7e0..25852e27 100644 --- a/db/syncinterface/wrapper_test.go +++ b/db/syncinterface/wrapper_test.go @@ -7,15 +7,15 @@ import ( "unsafe" "github.com/stretchr/testify/assert" - "github.com/taosdata/driver-go/v3/common" - "github.com/taosdata/driver-go/v3/common/parser" - stmtCommon "github.com/taosdata/driver-go/v3/common/stmt" - taoserrors "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/types" - "github.com/taosdata/driver-go/v3/wrapper" - "github.com/taosdata/driver-go/v3/wrapper/cgo" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/db" + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + stmtCommon "github.com/taosdata/taosadapter/v3/driver/common/stmt" + taoserrors "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/types" + "github.com/taosdata/taosadapter/v3/driver/wrapper" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/tools/generator" ) diff --git a/db/tool/createdb.go b/db/tool/createdb.go index 4391f9ac..4ddf277c 100644 --- a/db/tool/createdb.go +++ b/db/tool/createdb.go @@ -4,11 +4,11 @@ import ( "unsafe" "github.com/sirupsen/logrus" - "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/db/async" "github.com/taosdata/taosadapter/v3/db/syncinterface" + "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/httperror" "github.com/taosdata/taosadapter/v3/tools/pool" ) diff --git a/db/tool/createdb_test.go b/db/tool/createdb_test.go index f2465768..5214652a 100644 --- a/db/tool/createdb_test.go +++ b/db/tool/createdb_test.go @@ -7,10 +7,10 @@ import ( "github.com/spf13/viper" "github.com/stretchr/testify/assert" - tErrors "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/db" + tErrors "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/log" ) diff --git a/db/tool/notify.go b/db/tool/notify.go index cf5dd8f5..65dc224f 100644 --- a/db/tool/notify.go +++ b/db/tool/notify.go @@ -5,10 +5,10 @@ import ( "strings" "unsafe" - "github.com/taosdata/driver-go/v3/common" - "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" - "github.com/taosdata/driver-go/v3/wrapper/cgo" + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" "github.com/taosdata/taosadapter/v3/thread" ) diff --git a/db/tool/notify_test.go b/db/tool/notify_test.go index 6c9a8a17..c1df3fbc 100644 --- a/db/tool/notify_test.go +++ b/db/tool/notify_test.go @@ -8,9 +8,9 @@ import ( "unsafe" "github.com/stretchr/testify/assert" - tErrors "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" - "github.com/taosdata/driver-go/v3/wrapper/cgo" + tErrors "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" ) func TestWhiteListHandle(t *testing.T) { diff --git a/driver/common/change.go b/driver/common/change.go new file mode 100644 index 00000000..d287162e --- /dev/null +++ b/driver/common/change.go @@ -0,0 +1,34 @@ +package common + +import ( + "fmt" + "time" +) + +func TimestampConvertToTime(timestamp int64, precision int) time.Time { + switch precision { + case PrecisionMilliSecond: // milli-second + return time.Unix(0, timestamp*1e6) + case PrecisionMicroSecond: // micro-second + return time.Unix(0, timestamp*1e3) + case PrecisionNanoSecond: // nano-second + return time.Unix(0, timestamp) + default: + s := fmt.Sprintln("unknown precision", precision, "timestamp", timestamp) + panic(s) + } +} + +func TimeToTimestamp(t time.Time, precision int) (timestamp int64) { + switch precision { + case PrecisionMilliSecond: + return t.UnixNano() / 1e6 + case PrecisionMicroSecond: + return t.UnixNano() / 1e3 + case PrecisionNanoSecond: + return t.UnixNano() + default: + s := fmt.Sprintln("unknown precision", precision, "time", t) + panic(s) + } +} diff --git a/driver/common/change_test.go b/driver/common/change_test.go new file mode 100644 index 00000000..6c5c727d --- /dev/null +++ b/driver/common/change_test.go @@ -0,0 +1,101 @@ +package common + +import ( + "reflect" + "testing" + "time" +) + +// @author: xftan +// @date: 2022/1/25 16:55 +// @description: test timestamp with precision convert to time.Time +func TestTimestampConvertToTime(t *testing.T) { + type args struct { + timestamp int64 + precision int + } + tests := []struct { + name string + args args + want time.Time + }{ + { + name: "ms", + args: args{ + timestamp: 1643068800000, + precision: PrecisionMilliSecond, + }, + want: time.Date(2022, 01, 25, 0, 0, 0, 0, time.UTC), + }, + { + name: "us", + args: args{ + timestamp: 1643068800000000, + precision: PrecisionMicroSecond, + }, + want: time.Date(2022, 01, 25, 0, 0, 0, 0, time.UTC), + }, + { + name: "ns", + args: args{ + timestamp: 1643068800000000000, + precision: PrecisionNanoSecond, + }, + want: time.Date(2022, 01, 25, 0, 0, 0, 0, time.UTC), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := TimestampConvertToTime(tt.args.timestamp, tt.args.precision); !reflect.DeepEqual(got.UTC(), tt.want.UTC()) { + t.Errorf("TimestampConvertToTime() = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/25 16:56 +// @description: test time.Time with precision convert to timestamp +func TestTimeToTimestamp(t *testing.T) { + type args struct { + t time.Time + precision int + } + tests := []struct { + name string + args args + wantTimestamp int64 + }{ + { + name: "ms", + args: args{ + t: time.Date(2022, 01, 25, 0, 0, 0, 0, time.UTC), + precision: PrecisionMilliSecond, + }, + wantTimestamp: 1643068800000, + }, + { + name: "us", + args: args{ + t: time.Date(2022, 01, 25, 0, 0, 0, 0, time.UTC), + precision: PrecisionMicroSecond, + }, + wantTimestamp: 1643068800000000, + }, + { + name: "ns", + args: args{ + t: time.Date(2022, 01, 25, 0, 0, 0, 0, time.UTC), + precision: PrecisionNanoSecond, + }, + wantTimestamp: 1643068800000000000, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotTimestamp := TimeToTimestamp(tt.args.t, tt.args.precision); gotTimestamp != tt.wantTimestamp { + t.Errorf("TimeToTimestamp() = %v, want %v", gotTimestamp, tt.wantTimestamp) + } + }) + } +} diff --git a/driver/common/column.go b/driver/common/column.go new file mode 100644 index 00000000..a092fb6d --- /dev/null +++ b/driver/common/column.go @@ -0,0 +1,46 @@ +package common + +import ( + "reflect" + + "github.com/taosdata/taosadapter/v3/driver/types" +) + +var ( + NullInt8 = reflect.TypeOf(types.NullInt8{}) + NullInt16 = reflect.TypeOf(types.NullInt16{}) + NullInt32 = reflect.TypeOf(types.NullInt32{}) + NullInt64 = reflect.TypeOf(types.NullInt64{}) + NullUInt8 = reflect.TypeOf(types.NullUInt8{}) + NullUInt16 = reflect.TypeOf(types.NullUInt16{}) + NullUInt32 = reflect.TypeOf(types.NullUInt32{}) + NullUInt64 = reflect.TypeOf(types.NullUInt64{}) + NullFloat32 = reflect.TypeOf(types.NullFloat32{}) + NullFloat64 = reflect.TypeOf(types.NullFloat64{}) + NullTime = reflect.TypeOf(types.NullTime{}) + NullBool = reflect.TypeOf(types.NullBool{}) + NullString = reflect.TypeOf(types.NullString{}) + Bytes = reflect.TypeOf([]byte{}) + NullJson = reflect.TypeOf(types.NullJson{}) + UnknownType = reflect.TypeOf(new(interface{})).Elem() +) + +var ColumnTypeMap = map[int]reflect.Type{ + TSDB_DATA_TYPE_BOOL: NullBool, + TSDB_DATA_TYPE_TINYINT: NullInt8, + TSDB_DATA_TYPE_SMALLINT: NullInt16, + TSDB_DATA_TYPE_INT: NullInt32, + TSDB_DATA_TYPE_BIGINT: NullInt64, + TSDB_DATA_TYPE_UTINYINT: NullUInt8, + TSDB_DATA_TYPE_USMALLINT: NullUInt16, + TSDB_DATA_TYPE_UINT: NullUInt32, + TSDB_DATA_TYPE_UBIGINT: NullUInt64, + TSDB_DATA_TYPE_FLOAT: NullFloat32, + TSDB_DATA_TYPE_DOUBLE: NullFloat64, + TSDB_DATA_TYPE_BINARY: NullString, + TSDB_DATA_TYPE_NCHAR: NullString, + TSDB_DATA_TYPE_TIMESTAMP: NullTime, + TSDB_DATA_TYPE_JSON: NullJson, + TSDB_DATA_TYPE_VARBINARY: Bytes, + TSDB_DATA_TYPE_GEOMETRY: Bytes, +} diff --git a/driver/common/const.go b/driver/common/const.go new file mode 100644 index 00000000..49efee48 --- /dev/null +++ b/driver/common/const.go @@ -0,0 +1,73 @@ +package common + +import "unsafe" + +//revive:disable +const ( + MaxTaosSqlLen = 1048576 + DefaultUser = "root" + DefaultPassword = "taosdata" +) + +const ( + PrecisionMilliSecond = 0 + PrecisionMicroSecond = 1 + PrecisionNanoSecond = 2 +) + +const ( + TSDB_OPTION_LOCALE = iota + TSDB_OPTION_CHARSET + TSDB_OPTION_TIMEZONE + TSDB_OPTION_CONFIGDIR + TSDB_OPTION_SHELL_ACTIVITY_TIMER + TSDB_OPTION_USE_ADAPTER +) + +const ( + TMQ_RES_INVALID = -1 + TMQ_RES_DATA = 1 + TMQ_RES_TABLE_META = 2 + TMQ_RES_METADATA = 3 +) + +var TypeLengthMap = map[int]int{ + TSDB_DATA_TYPE_NULL: 1, + TSDB_DATA_TYPE_BOOL: 1, + TSDB_DATA_TYPE_TINYINT: 1, + TSDB_DATA_TYPE_SMALLINT: 2, + TSDB_DATA_TYPE_INT: 4, + TSDB_DATA_TYPE_BIGINT: 8, + TSDB_DATA_TYPE_FLOAT: 4, + TSDB_DATA_TYPE_DOUBLE: 8, + TSDB_DATA_TYPE_TIMESTAMP: 8, + TSDB_DATA_TYPE_UTINYINT: 1, + TSDB_DATA_TYPE_USMALLINT: 2, + TSDB_DATA_TYPE_UINT: 4, + TSDB_DATA_TYPE_UBIGINT: 8, +} + +const ( + Int8Size = unsafe.Sizeof(int8(0)) + Int16Size = unsafe.Sizeof(int16(0)) + Int32Size = unsafe.Sizeof(int32(0)) + Int64Size = unsafe.Sizeof(int64(0)) + UInt8Size = unsafe.Sizeof(uint8(0)) + UInt16Size = unsafe.Sizeof(uint16(0)) + UInt32Size = unsafe.Sizeof(uint32(0)) + UInt64Size = unsafe.Sizeof(uint64(0)) + Float32Size = unsafe.Sizeof(float32(0)) + Float64Size = unsafe.Sizeof(float64(0)) +) + +const ReqIDKey = "taos_req_id" + +const ( + TAOS_NOTIFY_PASSVER = 0 + TAOS_NOTIFY_WHITELIST_VER = 1 + TAOS_NOTIFY_USER_DROPPED = 2 +) + +const ( + TAOS_CONN_MODE_BI = 0 +) diff --git a/driver/common/datatype.go b/driver/common/datatype.go new file mode 100644 index 00000000..cf5a3379 --- /dev/null +++ b/driver/common/datatype.go @@ -0,0 +1,89 @@ +package common + +//revive:disable +const ( + TSDB_DATA_TYPE_NULL = 0 // 1 bytes + TSDB_DATA_TYPE_BOOL = 1 // 1 bytes + TSDB_DATA_TYPE_TINYINT = 2 // 1 byte + TSDB_DATA_TYPE_SMALLINT = 3 // 2 bytes + TSDB_DATA_TYPE_INT = 4 // 4 bytes + TSDB_DATA_TYPE_BIGINT = 5 // 8 bytes + TSDB_DATA_TYPE_FLOAT = 6 // 4 bytes + TSDB_DATA_TYPE_DOUBLE = 7 // 8 bytes + TSDB_DATA_TYPE_BINARY = 8 // string + TSDB_DATA_TYPE_TIMESTAMP = 9 // 8 bytes + TSDB_DATA_TYPE_NCHAR = 10 // unicode string + TSDB_DATA_TYPE_UTINYINT = 11 // 1 byte + TSDB_DATA_TYPE_USMALLINT = 12 // 2 bytes + TSDB_DATA_TYPE_UINT = 13 // 4 bytes + TSDB_DATA_TYPE_UBIGINT = 14 // 8 bytes + TSDB_DATA_TYPE_JSON = 15 + TSDB_DATA_TYPE_VARBINARY = 16 + TSDB_DATA_TYPE_DECIMAL = 17 + TSDB_DATA_TYPE_BLOB = 18 + TSDB_DATA_TYPE_MEDIUMBLOB = 19 + TSDB_DATA_TYPE_GEOMETRY = 20 +) + +const ( + TSDB_DATA_TYPE_NULL_Str = "NULL" + TSDB_DATA_TYPE_BOOL_Str = "BOOL" + TSDB_DATA_TYPE_TINYINT_Str = "TINYINT" + TSDB_DATA_TYPE_SMALLINT_Str = "SMALLINT" + TSDB_DATA_TYPE_INT_Str = "INT" + TSDB_DATA_TYPE_BIGINT_Str = "BIGINT" + TSDB_DATA_TYPE_FLOAT_Str = "FLOAT" + TSDB_DATA_TYPE_DOUBLE_Str = "DOUBLE" + TSDB_DATA_TYPE_BINARY_Str = "VARCHAR" + TSDB_DATA_TYPE_TIMESTAMP_Str = "TIMESTAMP" + TSDB_DATA_TYPE_NCHAR_Str = "NCHAR" + TSDB_DATA_TYPE_UTINYINT_Str = "TINYINT UNSIGNED" + TSDB_DATA_TYPE_USMALLINT_Str = "SMALLINT UNSIGNED" + TSDB_DATA_TYPE_UINT_Str = "INT UNSIGNED" + TSDB_DATA_TYPE_UBIGINT_Str = "BIGINT UNSIGNED" + TSDB_DATA_TYPE_JSON_Str = "JSON" + TSDB_DATA_TYPE_VARBINARY_Str = "VARBINARY" + TSDB_DATA_TYPE_GEOMETRY_Str = "GEOMETRY" +) + +var TypeNameMap = map[int]string{ + TSDB_DATA_TYPE_NULL: TSDB_DATA_TYPE_NULL_Str, + TSDB_DATA_TYPE_BOOL: TSDB_DATA_TYPE_BOOL_Str, + TSDB_DATA_TYPE_TINYINT: TSDB_DATA_TYPE_TINYINT_Str, + TSDB_DATA_TYPE_SMALLINT: TSDB_DATA_TYPE_SMALLINT_Str, + TSDB_DATA_TYPE_INT: TSDB_DATA_TYPE_INT_Str, + TSDB_DATA_TYPE_BIGINT: TSDB_DATA_TYPE_BIGINT_Str, + TSDB_DATA_TYPE_FLOAT: TSDB_DATA_TYPE_FLOAT_Str, + TSDB_DATA_TYPE_DOUBLE: TSDB_DATA_TYPE_DOUBLE_Str, + TSDB_DATA_TYPE_BINARY: TSDB_DATA_TYPE_BINARY_Str, + TSDB_DATA_TYPE_TIMESTAMP: TSDB_DATA_TYPE_TIMESTAMP_Str, + TSDB_DATA_TYPE_NCHAR: TSDB_DATA_TYPE_NCHAR_Str, + TSDB_DATA_TYPE_UTINYINT: TSDB_DATA_TYPE_UTINYINT_Str, + TSDB_DATA_TYPE_USMALLINT: TSDB_DATA_TYPE_USMALLINT_Str, + TSDB_DATA_TYPE_UINT: TSDB_DATA_TYPE_UINT_Str, + TSDB_DATA_TYPE_UBIGINT: TSDB_DATA_TYPE_UBIGINT_Str, + TSDB_DATA_TYPE_JSON: TSDB_DATA_TYPE_JSON_Str, + TSDB_DATA_TYPE_VARBINARY: TSDB_DATA_TYPE_VARBINARY_Str, + TSDB_DATA_TYPE_GEOMETRY: TSDB_DATA_TYPE_GEOMETRY_Str, +} + +var NameTypeMap = map[string]int{ + TSDB_DATA_TYPE_NULL_Str: TSDB_DATA_TYPE_NULL, + TSDB_DATA_TYPE_BOOL_Str: TSDB_DATA_TYPE_BOOL, + TSDB_DATA_TYPE_TINYINT_Str: TSDB_DATA_TYPE_TINYINT, + TSDB_DATA_TYPE_SMALLINT_Str: TSDB_DATA_TYPE_SMALLINT, + TSDB_DATA_TYPE_INT_Str: TSDB_DATA_TYPE_INT, + TSDB_DATA_TYPE_BIGINT_Str: TSDB_DATA_TYPE_BIGINT, + TSDB_DATA_TYPE_FLOAT_Str: TSDB_DATA_TYPE_FLOAT, + TSDB_DATA_TYPE_DOUBLE_Str: TSDB_DATA_TYPE_DOUBLE, + TSDB_DATA_TYPE_BINARY_Str: TSDB_DATA_TYPE_BINARY, + TSDB_DATA_TYPE_TIMESTAMP_Str: TSDB_DATA_TYPE_TIMESTAMP, + TSDB_DATA_TYPE_NCHAR_Str: TSDB_DATA_TYPE_NCHAR, + TSDB_DATA_TYPE_UTINYINT_Str: TSDB_DATA_TYPE_UTINYINT, + TSDB_DATA_TYPE_USMALLINT_Str: TSDB_DATA_TYPE_USMALLINT, + TSDB_DATA_TYPE_UINT_Str: TSDB_DATA_TYPE_UINT, + TSDB_DATA_TYPE_UBIGINT_Str: TSDB_DATA_TYPE_UBIGINT, + TSDB_DATA_TYPE_JSON_Str: TSDB_DATA_TYPE_JSON, + TSDB_DATA_TYPE_VARBINARY_Str: TSDB_DATA_TYPE_VARBINARY, + TSDB_DATA_TYPE_GEOMETRY_Str: TSDB_DATA_TYPE_GEOMETRY, +} diff --git a/driver/common/param/column.go b/driver/common/param/column.go new file mode 100644 index 00000000..06e47e61 --- /dev/null +++ b/driver/common/param/column.go @@ -0,0 +1,220 @@ +package param + +import ( + "fmt" + + "github.com/taosdata/taosadapter/v3/driver/types" +) + +type ColumnType struct { + size int + value []*types.ColumnType + column int +} + +func NewColumnType(size int) *ColumnType { + return &ColumnType{size: size, value: make([]*types.ColumnType, size)} +} + +func NewColumnTypeWithValue(value []*types.ColumnType) *ColumnType { + return &ColumnType{size: len(value), value: value, column: len(value)} +} + +func (c *ColumnType) AddBool() *ColumnType { + if c.column >= c.size { + return c + } + c.value[c.column] = &types.ColumnType{ + Type: types.TaosBoolType, + } + c.column += 1 + return c +} + +func (c *ColumnType) AddTinyint() *ColumnType { + if c.column >= c.size { + return c + } + c.value[c.column] = &types.ColumnType{ + Type: types.TaosTinyintType, + } + c.column += 1 + return c +} + +func (c *ColumnType) AddSmallint() *ColumnType { + if c.column >= c.size { + return c + } + c.value[c.column] = &types.ColumnType{ + Type: types.TaosSmallintType, + } + c.column += 1 + return c +} + +func (c *ColumnType) AddInt() *ColumnType { + if c.column >= c.size { + return c + } + c.value[c.column] = &types.ColumnType{ + Type: types.TaosIntType, + } + c.column += 1 + return c +} + +func (c *ColumnType) AddBigint() *ColumnType { + if c.column >= c.size { + return c + } + c.value[c.column] = &types.ColumnType{ + Type: types.TaosBigintType, + } + c.column += 1 + return c +} + +func (c *ColumnType) AddUTinyint() *ColumnType { + if c.column >= c.size { + return c + } + c.value[c.column] = &types.ColumnType{ + Type: types.TaosUTinyintType, + } + c.column += 1 + return c +} + +func (c *ColumnType) AddUSmallint() *ColumnType { + if c.column >= c.size { + return c + } + c.value[c.column] = &types.ColumnType{ + Type: types.TaosUSmallintType, + } + c.column += 1 + return c +} + +func (c *ColumnType) AddUInt() *ColumnType { + if c.column >= c.size { + return c + } + c.value[c.column] = &types.ColumnType{ + Type: types.TaosUIntType, + } + c.column += 1 + return c +} + +func (c *ColumnType) AddUBigint() *ColumnType { + if c.column >= c.size { + return c + } + c.value[c.column] = &types.ColumnType{ + Type: types.TaosUBigintType, + } + c.column += 1 + return c +} + +func (c *ColumnType) AddFloat() *ColumnType { + if c.column >= c.size { + return c + } + c.value[c.column] = &types.ColumnType{ + Type: types.TaosFloatType, + } + c.column += 1 + return c +} + +func (c *ColumnType) AddDouble() *ColumnType { + if c.column >= c.size { + return c + } + c.value[c.column] = &types.ColumnType{ + Type: types.TaosDoubleType, + } + c.column += 1 + return c +} + +func (c *ColumnType) AddBinary(strMaxLen int) *ColumnType { + if c.column >= c.size { + return c + } + c.value[c.column] = &types.ColumnType{ + Type: types.TaosBinaryType, + MaxLen: strMaxLen, + } + c.column += 1 + return c +} + +func (c *ColumnType) AddVarBinary(strMaxLen int) *ColumnType { + if c.column >= c.size { + return c + } + c.value[c.column] = &types.ColumnType{ + Type: types.TaosVarBinaryType, + MaxLen: strMaxLen, + } + c.column += 1 + return c +} + +func (c *ColumnType) AddNchar(strMaxLen int) *ColumnType { + if c.column >= c.size { + return c + } + c.value[c.column] = &types.ColumnType{ + Type: types.TaosNcharType, + MaxLen: strMaxLen, + } + c.column += 1 + return c +} + +func (c *ColumnType) AddTimestamp() *ColumnType { + if c.column >= c.size { + return c + } + c.value[c.column] = &types.ColumnType{ + Type: types.TaosTimestampType, + } + c.column += 1 + return c +} + +func (c *ColumnType) AddJson(strMaxLen int) *ColumnType { + if c.column >= c.size { + return c + } + c.value[c.column] = &types.ColumnType{ + Type: types.TaosJsonType, + MaxLen: strMaxLen, + } + c.column += 1 + return c +} + +func (c *ColumnType) AddGeometry(strMaxLen int) *ColumnType { + if c.column >= c.size { + return c + } + c.value[c.column] = &types.ColumnType{ + Type: types.TaosGeometryType, + MaxLen: strMaxLen, + } + c.column += 1 + return c +} + +func (c *ColumnType) GetValue() ([]*types.ColumnType, error) { + if c.size != c.column { + return nil, fmt.Errorf("incomplete column expect %d columns set %d columns", c.size, c.column) + } + return c.value, nil +} diff --git a/driver/common/param/column_test.go b/driver/common/param/column_test.go new file mode 100644 index 00000000..dc179035 --- /dev/null +++ b/driver/common/param/column_test.go @@ -0,0 +1,435 @@ +package param + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/taosdata/taosadapter/v3/driver/types" +) + +func TestColumnType_AddBool(t *testing.T) { + colType := NewColumnType(1) + colType.AddBool() + + expected := []*types.ColumnType{ + { + Type: types.TaosBoolType, + }, + } + + values, err := colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) + + colType.AddBool() + + values, err = colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) +} + +func TestColumnType_AddTinyint(t *testing.T) { + colType := NewColumnType(1) + + colType.AddTinyint() + + expected := []*types.ColumnType{ + { + Type: types.TaosTinyintType, + }, + } + + values, err := colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) + + colType.AddTinyint() + + values, err = colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) +} + +func TestColumnType_AddSmallint(t *testing.T) { + colType := NewColumnType(1) + + colType.AddSmallint() + + expected := []*types.ColumnType{ + { + Type: types.TaosSmallintType, + }, + } + + values, err := colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) + + colType.AddSmallint() + + values, err = colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) +} + +func TestColumnType_AddInt(t *testing.T) { + colType := NewColumnType(1) + + colType.AddInt() + + expected := []*types.ColumnType{ + { + Type: types.TaosIntType, + }, + } + + values, err := colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) + + colType.AddInt() + + values, err = colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) +} + +func TestColumnType_AddBigint(t *testing.T) { + colType := NewColumnType(1) + + colType.AddBigint() + + expected := []*types.ColumnType{ + { + Type: types.TaosBigintType, + }, + } + + values, err := colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) + + colType.AddBigint() + + values, err = colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) +} + +func TestColumnType_AddUTinyint(t *testing.T) { + colType := NewColumnType(1) + + colType.AddUTinyint() + + expected := []*types.ColumnType{ + { + Type: types.TaosUTinyintType, + }, + } + + values, err := colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) + + colType.AddUTinyint() + + values, err = colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) +} + +func TestColumnType_AddUSmallint(t *testing.T) { + colType := NewColumnType(1) + + colType.AddUSmallint() + + expected := []*types.ColumnType{ + { + Type: types.TaosUSmallintType, + }, + } + + values, err := colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) + + colType.AddUSmallint() + + values, err = colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) +} + +func TestColumnType_AddUInt(t *testing.T) { + colType := NewColumnType(1) + + colType.AddUInt() + + expected := []*types.ColumnType{ + { + Type: types.TaosUIntType, + }, + } + + values, err := colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) + + colType.AddUInt() + + values, err = colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) +} + +func TestColumnType_AddUBigint(t *testing.T) { + colType := NewColumnType(1) + + colType.AddUBigint() + + expected := []*types.ColumnType{ + { + Type: types.TaosUBigintType, + }, + } + + values, err := colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) + + colType.AddUBigint() + + values, err = colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) +} + +func TestColumnType_AddFloat(t *testing.T) { + colType := NewColumnType(1) + + colType.AddFloat() + + expected := []*types.ColumnType{ + { + Type: types.TaosFloatType, + }, + } + + values, err := colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) + + colType.AddFloat() + + values, err = colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) +} + +func TestColumnType_AddDouble(t *testing.T) { + colType := NewColumnType(1) + + colType.AddDouble() + + expected := []*types.ColumnType{ + { + Type: types.TaosDoubleType, + }, + } + + values, err := colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) + + colType.AddDouble() + + values, err = colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) +} + +func TestColumnType_AddBinary(t *testing.T) { + colType := NewColumnType(1) + + colType.AddBinary(100) + + expected := []*types.ColumnType{ + { + Type: types.TaosBinaryType, + MaxLen: 100, + }, + } + + values, err := colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) + + colType.AddBinary(50) + + values, err = colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) +} + +func TestColumnType_AddVarBinary(t *testing.T) { + colType := NewColumnType(1) + + colType.AddVarBinary(100) + + expected := []*types.ColumnType{ + { + Type: types.TaosVarBinaryType, + MaxLen: 100, + }, + } + + values, err := colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) + + colType.AddVarBinary(50) + values, err = colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) +} + +func TestColumnType_AddNchar(t *testing.T) { + colType := NewColumnType(1) + + colType.AddNchar(100) + + expected := []*types.ColumnType{ + { + Type: types.TaosNcharType, + MaxLen: 100, + }, + } + + values, err := colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) + + colType.AddNchar(50) + + values, err = colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) +} + +func TestColumnType_AddTimestamp(t *testing.T) { + colType := NewColumnType(1) + + colType.AddTimestamp() + + expected := []*types.ColumnType{ + { + Type: types.TaosTimestampType, + }, + } + + values, err := colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) + + colType.AddTimestamp() + + values, err = colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) +} + +func TestColumnType_AddJson(t *testing.T) { + colType := NewColumnType(1) + + colType.AddJson(100) + + expected := []*types.ColumnType{ + { + Type: types.TaosJsonType, + MaxLen: 100, + }, + } + + values, err := colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) + + colType.AddJson(50) + + values, err = colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) +} + +func TestColumnType_AddGeometry(t *testing.T) { + colType := NewColumnType(1) + + colType.AddGeometry(100) + + expected := []*types.ColumnType{ + { + Type: types.TaosGeometryType, + MaxLen: 100, + }, + } + + values, err := colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) + + colType.AddGeometry(50) + + values, err = colType.GetValue() + assert.NoError(t, err) + assert.Equal(t, expected, values) +} + +func TestColumnType_GetValue(t *testing.T) { + // Initialize ColumnType with size 3 + colType := NewColumnType(3) + + // Add column types + colType.AddBool() + colType.AddTinyint() + colType.AddFloat() + + // Try to get values + values, err := colType.GetValue() + assert.NoError(t, err) + + // Check if the length of values matches the expected size + expectedSize := 3 + assert.Equal(t, expectedSize, len(values)) + + // Initialize ColumnType with size 3 + colType = NewColumnType(3) + + // Add only 2 column types + colType.AddBool() + colType.AddTinyint() + + // Try to get values + _, err = colType.GetValue() + + // Check if an error is returned due to incomplete column + assert.Error(t, err) + assert.Equal(t, "incomplete column expect 3 columns set 2 columns", err.Error()) +} + +func TestNewColumnTypeWithValue(t *testing.T) { + value := []*types.ColumnType{ + {Type: types.TaosBoolType}, + {Type: types.TaosTinyintType}, + } + + colType := NewColumnTypeWithValue(value) + + expectedSize := len(value) + assert.Equal(t, expectedSize, colType.size) + + expectedValue := value + assert.Equal(t, expectedValue, colType.value) + + expectedColumn := len(value) + assert.Equal(t, expectedColumn, colType.column) +} diff --git a/driver/common/param/param.go b/driver/common/param/param.go new file mode 100644 index 00000000..b2aa7027 --- /dev/null +++ b/driver/common/param/param.go @@ -0,0 +1,337 @@ +package param + +import ( + "database/sql/driver" + "time" + + taosTypes "github.com/taosdata/taosadapter/v3/driver/types" +) + +type Param struct { + size int + value []driver.Value + offset int +} + +func NewParam(size int) *Param { + return &Param{ + size: size, + value: make([]driver.Value, size), + } +} + +func NewParamsWithRowValue(value []driver.Value) []*Param { + params := make([]*Param, len(value)) + for i, d := range value { + params[i] = NewParam(1) + params[i].AddValue(d) + } + return params +} + +func (p *Param) SetBool(offset int, value bool) { + if offset >= p.size { + return + } + p.value[offset] = taosTypes.TaosBool(value) +} + +func (p *Param) SetNull(offset int) { + if offset >= p.size { + return + } + p.value[offset] = nil +} + +func (p *Param) SetTinyint(offset int, value int) { + if offset >= p.size { + return + } + p.value[offset] = taosTypes.TaosTinyint(value) +} + +func (p *Param) SetSmallint(offset int, value int) { + if offset >= p.size { + return + } + p.value[offset] = taosTypes.TaosSmallint(value) +} + +func (p *Param) SetInt(offset int, value int) { + if offset >= p.size { + return + } + p.value[offset] = taosTypes.TaosInt(value) +} + +func (p *Param) SetBigint(offset int, value int) { + if offset >= p.size { + return + } + p.value[offset] = taosTypes.TaosBigint(value) +} + +func (p *Param) SetUTinyint(offset int, value uint) { + if offset >= p.size { + return + } + p.value[offset] = taosTypes.TaosUTinyint(value) +} + +func (p *Param) SetUSmallint(offset int, value uint) { + if offset >= p.size { + return + } + p.value[offset] = taosTypes.TaosUSmallint(value) +} + +func (p *Param) SetUInt(offset int, value uint) { + if offset >= p.size { + return + } + p.value[offset] = taosTypes.TaosUInt(value) +} + +func (p *Param) SetUBigint(offset int, value uint) { + if offset >= p.size { + return + } + p.value[offset] = taosTypes.TaosUBigint(value) +} + +func (p *Param) SetFloat(offset int, value float32) { + if offset >= p.size { + return + } + p.value[offset] = taosTypes.TaosFloat(value) +} + +func (p *Param) SetDouble(offset int, value float64) { + if offset >= p.size { + return + } + p.value[offset] = taosTypes.TaosDouble(value) +} + +func (p *Param) SetBinary(offset int, value []byte) { + if offset >= p.size { + return + } + p.value[offset] = taosTypes.TaosBinary(value) +} + +func (p *Param) SetVarBinary(offset int, value []byte) { + if offset >= p.size { + return + } + p.value[offset] = taosTypes.TaosVarBinary(value) +} + +func (p *Param) SetNchar(offset int, value string) { + if offset >= p.size { + return + } + p.value[offset] = taosTypes.TaosNchar(value) +} + +func (p *Param) SetTimestamp(offset int, value time.Time, precision int) { + if offset >= p.size { + return + } + p.value[offset] = taosTypes.TaosTimestamp{ + T: value, + Precision: precision, + } +} + +func (p *Param) SetJson(offset int, value []byte) { + if offset >= p.size { + return + } + p.value[offset] = taosTypes.TaosJson(value) +} + +func (p *Param) SetGeometry(offset int, value []byte) { + if offset >= p.size { + return + } + p.value[offset] = taosTypes.TaosGeometry(value) +} + +func (p *Param) AddBool(value bool) *Param { + if p.offset >= p.size { + return p + } + p.value[p.offset] = taosTypes.TaosBool(value) + p.offset += 1 + return p +} + +func (p *Param) AddNull() *Param { + if p.offset >= p.size { + return p + } + p.value[p.offset] = nil + p.offset += 1 + return p +} + +func (p *Param) AddTinyint(value int) *Param { + if p.offset >= p.size { + return p + } + p.value[p.offset] = taosTypes.TaosTinyint(value) + p.offset += 1 + return p +} + +func (p *Param) AddSmallint(value int) *Param { + if p.offset >= p.size { + return p + } + p.value[p.offset] = taosTypes.TaosSmallint(value) + p.offset += 1 + return p +} + +func (p *Param) AddInt(value int) *Param { + if p.offset >= p.size { + return p + } + p.value[p.offset] = taosTypes.TaosInt(value) + p.offset += 1 + return p +} + +func (p *Param) AddBigint(value int) *Param { + if p.offset >= p.size { + return p + } + p.value[p.offset] = taosTypes.TaosBigint(value) + p.offset += 1 + return p +} + +func (p *Param) AddUTinyint(value uint) *Param { + if p.offset >= p.size { + return p + } + p.value[p.offset] = taosTypes.TaosUTinyint(value) + p.offset += 1 + return p +} + +func (p *Param) AddUSmallint(value uint) *Param { + if p.offset >= p.size { + return p + } + p.value[p.offset] = taosTypes.TaosUSmallint(value) + p.offset += 1 + return p +} + +func (p *Param) AddUInt(value uint) *Param { + if p.offset >= p.size { + return p + } + p.value[p.offset] = taosTypes.TaosUInt(value) + p.offset += 1 + return p +} + +func (p *Param) AddUBigint(value uint) *Param { + if p.offset >= p.size { + return p + } + p.value[p.offset] = taosTypes.TaosUBigint(value) + p.offset += 1 + return p +} + +func (p *Param) AddFloat(value float32) *Param { + if p.offset >= p.size { + return p + } + p.value[p.offset] = taosTypes.TaosFloat(value) + p.offset += 1 + return p +} + +func (p *Param) AddDouble(value float64) *Param { + if p.offset >= p.size { + return p + } + p.value[p.offset] = taosTypes.TaosDouble(value) + p.offset += 1 + return p +} + +func (p *Param) AddBinary(value []byte) *Param { + if p.offset >= p.size { + return p + } + p.value[p.offset] = taosTypes.TaosBinary(value) + p.offset += 1 + return p +} + +func (p *Param) AddVarBinary(value []byte) *Param { + if p.offset >= p.size { + return p + } + p.value[p.offset] = taosTypes.TaosVarBinary(value) + p.offset += 1 + return p +} + +func (p *Param) AddNchar(value string) *Param { + if p.offset >= p.size { + return p + } + p.value[p.offset] = taosTypes.TaosNchar(value) + p.offset += 1 + return p +} + +func (p *Param) AddTimestamp(value time.Time, precision int) *Param { + if p.offset >= p.size { + return p + } + p.value[p.offset] = taosTypes.TaosTimestamp{ + T: value, + Precision: precision, + } + p.offset += 1 + return p +} + +func (p *Param) AddJson(value []byte) *Param { + if p.offset >= p.size { + return p + } + p.value[p.offset] = taosTypes.TaosJson(value) + p.offset += 1 + return p +} + +func (p *Param) AddGeometry(value []byte) *Param { + if p.offset >= p.size { + return p + } + p.value[p.offset] = taosTypes.TaosGeometry(value) + p.offset += 1 + return p +} + +func (p *Param) GetValues() []driver.Value { + return p.value +} + +func (p *Param) AddValue(value interface{}) *Param { + if p.offset >= p.size { + return p + } + p.value[p.offset] = value + p.offset += 1 + return p +} diff --git a/driver/common/param/param_test.go b/driver/common/param/param_test.go new file mode 100644 index 00000000..35834629 --- /dev/null +++ b/driver/common/param/param_test.go @@ -0,0 +1,654 @@ +package param + +import ( + "database/sql/driver" + "testing" + "time" + + "github.com/stretchr/testify/assert" + taosTypes "github.com/taosdata/taosadapter/v3/driver/types" +) + +func TestParam_SetBool(t *testing.T) { + param := NewParam(1) + param.SetBool(0, true) + + expected := []driver.Value{taosTypes.TaosBool(true)} + assert.Equal(t, expected, param.GetValues()) + + param = NewParam(0) + param.SetBool(0, true) + assert.Equal(t, 0, len(param.GetValues())) +} + +func TestParam_SetNull(t *testing.T) { + param := NewParam(1) + param.SetNull(0) + + if param.GetValues()[0] != nil { + t.Errorf("SetNull failed, expected nil, got %v", param.GetValues()[0]) + } + param = NewParam(0) + param.SetNull(0) + assert.Equal(t, 0, len(param.GetValues())) +} + +func TestParam_SetTinyint(t *testing.T) { + param := NewParam(1) + param.SetTinyint(0, 42) + + expected := []driver.Value{taosTypes.TaosTinyint(42)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.SetTinyint(1, 42) // Attempt to set at index 1 with size 1 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_SetSmallint(t *testing.T) { + param := NewParam(1) + param.SetSmallint(0, 42) + + expected := []driver.Value{taosTypes.TaosSmallint(42)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.SetSmallint(1, 42) // Attempt to set at index 1 with size 1 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_SetInt(t *testing.T) { + param := NewParam(1) + param.SetInt(0, 42) + + expected := []driver.Value{taosTypes.TaosInt(42)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.SetInt(1, 42) // Attempt to set at index 1 with size 1 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_SetBigint(t *testing.T) { + param := NewParam(1) + param.SetBigint(0, 42) + + expected := []driver.Value{taosTypes.TaosBigint(42)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.SetBigint(1, 42) // Attempt to set at index 1 with size 1 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_SetUTinyint(t *testing.T) { + param := NewParam(1) + param.SetUTinyint(0, 42) + + expected := []driver.Value{taosTypes.TaosUTinyint(42)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.SetUTinyint(1, 42) // Attempt to set at index 1 with size 1 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_SetUSmallint(t *testing.T) { + param := NewParam(1) + param.SetUSmallint(0, 42) + + expected := []driver.Value{taosTypes.TaosUSmallint(42)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.SetUSmallint(1, 42) // Attempt to set at index 1 with size 1 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_SetUInt(t *testing.T) { + param := NewParam(1) + param.SetUInt(0, 42) + + expected := []driver.Value{taosTypes.TaosUInt(42)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.SetUInt(1, 42) // Attempt to set at index 1 with size 1 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_SetUBigint(t *testing.T) { + param := NewParam(1) + param.SetUBigint(0, 42) + + expected := []driver.Value{taosTypes.TaosUBigint(42)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.SetUBigint(1, 42) // Attempt to set at index 1 with size 1 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_SetFloat(t *testing.T) { + param := NewParam(1) + param.SetFloat(0, 3.14) + + expected := []driver.Value{taosTypes.TaosFloat(3.14)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.SetFloat(1, 3.14) // Attempt to set at index 1 with size 1 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_SetDouble(t *testing.T) { + param := NewParam(1) + param.SetDouble(0, 3.14) + + expected := []driver.Value{taosTypes.TaosDouble(3.14)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.SetDouble(1, 3.14) // Attempt to set at index 1 with size 1 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_SetBinary(t *testing.T) { + param := NewParam(1) + param.SetBinary(0, []byte{0x01, 0x02}) + + expected := []driver.Value{taosTypes.TaosBinary([]byte{0x01, 0x02})} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.SetBinary(1, []byte{0x01, 0x02}) // Attempt to set at index 1 with size 1 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_SetVarBinary(t *testing.T) { + param := NewParam(1) + param.SetVarBinary(0, []byte{0x01, 0x02}) + + expected := []driver.Value{taosTypes.TaosVarBinary([]byte{0x01, 0x02})} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.SetVarBinary(1, []byte{0x01, 0x02}) // Attempt to set at index 1 with size 1 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_SetNchar(t *testing.T) { + param := NewParam(1) + param.SetNchar(0, "hello") + + expected := []driver.Value{taosTypes.TaosNchar("hello")} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.SetNchar(1, "hello") // Attempt to set at index 1 with size 1 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_SetTimestamp(t *testing.T) { + timestamp := time.Date(2022, time.January, 1, 12, 0, 0, 0, time.UTC) + param := NewParam(1) + param.SetTimestamp(0, timestamp, 6) + + expected := []driver.Value{taosTypes.TaosTimestamp{T: timestamp, Precision: 6}} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.SetTimestamp(1, timestamp, 6) // Attempt to set at index 1 with size 1 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_SetJson(t *testing.T) { + jsonData := []byte(`{"key": "value"}`) + param := NewParam(1) + param.SetJson(0, jsonData) + + expected := []driver.Value{taosTypes.TaosJson(jsonData)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.SetJson(1, jsonData) // Attempt to set at index 1 with size 1 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_SetGeometry(t *testing.T) { + geometryData := []byte{0x01, 0x02, 0x03, 0x04} + param := NewParam(1) + param.SetGeometry(0, geometryData) + + expected := []driver.Value{taosTypes.TaosGeometry(geometryData)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.SetGeometry(1, geometryData) // Attempt to set at index 1 with size 1 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_AddBool(t *testing.T) { + param := NewParam(2) // Initialize with size 2 + + // Add a bool value + param.AddBool(true) + + expected := []driver.Value{taosTypes.TaosBool(true), nil} + assert.Equal(t, expected, param.GetValues()) + + // Add another bool value + param.AddBool(false) + + expected = []driver.Value{taosTypes.TaosBool(true), taosTypes.TaosBool(false)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.AddBool(true) // Attempt to add at index 2 with size 2 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_AddNull(t *testing.T) { + param := NewParam(2) // Initialize with size 2 + + // Add a null value + param.AddNull() + + expected := []driver.Value{nil, nil} + assert.Equal(t, expected, param.GetValues()) + + // Add another null value + param.AddNull() + + expected = []driver.Value{nil, nil} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.AddNull() // Attempt to add at index 2 with size 2 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_AddTinyint(t *testing.T) { + param := NewParam(2) // Initialize with size 2 + + // Add a tinyint value + param.AddTinyint(42) + + expected := []driver.Value{taosTypes.TaosTinyint(42), nil} + assert.Equal(t, expected, param.GetValues()) + + // Add another tinyint value + param.AddTinyint(84) + + expected = []driver.Value{taosTypes.TaosTinyint(42), taosTypes.TaosTinyint(84)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.AddTinyint(126) // Attempt to add at index 2 with size 2 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_AddSmallint(t *testing.T) { + param := NewParam(2) // Initialize with size 2 + + // Add a smallint value + param.AddSmallint(42) + + expected := []driver.Value{taosTypes.TaosSmallint(42), nil} + assert.Equal(t, expected, param.GetValues()) + + // Add another smallint value + param.AddSmallint(84) + + expected = []driver.Value{taosTypes.TaosSmallint(42), taosTypes.TaosSmallint(84)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.AddSmallint(126) // Attempt to add at index 2 with size 2 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_AddInt(t *testing.T) { + param := NewParam(2) // Initialize with size 2 + + // Add an int value + param.AddInt(42) + + expected := []driver.Value{taosTypes.TaosInt(42), nil} + assert.Equal(t, expected, param.GetValues()) + + // Add another int value + param.AddInt(84) + + expected = []driver.Value{taosTypes.TaosInt(42), taosTypes.TaosInt(84)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.AddInt(126) // Attempt to add at index 2 with size 2 + assert.Equal(t, expected, param.GetValues()) // Should not mod +} + +func TestParam_AddBigint(t *testing.T) { + param := NewParam(2) // Initialize with size 2 + + // Add a bigint value + param.AddBigint(42) + + expected := []driver.Value{taosTypes.TaosBigint(42), nil} + assert.Equal(t, expected, param.GetValues()) + + // Add another bigint value + param.AddBigint(84) + + expected = []driver.Value{taosTypes.TaosBigint(42), taosTypes.TaosBigint(84)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.AddBigint(126) // Attempt to add at index 2 with size 2 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_AddUTinyint(t *testing.T) { + param := NewParam(2) // Initialize with size 2 + + // Add a utinyint value + param.AddUTinyint(42) + + expected := []driver.Value{taosTypes.TaosUTinyint(42), nil} + assert.Equal(t, expected, param.GetValues()) + + // Add another utinyint value + param.AddUTinyint(84) + + expected = []driver.Value{taosTypes.TaosUTinyint(42), taosTypes.TaosUTinyint(84)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.AddUTinyint(126) // Attempt to add at index 2 with size 2 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_AddUSmallint(t *testing.T) { + param := NewParam(2) // Initialize with size 2 + + // Add a usmallint value + param.AddUSmallint(42) + + expected := []driver.Value{taosTypes.TaosUSmallint(42), nil} + assert.Equal(t, expected, param.GetValues()) + + // Add another usmallint value + param.AddUSmallint(84) + + expected = []driver.Value{taosTypes.TaosUSmallint(42), taosTypes.TaosUSmallint(84)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.AddUSmallint(126) // Attempt to add at index 2 with size 2 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_AddUInt(t *testing.T) { + param := NewParam(2) // Initialize with size 2 + + // Add a uint value + param.AddUInt(42) + + expected := []driver.Value{taosTypes.TaosUInt(42), nil} + assert.Equal(t, expected, param.GetValues()) + + // Add another uint value + param.AddUInt(84) + + expected = []driver.Value{taosTypes.TaosUInt(42), taosTypes.TaosUInt(84)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.AddUInt(126) // Attempt to add at index 2 with size 2 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_AddUBigint(t *testing.T) { + param := NewParam(2) // Initialize with size 2 + + // Add a ubigint value + param.AddUBigint(42) + + expected := []driver.Value{taosTypes.TaosUBigint(42), nil} + assert.Equal(t, expected, param.GetValues()) + + // Add another ubigint value + param.AddUBigint(84) + + expected = []driver.Value{taosTypes.TaosUBigint(42), taosTypes.TaosUBigint(84)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.AddUBigint(126) // Attempt to add at index 2 with size 2 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_AddFloat(t *testing.T) { + param := NewParam(2) // Initialize with size 2 + + // Add a float value + param.AddFloat(3.14) + + expected := []driver.Value{taosTypes.TaosFloat(3.14), nil} + assert.Equal(t, expected, param.GetValues()) + + // Add another float value + param.AddFloat(6.28) + + expected = []driver.Value{taosTypes.TaosFloat(3.14), taosTypes.TaosFloat(6.28)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.AddFloat(9.42) // Attempt to add at index 2 with size 2 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_AddDouble(t *testing.T) { + param := NewParam(2) // Initialize with size 2 + + // Add a double value + param.AddDouble(3.14) + + expected := []driver.Value{taosTypes.TaosDouble(3.14), nil} + assert.Equal(t, expected, param.GetValues()) + + // Add another double value + param.AddDouble(6.28) + + expected = []driver.Value{taosTypes.TaosDouble(3.14), taosTypes.TaosDouble(6.28)} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.AddDouble(9.42) // Attempt to add at index 2 with size 2 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_AddBinary(t *testing.T) { + param := NewParam(2) // Initialize with size 2 + binaryData := []byte{0x01, 0x02, 0x03} + + // Add a binary value + param.AddBinary(binaryData) + + expected := []driver.Value{taosTypes.TaosBinary(binaryData), nil} + assert.Equal(t, expected, param.GetValues()) + + // Add another binary value + param.AddBinary([]byte{0x04, 0x05, 0x06}) + + expected = []driver.Value{taosTypes.TaosBinary(binaryData), taosTypes.TaosBinary([]byte{0x04, 0x05, 0x06})} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.AddBinary([]byte{0x07, 0x08, 0x09}) // Attempt to add at index 2 with size 2 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_AddVarBinary(t *testing.T) { + param := NewParam(2) // Initialize with size 2 + binaryData := []byte{0x01, 0x02, 0x03} + + // Add a varbinary value + param.AddVarBinary(binaryData) + + expected := []driver.Value{taosTypes.TaosVarBinary(binaryData), nil} + assert.Equal(t, expected, param.GetValues()) + + // Add another varbinary value + param.AddVarBinary([]byte{0x04, 0x05, 0x06}) + + expected = []driver.Value{taosTypes.TaosVarBinary(binaryData), taosTypes.TaosVarBinary([]byte{0x04, 0x05, 0x06})} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.AddVarBinary([]byte{0x07, 0x08, 0x09}) // Attempt to add at index 2 with size 2 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_AddNchar(t *testing.T) { + param := NewParam(2) // Initialize with size 2 + + // Add an nchar value + param.AddNchar("hello") + + expected := []driver.Value{taosTypes.TaosNchar("hello"), nil} + assert.Equal(t, expected, param.GetValues()) + + // Add another nchar value + param.AddNchar("world") + + expected = []driver.Value{taosTypes.TaosNchar("hello"), taosTypes.TaosNchar("world")} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.AddNchar("test") // Attempt to add at index 2 with size 2 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_AddTimestamp(t *testing.T) { + timestamp := time.Date(2022, time.January, 1, 12, 0, 0, 0, time.UTC) + param := NewParam(2) // Initialize with size 2 + + // Add a timestamp value + param.AddTimestamp(timestamp, 6) + + expected := []driver.Value{taosTypes.TaosTimestamp{T: timestamp, Precision: 6}, nil} + assert.Equal(t, expected, param.GetValues()) + + // Add another timestamp value + param.AddTimestamp(timestamp.Add(time.Hour), 9) + + expected = []driver.Value{ + taosTypes.TaosTimestamp{T: timestamp, Precision: 6}, + taosTypes.TaosTimestamp{T: timestamp.Add(time.Hour), Precision: 9}, + } + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.AddTimestamp(timestamp.Add(2*time.Hour), 6) // Attempt to add at index 2 with size 2 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_AddJson(t *testing.T) { + jsonData := []byte(`{"key": "value"}`) + param := NewParam(2) // Initialize with size 2 + + // Add a JSON value + param.AddJson(jsonData) + + expected := []driver.Value{taosTypes.TaosJson(jsonData), nil} + assert.Equal(t, expected, param.GetValues()) + + // Add another JSON value + param.AddJson([]byte(`{"key2": "value2"}`)) + + expected = []driver.Value{ + taosTypes.TaosJson(jsonData), + taosTypes.TaosJson([]byte(`{"key2": "value2"}`)), + } + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.AddJson([]byte(`{"key3": "value3"}`)) // Attempt to add at index 2 with size 2 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_AddGeometry(t *testing.T) { + geometryData := []byte{0x01, 0x02, 0x03} + param := NewParam(2) // Initialize with size 2 + + // Add a geometry value + param.AddGeometry(geometryData) + + expected := []driver.Value{taosTypes.TaosGeometry(geometryData), nil} + assert.Equal(t, expected, param.GetValues()) + + // Add another geometry value + param.AddGeometry([]byte{0x04, 0x05, 0x06}) + + expected = []driver.Value{ + taosTypes.TaosGeometry(geometryData), + taosTypes.TaosGeometry([]byte{0x04, 0x05, 0x06}), + } + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.AddGeometry([]byte{0x07, 0x08, 0x09}) // Attempt to add at index 2 with size 2 + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestParam_AddValue(t *testing.T) { + param := NewParam(2) // Initialize with size 2 + + // Add a binary value + binaryData := []byte{0x01, 0x02, 0x03} + param.AddValue(taosTypes.TaosBinary(binaryData)) + + expected := []driver.Value{taosTypes.TaosBinary(binaryData), nil} + assert.Equal(t, expected, param.GetValues()) + + // Add a varchar value + param.AddValue(taosTypes.TaosVarBinary("hello")) + + expected = []driver.Value{taosTypes.TaosBinary(binaryData), taosTypes.TaosVarBinary("hello")} + assert.Equal(t, expected, param.GetValues()) + + // Test when offset is out of range + param.AddValue(taosTypes.TaosVarBinary("world")) + assert.Equal(t, expected, param.GetValues()) // Should not modify values +} + +func TestNewParamsWithRowValue(t *testing.T) { + rowValues := []driver.Value{taosTypes.TaosBool(true), taosTypes.TaosInt(42), taosTypes.TaosNchar("hello")} + + params := NewParamsWithRowValue(rowValues) + + expected := []*Param{ + { + size: 1, + value: []driver.Value{taosTypes.TaosBool(true)}, + offset: 1, + }, + { + size: 1, + value: []driver.Value{taosTypes.TaosInt(42)}, + offset: 1, + }, + { + size: 1, + value: []driver.Value{taosTypes.TaosNchar("hello")}, + offset: 1, + }, + } + + for i, param := range params { + assert.Equal(t, expected[i].size, param.size) + assert.Equal(t, expected[i].value, param.value) + assert.Equal(t, expected[i].offset, param.offset) + } +} diff --git a/driver/common/parser/block.go b/driver/common/parser/block.go new file mode 100644 index 00000000..b527242d --- /dev/null +++ b/driver/common/parser/block.go @@ -0,0 +1,374 @@ +package parser + +import ( + "database/sql/driver" + "math" + "unsafe" + + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/tools" +) + +const ( + Int8Size = common.Int8Size + Int16Size = common.Int16Size + Int32Size = common.Int32Size + Int64Size = common.Int64Size + UInt8Size = common.UInt8Size + UInt16Size = common.UInt16Size + UInt32Size = common.UInt32Size + UInt64Size = common.UInt64Size + Float32Size = common.Float32Size + Float64Size = common.Float64Size +) + +const ( + ColInfoSize = Int8Size + Int32Size + RawBlockVersionOffset = 0 + RawBlockLengthOffset = RawBlockVersionOffset + Int32Size + NumOfRowsOffset = RawBlockLengthOffset + Int32Size + NumOfColsOffset = NumOfRowsOffset + Int32Size + HasColumnSegmentOffset = NumOfColsOffset + Int32Size + GroupIDOffset = HasColumnSegmentOffset + Int32Size + ColInfoOffset = GroupIDOffset + UInt64Size +) + +func RawBlockGetVersion(rawBlock unsafe.Pointer) int32 { + return *((*int32)(tools.AddPointer(rawBlock, RawBlockVersionOffset))) +} + +func RawBlockGetLength(rawBlock unsafe.Pointer) int32 { + return *((*int32)(tools.AddPointer(rawBlock, RawBlockLengthOffset))) +} + +func RawBlockGetNumOfRows(rawBlock unsafe.Pointer) int32 { + return *((*int32)(tools.AddPointer(rawBlock, NumOfRowsOffset))) +} + +func RawBlockGetNumOfCols(rawBlock unsafe.Pointer) int32 { + return *((*int32)(tools.AddPointer(rawBlock, NumOfColsOffset))) +} + +func RawBlockGetHasColumnSegment(rawBlock unsafe.Pointer) int32 { + return *((*int32)(tools.AddPointer(rawBlock, HasColumnSegmentOffset))) +} + +func RawBlockGetGroupID(rawBlock unsafe.Pointer) uint64 { + return *((*uint64)(tools.AddPointer(rawBlock, GroupIDOffset))) +} + +type RawBlockColInfo struct { + ColType int8 + Bytes int32 +} + +func RawBlockGetColInfo(rawBlock unsafe.Pointer, infos []RawBlockColInfo) { + for i := 0; i < len(infos); i++ { + offset := ColInfoOffset + ColInfoSize*uintptr(i) + infos[i].ColType = *((*int8)(tools.AddPointer(rawBlock, offset))) + infos[i].Bytes = *((*int32)(tools.AddPointer(rawBlock, offset+Int8Size))) + } +} + +func RawBlockGetColumnLengthOffset(colCount int) uintptr { + return ColInfoOffset + uintptr(colCount)*ColInfoSize +} + +func RawBlockGetColDataOffset(colCount int) uintptr { + return ColInfoOffset + uintptr(colCount)*ColInfoSize + uintptr(colCount)*Int32Size +} + +type FormatTimeFunc func(ts int64, precision int) driver.Value + +func IsVarDataType(colType uint8) bool { + return colType == common.TSDB_DATA_TYPE_BINARY || + colType == common.TSDB_DATA_TYPE_NCHAR || + colType == common.TSDB_DATA_TYPE_JSON || + colType == common.TSDB_DATA_TYPE_VARBINARY || + colType == common.TSDB_DATA_TYPE_GEOMETRY +} + +func BitmapLen(n int) int { + return ((n) + ((1 << 3) - 1)) >> 3 +} + +func BitPos(n int) int { + return n & ((1 << 3) - 1) +} + +func CharOffset(n int) int { + return n >> 3 +} + +func BMIsNull(c byte, n int) bool { + return c&(1<<(7-BitPos(n))) == (1 << (7 - BitPos(n))) +} + +type rawConvertFunc func(pStart unsafe.Pointer, row int, arg ...interface{}) driver.Value + +type rawConvertVarDataFunc func(pHeader, pStart unsafe.Pointer, row int) driver.Value + +var rawConvertFuncSlice = [15]rawConvertFunc{} + +var rawConvertVarDataSlice = [21]rawConvertVarDataFunc{} + +func ItemIsNull(pHeader unsafe.Pointer, row int) bool { + offset := CharOffset(row) + c := *((*byte)(tools.AddPointer(pHeader, uintptr(offset)))) + return BMIsNull(c, row) +} + +func rawConvertBool(pStart unsafe.Pointer, row int, _ ...interface{}) driver.Value { + return (*((*byte)(tools.AddPointer(pStart, uintptr(row)*1)))) != 0 +} + +func rawConvertTinyint(pStart unsafe.Pointer, row int, _ ...interface{}) driver.Value { + return *((*int8)(tools.AddPointer(pStart, uintptr(row)*Int8Size))) +} + +func rawConvertSmallint(pStart unsafe.Pointer, row int, _ ...interface{}) driver.Value { + return *((*int16)(tools.AddPointer(pStart, uintptr(row)*Int16Size))) +} + +func rawConvertInt(pStart unsafe.Pointer, row int, _ ...interface{}) driver.Value { + return *((*int32)(tools.AddPointer(pStart, uintptr(row)*Int32Size))) +} + +func rawConvertBigint(pStart unsafe.Pointer, row int, _ ...interface{}) driver.Value { + return *((*int64)(tools.AddPointer(pStart, uintptr(row)*Int64Size))) +} + +func rawConvertUTinyint(pStart unsafe.Pointer, row int, _ ...interface{}) driver.Value { + return *((*uint8)(tools.AddPointer(pStart, uintptr(row)*UInt8Size))) +} + +func rawConvertUSmallint(pStart unsafe.Pointer, row int, _ ...interface{}) driver.Value { + return *((*uint16)(tools.AddPointer(pStart, uintptr(row)*UInt16Size))) +} + +func rawConvertUInt(pStart unsafe.Pointer, row int, _ ...interface{}) driver.Value { + return *((*uint32)(tools.AddPointer(pStart, uintptr(row)*UInt32Size))) +} + +func rawConvertUBigint(pStart unsafe.Pointer, row int, _ ...interface{}) driver.Value { + return *((*uint64)(tools.AddPointer(pStart, uintptr(row)*UInt64Size))) +} + +func rawConvertFloat(pStart unsafe.Pointer, row int, _ ...interface{}) driver.Value { + return math.Float32frombits(*((*uint32)(tools.AddPointer(pStart, uintptr(row)*Float32Size)))) +} + +func rawConvertDouble(pStart unsafe.Pointer, row int, _ ...interface{}) driver.Value { + return math.Float64frombits(*((*uint64)(tools.AddPointer(pStart, uintptr(row)*Float64Size)))) +} + +func rawConvertTime(pStart unsafe.Pointer, row int, arg ...interface{}) driver.Value { + if len(arg) == 1 { + return common.TimestampConvertToTime(*((*int64)(tools.AddPointer(pStart, uintptr(row)*Int64Size))), arg[0].(int)) + } else if len(arg) == 2 { + return arg[1].(FormatTimeFunc)(*((*int64)(tools.AddPointer(pStart, uintptr(row)*Int64Size))), arg[0].(int)) + } + panic("convertTime error") +} + +func rawConvertVarBinary(pHeader, pStart unsafe.Pointer, row int) driver.Value { + result := rawGetBytes(pHeader, pStart, row) + if result == nil { + return nil + } + return result +} + +func rawGetBytes(pHeader, pStart unsafe.Pointer, row int) []byte { + offset := *((*int32)(tools.AddPointer(pHeader, uintptr(row*4)))) + if offset == -1 { + return nil + } + currentRow := tools.AddPointer(pStart, uintptr(offset)) + clen := *((*uint16)(currentRow)) + if clen == 0 { + return make([]byte, 0) + } + currentRow = tools.AddPointer(currentRow, 2) + result := make([]byte, clen) + Copy(currentRow, result, 0, int(clen)) + return result +} + +func rawConvertGeometry(pHeader, pStart unsafe.Pointer, row int) driver.Value { + return rawConvertVarBinary(pHeader, pStart, row) +} + +func rawConvertBinary(pHeader, pStart unsafe.Pointer, row int) driver.Value { + result := rawGetBytes(pHeader, pStart, row) + if result == nil { + return nil + } + return *(*string)(unsafe.Pointer(&result)) +} + +func rawConvertNchar(pHeader, pStart unsafe.Pointer, row int) driver.Value { + offset := *((*int32)(tools.AddPointer(pHeader, uintptr(row*4)))) + if offset == -1 { + return nil + } + currentRow := tools.AddPointer(pStart, uintptr(offset)) + clen := *((*uint16)(currentRow)) / 4 + if clen == 0 { + return "" + } + currentRow = unsafe.Pointer(uintptr(currentRow) + 2) + binaryVal := make([]rune, clen) + + for index := uint16(0); index < clen; index++ { + binaryVal[index] = *((*rune)(unsafe.Pointer(uintptr(currentRow) + uintptr(index*4)))) + } + return string(binaryVal) +} + +func rawConvertJson(pHeader, pStart unsafe.Pointer, row int) driver.Value { + return rawConvertVarBinary(pHeader, pStart, row) +} + +func ReadBlockSimple(block unsafe.Pointer, precision int) [][]driver.Value { + blockSize := RawBlockGetNumOfRows(block) + colCount := RawBlockGetNumOfCols(block) + colInfo := make([]RawBlockColInfo, colCount) + RawBlockGetColInfo(block, colInfo) + colTypes := make([]uint8, colCount) + for i := int32(0); i < colCount; i++ { + colTypes[i] = uint8(colInfo[i].ColType) + } + return ReadBlock(block, int(blockSize), colTypes, precision) +} + +// ReadBlock in-place +func ReadBlock(block unsafe.Pointer, blockSize int, colTypes []uint8, precision int) [][]driver.Value { + r := make([][]driver.Value, blockSize) + colCount := len(colTypes) + nullBitMapOffset := uintptr(BitmapLen(blockSize)) + lengthOffset := RawBlockGetColumnLengthOffset(colCount) + pHeader := tools.AddPointer(block, RawBlockGetColDataOffset(colCount)) + var pStart unsafe.Pointer + for column := 0; column < colCount; column++ { + colLength := *((*int32)(tools.AddPointer(block, lengthOffset+uintptr(column)*Int32Size))) + if IsVarDataType(colTypes[column]) { + convertF := rawConvertVarDataSlice[colTypes[column]] + pStart = tools.AddPointer(pHeader, Int32Size*uintptr(blockSize)) + for row := 0; row < blockSize; row++ { + if column == 0 { + r[row] = make([]driver.Value, colCount) + } + r[row][column] = convertF(pHeader, pStart, row) + } + } else { + convertF := rawConvertFuncSlice[colTypes[column]] + pStart = tools.AddPointer(pHeader, nullBitMapOffset) + for row := 0; row < blockSize; row++ { + if column == 0 { + r[row] = make([]driver.Value, colCount) + } + if ItemIsNull(pHeader, row) { + r[row][column] = nil + } else { + r[row][column] = convertF(pStart, row, precision) + } + } + } + pHeader = tools.AddPointer(pStart, uintptr(colLength)) + } + return r +} + +func ReadRow(dest []driver.Value, block unsafe.Pointer, blockSize int, row int, colTypes []uint8, precision int) { + colCount := len(colTypes) + nullBitMapOffset := uintptr(BitmapLen(blockSize)) + lengthOffset := RawBlockGetColumnLengthOffset(colCount) + pHeader := tools.AddPointer(block, RawBlockGetColDataOffset(colCount)) + var pStart unsafe.Pointer + for column := 0; column < colCount; column++ { + colLength := *((*int32)(tools.AddPointer(block, lengthOffset+uintptr(column)*Int32Size))) + if IsVarDataType(colTypes[column]) { + convertF := rawConvertVarDataSlice[colTypes[column]] + pStart = tools.AddPointer(pHeader, Int32Size*uintptr(blockSize)) + dest[column] = convertF(pHeader, pStart, row) + } else { + convertF := rawConvertFuncSlice[colTypes[column]] + pStart = tools.AddPointer(pHeader, nullBitMapOffset) + if ItemIsNull(pHeader, row) { + dest[column] = nil + } else { + dest[column] = convertF(pStart, row, precision) + } + } + pHeader = tools.AddPointer(pStart, uintptr(colLength)) + } +} + +func ReadBlockWithTimeFormat(block unsafe.Pointer, blockSize int, colTypes []uint8, precision int, formatFunc FormatTimeFunc) [][]driver.Value { + r := make([][]driver.Value, blockSize) + colCount := len(colTypes) + nullBitMapOffset := uintptr(BitmapLen(blockSize)) + lengthOffset := RawBlockGetColumnLengthOffset(colCount) + pHeader := tools.AddPointer(block, RawBlockGetColDataOffset(colCount)) + var pStart unsafe.Pointer + for column := 0; column < colCount; column++ { + colLength := *((*int32)(tools.AddPointer(block, lengthOffset+uintptr(column)*Int32Size))) + if IsVarDataType(colTypes[column]) { + convertF := rawConvertVarDataSlice[colTypes[column]] + pStart = tools.AddPointer(pHeader, uintptr(4*blockSize)) + for row := 0; row < blockSize; row++ { + if column == 0 { + r[row] = make([]driver.Value, colCount) + } + r[row][column] = convertF(pHeader, pStart, row) + } + } else { + convertF := rawConvertFuncSlice[colTypes[column]] + pStart = tools.AddPointer(pHeader, nullBitMapOffset) + for row := 0; row < blockSize; row++ { + if column == 0 { + r[row] = make([]driver.Value, colCount) + } + if ItemIsNull(pHeader, row) { + r[row][column] = nil + } else { + r[row][column] = convertF(pStart, row, precision, formatFunc) + } + } + } + pHeader = tools.AddPointer(pStart, uintptr(colLength)) + } + return r +} + +func ItemRawBlock(colType uint8, pHeader, pStart unsafe.Pointer, row int, precision int, timeFormat FormatTimeFunc) driver.Value { + if IsVarDataType(colType) { + return rawConvertVarDataSlice[colType](pHeader, pStart, row) + } + if ItemIsNull(pHeader, row) { + return nil + } + return rawConvertFuncSlice[colType](pStart, row, precision, timeFormat) +} + +func init() { + rawConvertFuncSlice[uint8(common.TSDB_DATA_TYPE_BOOL)] = rawConvertBool + rawConvertFuncSlice[uint8(common.TSDB_DATA_TYPE_TINYINT)] = rawConvertTinyint + rawConvertFuncSlice[uint8(common.TSDB_DATA_TYPE_SMALLINT)] = rawConvertSmallint + rawConvertFuncSlice[uint8(common.TSDB_DATA_TYPE_INT)] = rawConvertInt + rawConvertFuncSlice[uint8(common.TSDB_DATA_TYPE_BIGINT)] = rawConvertBigint + rawConvertFuncSlice[uint8(common.TSDB_DATA_TYPE_UTINYINT)] = rawConvertUTinyint + rawConvertFuncSlice[uint8(common.TSDB_DATA_TYPE_USMALLINT)] = rawConvertUSmallint + rawConvertFuncSlice[uint8(common.TSDB_DATA_TYPE_UINT)] = rawConvertUInt + rawConvertFuncSlice[uint8(common.TSDB_DATA_TYPE_UBIGINT)] = rawConvertUBigint + rawConvertFuncSlice[uint8(common.TSDB_DATA_TYPE_FLOAT)] = rawConvertFloat + rawConvertFuncSlice[uint8(common.TSDB_DATA_TYPE_DOUBLE)] = rawConvertDouble + rawConvertFuncSlice[uint8(common.TSDB_DATA_TYPE_TIMESTAMP)] = rawConvertTime + + rawConvertVarDataSlice[uint8(common.TSDB_DATA_TYPE_BINARY)] = rawConvertBinary + rawConvertVarDataSlice[uint8(common.TSDB_DATA_TYPE_NCHAR)] = rawConvertNchar + rawConvertVarDataSlice[uint8(common.TSDB_DATA_TYPE_JSON)] = rawConvertJson + rawConvertVarDataSlice[uint8(common.TSDB_DATA_TYPE_VARBINARY)] = rawConvertVarBinary + rawConvertVarDataSlice[uint8(common.TSDB_DATA_TYPE_GEOMETRY)] = rawConvertGeometry +} diff --git a/driver/common/parser/block_test.go b/driver/common/parser/block_test.go new file mode 100644 index 00000000..11b2877e --- /dev/null +++ b/driver/common/parser/block_test.go @@ -0,0 +1,797 @@ +package parser + +import ( + "database/sql/driver" + "fmt" + "testing" + "time" + "unsafe" + + "github.com/stretchr/testify/assert" + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" + "github.com/taosdata/taosadapter/v3/tools" +) + +// @author: xftan +// @date: 2023/10/13 11:13 +// @description: test block +func TestReadBlock(t *testing.T) { + conn, err := wrapper.TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + + defer wrapper.TaosClose(conn) + defer func() { + res := wrapper.TaosQuery(conn, "drop database if exists test_block_raw_parser") + code := wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + }() + res := wrapper.TaosQuery(conn, "create database if not exists test_block_raw_parser") + code := wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + res = wrapper.TaosQuery(conn, "drop table if exists test_block_raw_parser.all_type2") + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + res = wrapper.TaosQuery(conn, "create table if not exists test_block_raw_parser.all_type2 (ts timestamp,"+ + "c1 bool,"+ + "c2 tinyint,"+ + "c3 smallint,"+ + "c4 int,"+ + "c5 bigint,"+ + "c6 tinyint unsigned,"+ + "c7 smallint unsigned,"+ + "c8 int unsigned,"+ + "c9 bigint unsigned,"+ + "c10 float,"+ + "c11 double,"+ + "c12 binary(20),"+ + "c13 nchar(20)"+ + ")") + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + now := time.Now() + after1s := now.Add(time.Second) + sql := fmt.Sprintf("insert into test_block_raw_parser.all_type2 values('%s',1,1,1,1,1,1,1,1,1,1,1,'test_binary','test_nchar')('%s',null,null,null,null,null,null,null,null,null,null,null,null,null)", now.Format(time.RFC3339Nano), after1s.Format(time.RFC3339Nano)) + res = wrapper.TaosQuery(conn, sql) + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + + sql = "select * from test_block_raw_parser.all_type2" + res = wrapper.TaosQuery(conn, sql) + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + fileCount := wrapper.TaosNumFields(res) + rh, err := wrapper.ReadColumn(res, fileCount) + if err != nil { + t.Error(err) + return + } + precision := wrapper.TaosResultPrecision(res) + pHeaderList := make([]unsafe.Pointer, fileCount) + pStartList := make([]unsafe.Pointer, fileCount) + var data [][]driver.Value + for { + blockSize, errCode, block := wrapper.TaosFetchRawBlock(res) + if errCode != int(errors.SUCCESS) { + errStr := wrapper.TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + wrapper.TaosFreeResult(res) + return + } + if blockSize == 0 { + break + } + nullBitMapOffset := uintptr(BitmapLen(blockSize)) + lengthOffset := RawBlockGetColumnLengthOffset(fileCount) + tmpPHeader := tools.AddPointer(block, RawBlockGetColDataOffset(fileCount)) + var tmpPStart unsafe.Pointer + for column := 0; column < fileCount; column++ { + colLength := *((*int32)(tools.AddPointer(block, lengthOffset+uintptr(column)*Int32Size))) + if IsVarDataType(rh.ColTypes[column]) { + pHeaderList[column] = tmpPHeader + tmpPStart = tools.AddPointer(tmpPHeader, Int32Size*uintptr(blockSize)) + pStartList[column] = tmpPStart + } else { + pHeaderList[column] = tmpPHeader + tmpPStart = tools.AddPointer(tmpPHeader, nullBitMapOffset) + pStartList[column] = tmpPStart + } + tmpPHeader = tools.AddPointer(tmpPStart, uintptr(colLength)) + } + for row := 0; row < blockSize; row++ { + rowV := make([]driver.Value, fileCount) + for column := 0; column < fileCount; column++ { + v := ItemRawBlock(rh.ColTypes[column], pHeaderList[column], pStartList[column], row, precision, func(ts int64, precision int) driver.Value { + return common.TimestampConvertToTime(ts, precision) + }) + rowV[column] = v + } + data = append(data, rowV) + } + } + wrapper.TaosFreeResult(res) + assert.Equal(t, 2, len(data)) + row1 := data[0] + assert.Equal(t, now.UnixNano()/1e6, row1[0].(time.Time).UnixNano()/1e6) + assert.Equal(t, true, row1[1].(bool)) + assert.Equal(t, int8(1), row1[2].(int8)) + assert.Equal(t, int16(1), row1[3].(int16)) + assert.Equal(t, int32(1), row1[4].(int32)) + assert.Equal(t, int64(1), row1[5].(int64)) + assert.Equal(t, uint8(1), row1[6].(uint8)) + assert.Equal(t, uint16(1), row1[7].(uint16)) + assert.Equal(t, uint32(1), row1[8].(uint32)) + assert.Equal(t, uint64(1), row1[9].(uint64)) + assert.Equal(t, float32(1), row1[10].(float32)) + assert.Equal(t, float64(1), row1[11].(float64)) + assert.Equal(t, "test_binary", row1[12].(string)) + assert.Equal(t, "test_nchar", row1[13].(string)) + row2 := data[1] + assert.Equal(t, after1s.UnixNano()/1e6, row2[0].(time.Time).UnixNano()/1e6) + for i := 1; i < 14; i++ { + assert.Nil(t, row2[i]) + } +} + +// @author: xftan +// @date: 2023/10/13 11:13 +// @description: test block tag +func TestBlockTag(t *testing.T) { + conn, err := wrapper.TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + + defer wrapper.TaosClose(conn) + defer func() { + res := wrapper.TaosQuery(conn, "drop database if exists test_block_abc1") + code := wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + }() + res := wrapper.TaosQuery(conn, "create database if not exists test_block_abc1") + code := wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + + res = wrapper.TaosQuery(conn, "use test_block_abc1") + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + + res = wrapper.TaosQuery(conn, "create table if not exists meters(ts timestamp, v int) tags(location varchar(16))") + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + + res = wrapper.TaosQuery(conn, "create table if not exists tb1 using meters tags('abcd')") + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + + sql := "select distinct tbname,location from meters;" + res = wrapper.TaosQuery(conn, sql) + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + fileCount := wrapper.TaosNumFields(res) + rh, err := wrapper.ReadColumn(res, fileCount) + if err != nil { + t.Error(err) + return + } + precision := wrapper.TaosResultPrecision(res) + pHeaderList := make([]unsafe.Pointer, fileCount) + pStartList := make([]unsafe.Pointer, fileCount) + var data [][]driver.Value + for { + blockSize, errCode, block := wrapper.TaosFetchRawBlock(res) + if errCode != int(errors.SUCCESS) { + errStr := wrapper.TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + wrapper.TaosFreeResult(res) + return + } + if blockSize == 0 { + break + } + nullBitMapOffset := uintptr(BitmapLen(blockSize)) + lengthOffset := RawBlockGetColumnLengthOffset(fileCount) + tmpPHeader := tools.AddPointer(block, RawBlockGetColDataOffset(fileCount)) // length i32, group u64 + var tmpPStart unsafe.Pointer + for column := 0; column < fileCount; column++ { + colLength := *((*int32)(tools.AddPointer(block, lengthOffset+uintptr(column)*Int32Size))) + if IsVarDataType(rh.ColTypes[column]) { + pHeaderList[column] = tmpPHeader + tmpPStart = tools.AddPointer(tmpPHeader, Int32Size*uintptr(blockSize)) + pStartList[column] = tmpPStart + } else { + pHeaderList[column] = tmpPHeader + tmpPStart = tools.AddPointer(tmpPHeader, nullBitMapOffset) + pStartList[column] = tmpPStart + } + tmpPHeader = tools.AddPointer(tmpPStart, uintptr(colLength)) + } + for row := 0; row < blockSize; row++ { + rowV := make([]driver.Value, fileCount) + for column := 0; column < fileCount; column++ { + v := ItemRawBlock(rh.ColTypes[column], pHeaderList[column], pStartList[column], row, precision, func(ts int64, precision int) driver.Value { + return common.TimestampConvertToTime(ts, precision) + }) + rowV[column] = v + } + data = append(data, rowV) + } + } + wrapper.TaosFreeResult(res) + t.Log(data) + t.Log(len(data[0][1].(string))) +} + +// @author: xftan +// @date: 2023/10/13 11:18 +// @description: test read row +func TestReadRow(t *testing.T) { + conn, err := wrapper.TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + + defer wrapper.TaosClose(conn) + res := wrapper.TaosQuery(conn, "drop database if exists test_read_row") + code := wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + defer func() { + res = wrapper.TaosQuery(conn, "drop database if exists test_read_row") + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + }() + res = wrapper.TaosQuery(conn, "create database test_read_row") + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + + res = wrapper.TaosQuery(conn, "create table if not exists test_read_row.all_type (ts timestamp,"+ + "c1 bool,"+ + "c2 tinyint,"+ + "c3 smallint,"+ + "c4 int,"+ + "c5 bigint,"+ + "c6 tinyint unsigned,"+ + "c7 smallint unsigned,"+ + "c8 int unsigned,"+ + "c9 bigint unsigned,"+ + "c10 float,"+ + "c11 double,"+ + "c12 binary(20),"+ + "c13 nchar(20)"+ + ") tags (info json)") + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + now := time.Now() + after1s := now.Add(time.Second) + sql := fmt.Sprintf("insert into test_read_row.t0 using test_read_row.all_type tags('{\"a\":1}') values('%s',1,1,1,1,1,1,1,1,1,1,1,'test_binary','test_nchar')('%s',null,null,null,null,null,null,null,null,null,null,null,null,null)", now.Format(time.RFC3339Nano), after1s.Format(time.RFC3339Nano)) + res = wrapper.TaosQuery(conn, sql) + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + + sql = "select * from test_read_row.all_type" + res = wrapper.TaosQuery(conn, sql) + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + fileCount := wrapper.TaosNumFields(res) + rh, err := wrapper.ReadColumn(res, fileCount) + if err != nil { + t.Error(err) + return + } + precision := wrapper.TaosResultPrecision(res) + var data [][]driver.Value + for { + blockSize, errCode, block := wrapper.TaosFetchRawBlock(res) + if errCode != int(errors.SUCCESS) { + errStr := wrapper.TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + wrapper.TaosFreeResult(res) + return + } + if blockSize == 0 { + break + } + for i := 0; i < blockSize; i++ { + tmp := make([]driver.Value, fileCount) + ReadRow(tmp, block, blockSize, i, rh.ColTypes, precision) + data = append(data, tmp) + } + } + wrapper.TaosFreeResult(res) + assert.Equal(t, 2, len(data)) + row1 := data[0] + assert.Equal(t, now.UnixNano()/1e6, row1[0].(time.Time).UnixNano()/1e6) + assert.Equal(t, true, row1[1].(bool)) + assert.Equal(t, int8(1), row1[2].(int8)) + assert.Equal(t, int16(1), row1[3].(int16)) + assert.Equal(t, int32(1), row1[4].(int32)) + assert.Equal(t, int64(1), row1[5].(int64)) + assert.Equal(t, uint8(1), row1[6].(uint8)) + assert.Equal(t, uint16(1), row1[7].(uint16)) + assert.Equal(t, uint32(1), row1[8].(uint32)) + assert.Equal(t, uint64(1), row1[9].(uint64)) + assert.Equal(t, float32(1), row1[10].(float32)) + assert.Equal(t, float64(1), row1[11].(float64)) + assert.Equal(t, "test_binary", row1[12].(string)) + assert.Equal(t, "test_nchar", row1[13].(string)) + assert.Equal(t, []byte(`{"a":1}`), row1[14].([]byte)) + row2 := data[1] + assert.Equal(t, after1s.UnixNano()/1e6, row2[0].(time.Time).UnixNano()/1e6) + for i := 1; i < 14; i++ { + assert.Nil(t, row2[i]) + } + assert.Equal(t, []byte(`{"a":1}`), row2[14].([]byte)) +} + +// @author: xftan +// @date: 2023/10/13 11:18 +// @description: test read block with time format +func TestReadBlockWithTimeFormat(t *testing.T) { + conn, err := wrapper.TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + + defer wrapper.TaosClose(conn) + res := wrapper.TaosQuery(conn, "drop database if exists test_read_block_tf") + code := wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + defer func() { + res = wrapper.TaosQuery(conn, "drop database if exists test_read_block_tf") + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + }() + res = wrapper.TaosQuery(conn, "create database test_read_block_tf") + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + + res = wrapper.TaosQuery(conn, "create table if not exists test_read_block_tf.all_type (ts timestamp,"+ + "c1 bool,"+ + "c2 tinyint,"+ + "c3 smallint,"+ + "c4 int,"+ + "c5 bigint,"+ + "c6 tinyint unsigned,"+ + "c7 smallint unsigned,"+ + "c8 int unsigned,"+ + "c9 bigint unsigned,"+ + "c10 float,"+ + "c11 double,"+ + "c12 binary(20),"+ + "c13 nchar(20)"+ + ") tags (info json)") + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + now := time.Now() + after1s := now.Add(time.Second) + sql := fmt.Sprintf("insert into test_read_block_tf.t0 using test_read_block_tf.all_type tags('{\"a\":1}') values('%s',false,1,1,1,1,1,1,1,1,1,1,'test_binary','test_nchar')('%s',null,null,null,null,null,null,null,null,null,null,null,null,null)", now.Format(time.RFC3339Nano), after1s.Format(time.RFC3339Nano)) + res = wrapper.TaosQuery(conn, sql) + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + + sql = "select * from test_read_block_tf.all_type" + res = wrapper.TaosQuery(conn, sql) + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + fileCount := wrapper.TaosNumFields(res) + rh, err := wrapper.ReadColumn(res, fileCount) + if err != nil { + t.Error(err) + return + } + precision := wrapper.TaosResultPrecision(res) + var data [][]driver.Value + for { + blockSize, errCode, block := wrapper.TaosFetchRawBlock(res) + if errCode != int(errors.SUCCESS) { + errStr := wrapper.TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + wrapper.TaosFreeResult(res) + return + } + if blockSize == 0 { + break + } + data = ReadBlockWithTimeFormat(block, blockSize, rh.ColTypes, precision, func(ts int64, precision int) driver.Value { + return common.TimestampConvertToTime(ts, precision) + }) + } + wrapper.TaosFreeResult(res) + assert.Equal(t, 2, len(data)) + row1 := data[0] + assert.Equal(t, now.UnixNano()/1e6, row1[0].(time.Time).UnixNano()/1e6) + assert.Equal(t, false, row1[1].(bool)) + assert.Equal(t, int8(1), row1[2].(int8)) + assert.Equal(t, int16(1), row1[3].(int16)) + assert.Equal(t, int32(1), row1[4].(int32)) + assert.Equal(t, int64(1), row1[5].(int64)) + assert.Equal(t, uint8(1), row1[6].(uint8)) + assert.Equal(t, uint16(1), row1[7].(uint16)) + assert.Equal(t, uint32(1), row1[8].(uint32)) + assert.Equal(t, uint64(1), row1[9].(uint64)) + assert.Equal(t, float32(1), row1[10].(float32)) + assert.Equal(t, float64(1), row1[11].(float64)) + assert.Equal(t, "test_binary", row1[12].(string)) + assert.Equal(t, "test_nchar", row1[13].(string)) + assert.Equal(t, []byte(`{"a":1}`), row1[14].([]byte)) + row2 := data[1] + assert.Equal(t, after1s.UnixNano()/1e6, row2[0].(time.Time).UnixNano()/1e6) + for i := 1; i < 14; i++ { + assert.Nil(t, row2[i]) + } + assert.Equal(t, []byte(`{"a":1}`), row2[14].([]byte)) +} + +// @author: xftan +// @date: 2023/10/13 11:18 +// @description: test parse block +func TestParseBlock(t *testing.T) { + conn, err := wrapper.TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + + defer wrapper.TaosClose(conn) + res := wrapper.TaosQuery(conn, "drop database if exists parse_block") + code := wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + defer func() { + res = wrapper.TaosQuery(conn, "drop database if exists parse_block") + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + }() + res = wrapper.TaosQuery(conn, "create database parse_block vgroups 1") + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + + res = wrapper.TaosQuery(conn, "create table if not exists parse_block.all_type (ts timestamp,"+ + "c1 bool,"+ + "c2 tinyint,"+ + "c3 smallint,"+ + "c4 int,"+ + "c5 bigint,"+ + "c6 tinyint unsigned,"+ + "c7 smallint unsigned,"+ + "c8 int unsigned,"+ + "c9 bigint unsigned,"+ + "c10 float,"+ + "c11 double,"+ + "c12 binary(20),"+ + "c13 nchar(20),"+ + "c14 varbinary(20),"+ + "c15 geometry(100)"+ + ") tags (info json)") + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + now := time.Now() + after1s := now.Add(time.Second) + sql := fmt.Sprintf("insert into parse_block.t0 using parse_block.all_type tags('{\"a\":1}') "+ + "values('%s',1,1,1,1,1,1,1,1,1,1,1,'test_binary','test_nchar','test_varbinary','POINT(100 100)')"+ + "('%s',null,null,null,null,null,null,null,null,null,null,null,null,null,null,null)", now.Format(time.RFC3339Nano), after1s.Format(time.RFC3339Nano)) + res = wrapper.TaosQuery(conn, sql) + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(res) + + sql = "select * from parse_block.all_type" + res = wrapper.TaosQuery(conn, sql) + code = wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + precision := wrapper.TaosResultPrecision(res) + var data [][]driver.Value + for { + blockSize, errCode, block := wrapper.TaosFetchRawBlock(res) + if errCode != int(errors.SUCCESS) { + errStr := wrapper.TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + wrapper.TaosFreeResult(res) + return + } + if blockSize == 0 { + break + } + version := RawBlockGetVersion(block) + t.Log(version) + length := RawBlockGetLength(block) + assert.Equal(t, int32(448), length) + rows := RawBlockGetNumOfRows(block) + assert.Equal(t, int32(2), rows) + columns := RawBlockGetNumOfCols(block) + assert.Equal(t, int32(17), columns) + hasColumnSegment := RawBlockGetHasColumnSegment(block) + assert.Equal(t, int32(-2147483648), hasColumnSegment) + groupId := RawBlockGetGroupID(block) + assert.Equal(t, uint64(0), groupId) + infos := make([]RawBlockColInfo, columns) + RawBlockGetColInfo(block, infos) + assert.Equal( + t, + []RawBlockColInfo{ + { + ColType: 9, + Bytes: 8, + }, + { + ColType: 1, + Bytes: 1, + }, + { + ColType: 2, + Bytes: 1, + }, + { + ColType: 3, + Bytes: 2, + }, + { + ColType: 4, + Bytes: 4, + }, + { + ColType: 5, + Bytes: 8, + }, + { + ColType: 11, + Bytes: 1, + }, + { + ColType: 12, + Bytes: 2, + }, + { + ColType: 13, + Bytes: 4, + }, + { + ColType: 14, + Bytes: 8, + }, + { + ColType: 6, + Bytes: 4, + }, + { + ColType: 7, + Bytes: 8, + }, + { + ColType: 8, + Bytes: 22, + }, + { + ColType: 10, + Bytes: 82, + }, + { + ColType: 16, + Bytes: 22, + }, + { + ColType: 20, + Bytes: 102, + }, + { + ColType: 15, + Bytes: 16384, + }, + }, + infos, + ) + d := ReadBlockSimple(block, precision) + data = append(data, d...) + } + wrapper.TaosFreeResult(res) + assert.Equal(t, 2, len(data)) + row1 := data[0] + assert.Equal(t, now.UnixNano()/1e6, row1[0].(time.Time).UnixNano()/1e6) + assert.Equal(t, true, row1[1].(bool)) + assert.Equal(t, int8(1), row1[2].(int8)) + assert.Equal(t, int16(1), row1[3].(int16)) + assert.Equal(t, int32(1), row1[4].(int32)) + assert.Equal(t, int64(1), row1[5].(int64)) + assert.Equal(t, uint8(1), row1[6].(uint8)) + assert.Equal(t, uint16(1), row1[7].(uint16)) + assert.Equal(t, uint32(1), row1[8].(uint32)) + assert.Equal(t, uint64(1), row1[9].(uint64)) + assert.Equal(t, float32(1), row1[10].(float32)) + assert.Equal(t, float64(1), row1[11].(float64)) + assert.Equal(t, "test_binary", row1[12].(string)) + assert.Equal(t, "test_nchar", row1[13].(string)) + assert.Equal(t, []byte("test_varbinary"), row1[14].([]byte)) + assert.Equal(t, []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, row1[15].([]byte)) + assert.Equal(t, []byte(`{"a":1}`), row1[16].([]byte)) + row2 := data[1] + assert.Equal(t, after1s.UnixNano()/1e6, row2[0].(time.Time).UnixNano()/1e6) + for i := 1; i < 16; i++ { + assert.Nil(t, row2[i]) + } + assert.Equal(t, []byte(`{"a":1}`), row2[16].([]byte)) +} diff --git a/driver/common/parser/mem.go b/driver/common/parser/mem.go new file mode 100644 index 00000000..f0d4b000 --- /dev/null +++ b/driver/common/parser/mem.go @@ -0,0 +1,12 @@ +package parser + +import "unsafe" + +//go:noescape +func memmove(to, from unsafe.Pointer, n uintptr) + +//go:linkname memmove runtime.memmove + +func Copy(source unsafe.Pointer, data []byte, index int, length int) { + memmove(unsafe.Pointer(&data[index]), source, uintptr(length)) +} diff --git a/driver/common/parser/mem.s b/driver/common/parser/mem.s new file mode 100644 index 00000000..e69de29b diff --git a/driver/common/parser/mem_test.go b/driver/common/parser/mem_test.go new file mode 100644 index 00000000..d3e244be --- /dev/null +++ b/driver/common/parser/mem_test.go @@ -0,0 +1,20 @@ +package parser + +import ( + "testing" + "unsafe" + + "github.com/stretchr/testify/assert" +) + +func TestCopy(t *testing.T) { + data := []byte("World") + data1 := make([]byte, 10) + data1[0] = 'H' + data1[1] = 'e' + data1[2] = 'l' + data1[3] = 'l' + data1[4] = 'o' + Copy(unsafe.Pointer(&data[0]), data1, 5, 5) + assert.Equal(t, "HelloWorld", string(data1)) +} diff --git a/driver/common/parser/raw.go b/driver/common/parser/raw.go new file mode 100644 index 00000000..9a8235a3 --- /dev/null +++ b/driver/common/parser/raw.go @@ -0,0 +1,184 @@ +package parser + +import ( + "fmt" + "unsafe" + + "github.com/taosdata/taosadapter/v3/tools" +) + +type TMQRawDataParser struct { + block unsafe.Pointer + offset uintptr +} + +func NewTMQRawDataParser() *TMQRawDataParser { + return &TMQRawDataParser{} +} + +type TMQBlockInfo struct { + RawBlock unsafe.Pointer + Precision int + Schema []*TMQRawDataSchema + TableName string +} + +type TMQRawDataSchema struct { + ColType uint8 + Flag int8 + Bytes int64 + ColID int + Name string +} + +func (p *TMQRawDataParser) getTypeSkip(t int8) (int, error) { + skip := 8 + switch t { + case 1: + case 2, 3: + skip = 16 + default: + return 0, fmt.Errorf("unknown type %d", t) + } + return skip, nil +} + +func (p *TMQRawDataParser) skipHead() error { + v := p.parseInt8() + if v >= 100 { + skip := p.parseInt32() + p.skip(int(skip)) + return nil + } + skip, err := p.getTypeSkip(v) + if err != nil { + return err + } + p.skip(skip) + v = p.parseInt8() + skip, err = p.getTypeSkip(v) + if err != nil { + return err + } + p.skip(skip) + return nil +} + +func (p *TMQRawDataParser) skip(count int) { + p.offset += uintptr(count) +} + +func (p *TMQRawDataParser) parseBlockInfos() []*TMQBlockInfo { + blockNum := p.parseInt32() + blockInfos := make([]*TMQBlockInfo, blockNum) + withTableName := p.parseBool() + withSchema := p.parseBool() + for i := int32(0); i < blockNum; i++ { + blockInfo := &TMQBlockInfo{} + blockTotalLen := p.parseVariableByteInteger() + p.skip(17) + blockInfo.Precision = int(p.parseUint8()) + blockInfo.RawBlock = tools.AddPointer(p.block, p.offset) + p.skip(blockTotalLen - 18) + if withSchema { + cols := p.parseZigzagVariableByteInteger() + //version + _ = p.parseZigzagVariableByteInteger() + + blockInfo.Schema = make([]*TMQRawDataSchema, cols) + for j := 0; j < cols; j++ { + blockInfo.Schema[j] = p.parseSchema() + } + } + if withTableName { + blockInfo.TableName = p.parseName() + } + blockInfos[i] = blockInfo + } + return blockInfos +} + +func (p *TMQRawDataParser) parseZigzagVariableByteInteger() int { + return zigzagDecode(p.parseVariableByteInteger()) +} + +func (p *TMQRawDataParser) parseBool() bool { + v := *(*int8)(tools.AddPointer(p.block, p.offset)) + p.skip(1) + return v != 0 +} + +func (p *TMQRawDataParser) parseUint8() uint8 { + v := *(*uint8)(tools.AddPointer(p.block, p.offset)) + p.skip(1) + return v +} + +func (p *TMQRawDataParser) parseInt8() int8 { + v := *(*int8)(tools.AddPointer(p.block, p.offset)) + p.skip(1) + return v +} + +func (p *TMQRawDataParser) parseInt32() int32 { + v := *(*int32)(tools.AddPointer(p.block, p.offset)) + p.skip(4) + return v +} + +func (p *TMQRawDataParser) parseSchema() *TMQRawDataSchema { + colType := p.parseUint8() + flag := p.parseInt8() + bytes := int64(p.parseZigzagVariableByteInteger()) + colID := p.parseZigzagVariableByteInteger() + name := p.parseName() + return &TMQRawDataSchema{ + ColType: colType, + Flag: flag, + Bytes: bytes, + ColID: colID, + Name: name, + } +} + +func (p *TMQRawDataParser) parseName() string { + nameLen := p.parseVariableByteInteger() + name := make([]byte, nameLen-1) + for i := 0; i < nameLen-1; i++ { + name[i] = *(*byte)(tools.AddPointer(p.block, p.offset+uintptr(i))) + } + p.skip(nameLen) + return string(name) +} + +func (p *TMQRawDataParser) Parse(block unsafe.Pointer) ([]*TMQBlockInfo, error) { + p.reset(block) + err := p.skipHead() + if err != nil { + return nil, err + } + return p.parseBlockInfos(), nil +} + +func (p *TMQRawDataParser) reset(block unsafe.Pointer) { + p.block = block + p.offset = 0 +} + +func (p *TMQRawDataParser) parseVariableByteInteger() int { + multiplier := 1 + value := 0 + for { + encodedByte := p.parseUint8() + value += int(encodedByte&127) * multiplier + if encodedByte&128 == 0 { + break + } + multiplier *= 128 + } + return value +} + +func zigzagDecode(n int) int { + return (n >> 1) ^ (-(n & 1)) +} diff --git a/driver/common/parser/raw_test.go b/driver/common/parser/raw_test.go new file mode 100644 index 00000000..521a6260 --- /dev/null +++ b/driver/common/parser/raw_test.go @@ -0,0 +1,1049 @@ +package parser + +import ( + "database/sql/driver" + "fmt" + "testing" + "time" + "unsafe" + + "github.com/stretchr/testify/assert" +) + +func TestParse(t *testing.T) { + data := []byte{ + 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x01, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x01, 0x00, 0x00, 0x00, + + 0x01, + 0x01, + + 0xc5, 0x01, + + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + + 0x02, + + 0x02, 0x00, 0x00, 0x00, + 0xb3, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x09, 0x08, 0x00, 0x00, 0x00, + 0x04, 0x04, 0x00, 0x00, 0x00, + 0x06, 0x04, 0x00, 0x00, 0x00, + 0x08, 0x82, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x5c, 0x00, 0x00, 0x00, + + 0x00, + 0xc0, 0xed, 0x82, 0x05, 0xc3, 0x1b, 0xab, 0x17, + + 0x80, + 0x00, 0x00, 0x00, 0x00, + + 0x80, + 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, + 0x5a, 0x00, + 0x61, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x61, + + 0x08, + 0x00, + + 0x09, + 0x01, + 0x10, + 0x02, + 0x03, + 0x74, 0x73, 0x00, + + 0x04, + 0x01, + 0x08, + 0x04, + 0x03, + 0x63, 0x31, 0x00, + + 0x06, + 0x01, + 0x08, + 0x06, + 0x03, + 0x63, 0x32, 0x00, + + 0x08, + 0x01, + 0x84, 0x02, + 0x08, + 0x03, 0x63, 0x33, 0x00, + + 0x05, + 0x63, 0x74, 0x62, 0x30, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } + parser := NewTMQRawDataParser() + blockInfos, err := parser.Parse(unsafe.Pointer(&data[0])) + assert.NoError(t, err) + assert.Equal(t, 1, len(blockInfos)) + assert.Equal(t, 2, blockInfos[0].Precision) + assert.Equal(t, 4, len(blockInfos[0].Schema)) + assert.Equal(t, []*TMQRawDataSchema{ + { + ColType: 9, + Flag: 1, + Bytes: 8, + ColID: 1, + Name: "ts", + }, + { + ColType: 4, + Flag: 1, + Bytes: 4, + ColID: 2, + Name: "c1", + }, + { + ColType: 6, + Flag: 1, + Bytes: 4, + ColID: 3, + Name: "c2", + }, + { + ColType: 8, + Flag: 1, + Bytes: 130, + ColID: 4, + Name: "c3", + }, + }, blockInfos[0].Schema) + assert.Equal(t, "ctb0", blockInfos[0].TableName) +} + +func TestParseTwoBlock(t *testing.T) { + data := []byte{ + 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x02, 0x00, 0x00, 0x00, + + 0x00, // withTbName false + 0x01, // withSchema true + + 0x60, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, + + 0x02, 0x00, 0x00, 0x00, + 0x4e, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x09, 0x08, 0x00, 0x00, 0x00, + 0x04, 0x04, 0x00, 0x00, 0x00, + 0x08, 0x0c, 0x00, 0x00, 0x00, + + 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + + 0x00, + 0xf8, 0x6b, 0x75, 0x35, 0x8d, 0x01, 0x00, 0x00, + + 0x00, + 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, + 0x63, 0x74, 0x30, + + 0x06, + 0x00, + + 0x09, + 0x00, + 0x10, + 0x02, + 0x03, + 0x74, 0x73, 0x00, + + 0x04, + 0x00, + 0x08, + 0x04, + 0x02, + 0x76, 0x00, + + 0x08, + 0x00, + 0x18, + 0x06, + 0x02, + 0x6e, 0x00, + + 0x60, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + + 0x00, + + 0x02, 0x00, 0x00, 0x00, + 0x4e, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x09, 0x08, 0x00, 0x00, 0x00, + 0x04, 0x04, 0x00, 0x00, 0x00, + 0x08, 0x0c, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + + 0x00, + 0xf9, 0x6b, 0x75, 0x35, + 0x8d, 0x01, 0x00, 0x00, + + 0x00, + 0x01, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, + 0x63, 0x74, 0x31, + + 0x06, + 0x00, + + 0x09, + 0x00, + 0x10, + 0x02, + 0x03, + 0x74, 0x73, 0x00, + + 0x04, + 0x00, + 0x08, + 0x04, + 0x02, + 0x76, 0x00, + + 0x08, + 0x00, + 0x18, + 0x06, + 0x02, + 0x6e, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } + parser := NewTMQRawDataParser() + blockInfos, err := parser.Parse(unsafe.Pointer(&data[0])) + assert.NoError(t, err) + assert.Equal(t, 2, len(blockInfos)) + assert.Equal(t, 0, blockInfos[0].Precision) + assert.Equal(t, 0, blockInfos[1].Precision) + assert.Equal(t, 3, len(blockInfos[0].Schema)) + assert.Equal(t, []*TMQRawDataSchema{ + { + ColType: 9, + Flag: 0, + Bytes: 8, + ColID: 1, + Name: "ts", + }, + { + ColType: 4, + Flag: 0, + Bytes: 4, + ColID: 2, + Name: "v", + }, + { + ColType: 8, + Flag: 0, + Bytes: 12, + ColID: 3, + Name: "n", + }, + }, blockInfos[0].Schema) + assert.Equal(t, []*TMQRawDataSchema{ + { + ColType: 9, + Flag: 0, + Bytes: 8, + ColID: 1, + Name: "ts", + }, + { + ColType: 4, + Flag: 0, + Bytes: 4, + ColID: 2, + Name: "v", + }, + { + ColType: 8, + Flag: 0, + Bytes: 12, + ColID: 3, + Name: "n", + }, + }, blockInfos[1].Schema) + assert.Equal(t, "", blockInfos[0].TableName) + assert.Equal(t, "", blockInfos[1].TableName) +} + +func TestParseTenBlock(t *testing.T) { + data := []byte{ + 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, + 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, + 0x01, + 0x01, + + // block1 + 0x4e, + + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, + + 0x02, 0x00, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x09, 0x08, 0x00, 0x00, 0x00, + 0x04, 0x04, 0x00, 0x00, 0x00, + + 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + + 0x00, + 0x52, 0xed, 0x5b, 0x3a, 0x8d, 0x01, 0x00, 0x00, + + 0x00, + 0x01, 0x00, 0x00, 0x00, + + 0x04, + 0x00, + + 0x09, + 0x01, + 0x10, + 0x02, + 0x03, + 0x74, 0x73, 0x00, + + 0x04, + 0x01, + 0x08, + 0x04, + 0x02, + 0x76, 0x00, + + 0x03, + 0x74, 0x31, 0x00, + + //block2 + 0x4e, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + + 0x00, + + 0x02, 0x00, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x09, 0x08, 0x00, 0x00, 0x00, + 0x04, 0x04, 0x00, 0x00, 0x00, + + 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x00, + 0x52, 0xed, 0x5b, 0x3a, 0x8d, 0x01, 0x00, 0x00, + 0x00, + 0x02, 0x00, 0x00, 0x00, + + 0x04, + 0x00, + + 0x09, + 0x01, + 0x10, + 0x02, + 0x03, + 0x74, 0x73, 0x00, + + 0x04, + 0x01, + 0x08, + 0x04, + 0x02, + 0x76, 0x00, + + 0x03, + 0x74, 0x32, 0x00, + + //block3 + 0x4e, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + + 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x09, 0x08, 0x00, 0x00, 0x00, + 0x04, 0x04, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + + 0x00, + 0x52, 0xed, 0x5b, 0x3a, 0x8d, 0x01, 0x00, 0x00, + + 0x00, + 0x03, 0x00, 0x00, 0x00, + + 0x04, + 0x00, + + 0x09, + 0x01, + 0x10, + 0x02, + 0x03, + 0x74, 0x73, 0x00, + + 0x04, + 0x01, + 0x08, + 0x04, + 0x02, + 0x76, 0x00, + + 0x03, + 0x74, 0x33, 0x00, + + //block4 + 0x4e, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, + + 0x02, 0x00, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x09, 0x08, 0x00, 0x00, 0x00, + 0x04, 0x04, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + + 0x00, + 0x52, 0xed, 0x5b, 0x3a, 0x8d, 0x01, 0x00, 0x00, + + 0x00, + 0x04, 0x00, 0x00, 0x00, + + 0x04, + 0x00, + + 0x09, + 0x01, + 0x10, + 0x02, + 0x03, + 0x74, 0x73, 0x00, + + 0x04, + 0x01, + 0x08, + 0x04, + 0x02, + 0x76, 0x00, + + 0x03, + 0x74, 0x34, 0x00, + + // block5 + 0x4e, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, + + 0x02, 0x00, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x09, 0x08, 0x00, 0x00, 0x00, + 0x04, 0x04, 0x00, 0x00, 0x00, + + 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + + 0x00, + 0x52, 0xed, 0x5b, 0x3a, 0x8d, 0x01, 0x00, 0x00, + + 0x00, + 0x05, 0x00, 0x00, 0x00, + + 0x04, + 0x00, + + 0x09, + 0x01, + 0x10, + 0x02, + 0x03, + 0x74, 0x73, 0x00, + + 0x04, + 0x01, + 0x08, + 0x04, + 0x02, + 0x76, 0x00, + + 0x03, + 0x74, 0x35, 0x00, + + //block6 + 0x4e, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, + + 0x02, 0x00, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x09, 0x08, 0x00, 0x00, 0x00, + 0x04, 0x04, 0x00, 0x00, 0x00, + + 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + + 0x00, + 0x52, 0xed, 0x5b, 0x3a, 0x8d, 0x01, 0x00, 0x00, + + 0x00, + 0x06, 0x00, 0x00, 0x00, + + 0x04, + 0x00, + + 0x09, + 0x01, + 0x10, + 0x02, + 0x03, + 0x74, 0x73, 0x00, + + 0x04, + 0x01, + 0x08, + 0x04, + 0x02, + 0x76, 0x00, + + 0x03, + 0x74, 0x36, 0x00, + + //block7 + 0x4e, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, + + 0x02, 0x00, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x09, 0x08, 0x00, 0x00, 0x00, + 0x04, 0x04, 0x00, 0x00, 0x00, + + 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + + 0x00, + 0x52, 0xed, 0x5b, 0x3a, 0x8d, 0x01, 0x00, 0x00, + + 0x00, + 0x07, 0x00, 0x00, 0x00, + + 0x04, + 0x00, + + 0x09, + 0x01, + 0x10, + 0x02, + 0x03, + 0x74, 0x73, 0x00, + + 0x04, + 0x01, + 0x08, + 0x04, + 0x02, + 0x76, 0x00, + + 0x03, + 0x74, 0x37, 0x00, + + //block8 + 0x4e, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, + + 0x02, 0x00, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x09, 0x08, 0x00, 0x00, 0x00, + 0x04, 0x04, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + + 0x00, + 0x52, 0xed, 0x5b, 0x3a, 0x8d, 0x01, 0x00, 0x00, + + 0x00, + 0x08, 0x00, 0x00, 0x00, + + 0x04, + 0x00, + + 0x09, + 0x01, + 0x10, + 0x02, + 0x03, + 0x74, 0x73, 0x00, + + 0x04, + 0x01, + 0x08, + 0x04, + 0x02, + 0x76, 0x00, + + 0x03, + 0x74, 0x38, 0x00, + + //block9 + 0x4e, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, + + 0x02, 0x00, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x09, 0x08, 0x00, 0x00, 0x00, + 0x04, 0x04, 0x00, 0x00, 0x00, + + 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + + 0x00, + 0x52, 0xed, 0x5b, 0x3a, 0x8d, 0x01, 0x00, 0x00, + + 0x00, + 0x09, 0x00, 0x00, 0x00, + + 0x04, + 0x00, + + 0x09, + 0x01, + 0x10, + 0x02, + 0x03, + 0x74, 0x73, 0x00, + + 0x04, + 0x01, + 0x08, + 0x04, + 0x02, + 0x76, 0x00, + + 0x03, + 0x74, 0x39, 0x00, + + //block10 + 0x4e, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, + + 0x02, 0x00, 0x00, 0x00, + 0x3c, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x09, 0x08, 0x00, 0x00, 0x00, + 0x04, 0x04, 0x00, 0x00, 0x00, + + 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + + 0x00, + 0x52, 0xed, 0x5b, 0x3a, 0x8d, 0x01, 0x00, 0x00, + 0x00, + 0x0a, 0x00, 0x00, 0x00, + + 0x04, + 0x00, + + 0x09, + 0x01, + 0x10, + 0x02, + 0x03, + 0x74, 0x73, 0x00, + + 0x04, + 0x01, + 0x08, + 0x04, + 0x02, + 0x76, 0x00, + 0x04, + 0x74, 0x31, 0x30, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } + parser := NewTMQRawDataParser() + blockInfos, err := parser.Parse(unsafe.Pointer(&data[0])) + assert.NoError(t, err) + assert.Equal(t, 10, len(blockInfos)) + for i := 0; i < 10; i++ { + assert.Equal(t, 0, blockInfos[i].Precision) + assert.Equal(t, 2, len(blockInfos[i].Schema)) + assert.Equal(t, []*TMQRawDataSchema{ + { + ColType: 9, + Flag: 1, + Bytes: 8, + ColID: 1, + Name: "ts", + }, + { + ColType: 4, + Flag: 1, + Bytes: 4, + ColID: 2, + Name: "v", + }, + }, blockInfos[i].Schema) + assert.Equal(t, fmt.Sprintf("t%d", i+1), blockInfos[i].TableName) + value := ReadBlockSimple(blockInfos[i].RawBlock, blockInfos[i].Precision) + ts := time.Unix(0, 1706081119570000000).Local() + assert.Equal(t, [][]driver.Value{{ts, int32(i + 1)}}, value) + } +} + +func TestVersion100Block(t *testing.T) { + data := []byte{ + 0x64, //version + 0x12, 0x00, 0x00, 0x00, // skip 18 bytes + 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, //block count 1 + + 0x01, // with table name + 0x01, // with schema + + 0x92, 0x02, // block length 274 + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, + + 0x02, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, // 256 + 0x01, 0x00, 0x00, 0x00, // rows + 0x0e, 0x00, 0x00, 0x00, // cols + 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x09, 0x08, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x01, 0x00, 0x00, 0x00, + 0x03, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x08, 0x00, 0x00, 0x00, + 0x0b, 0x01, 0x00, 0x00, 0x00, + 0x0c, 0x02, 0x00, 0x00, 0x00, + 0x0d, 0x04, 0x00, 0x00, 0x00, + 0x0e, 0x08, 0x00, 0x00, 0x00, + 0x06, 0x04, 0x00, 0x00, 0x00, + 0x07, 0x08, 0x00, 0x00, 0x00, + 0x08, 0x16, 0x00, 0x00, 0x00, + 0x0a, 0x52, 0x00, 0x00, 0x00, + + 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, + + 0x00, + 0x9e, 0x37, 0x6a, 0x04, 0x8f, 0x01, 0x00, 0x00, + + 0x00, + 0x01, + + 0x00, + 0x02, + + 0x00, + 0x03, 0x00, + + 0x00, + 0x04, 0x00, 0x00, 0x00, + + 0x00, + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, + 0x06, + + 0x00, + 0x07, 0x00, + + 0x00, + 0x08, 0x00, 0x00, 0x00, + + 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, + 0xcf, 0xf7, 0x21, 0x41, + + 0x00, + 0xe5, 0xd0, 0x22, 0xdb, 0xf9, 0x3e, 0x26, 0x40, + + 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, + 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, + + 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, + 0x6e, 0x00, 0x00, 0x00, + 0x63, 0x00, 0x00, 0x00, + 0x68, 0x00, 0x00, 0x00, + 0x61, 0x00, 0x00, 0x00, + 0x72, 0x00, 0x00, 0x00, + + 0x00, // + + 0x1c, // cols 14 + 0x00, // version + + // col meta + 0x09, 0x01, 0x10, 0x02, 0x03, 0x74, 0x73, 0x00, + 0x01, 0x01, 0x02, 0x04, 0x03, 0x63, 0x31, 0x00, + 0x02, 0x01, 0x02, 0x06, 0x03, 0x63, 0x32, 0x00, + 0x03, 0x01, 0x04, 0x08, 0x03, 0x63, 0x33, 0x00, + 0x04, 0x01, 0x08, 0x0a, 0x03, 0x63, 0x34, 0x00, + 0x05, 0x01, 0x10, 0x0c, 0x03, 0x63, 0x35, 0x00, + 0x0b, 0x01, 0x02, 0x0e, 0x03, 0x63, 0x36, 0x00, + 0x0c, 0x01, 0x04, 0x10, 0x03, 0x63, 0x37, 0x00, + 0x0d, 0x01, 0x08, 0x12, 0x03, 0x63, 0x38, 0x00, + 0x0e, 0x01, 0x10, 0x14, 0x03, 0x63, 0x39, 0x00, + 0x06, 0x01, 0x08, 0x16, 0x04, 0x63, 0x31, 0x30, 0x00, + 0x07, 0x01, 0x10, 0x18, 0x04, 0x63, 0x31, 0x31, 0x00, + 0x08, 0x01, 0x2c, 0x1a, 0x04, 0x63, 0x31, 0x32, 0x00, + 0x0a, 0x01, 0xa4, 0x01, 0x1c, 0x04, 0x63, 0x31, 0x33, 0x00, + + 0x06, // table name + 0x74, 0x5f, 0x61, 0x6c, 0x6c, 0x00, + // sleep time + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } + parser := NewTMQRawDataParser() + blockInfos, err := parser.Parse(unsafe.Pointer(&data[0])) + assert.NoError(t, err) + assert.Equal(t, 1, len(blockInfos)) + assert.Equal(t, 0, blockInfos[0].Precision) + assert.Equal(t, 14, len(blockInfos[0].Schema)) + assert.Equal(t, []*TMQRawDataSchema{ + { + ColType: 9, + Flag: 1, + Bytes: 8, + ColID: 1, + Name: "ts", + }, + { + ColType: 1, + Flag: 1, + Bytes: 1, + ColID: 2, + Name: "c1", + }, + { + ColType: 2, + Flag: 1, + Bytes: 1, + ColID: 3, + Name: "c2", + }, + { + ColType: 3, + Flag: 1, + Bytes: 2, + ColID: 4, + Name: "c3", + }, + { + ColType: 4, + Flag: 1, + Bytes: 4, + ColID: 5, + Name: "c4", + }, + { + ColType: 5, + Flag: 1, + Bytes: 8, + ColID: 6, + Name: "c5", + }, + { + ColType: 11, + Flag: 1, + Bytes: 1, + ColID: 7, + Name: "c6", + }, + { + ColType: 12, + Flag: 1, + Bytes: 2, + ColID: 8, + Name: "c7", + }, + { + ColType: 13, + Flag: 1, + Bytes: 4, + ColID: 9, + Name: "c8", + }, + { + ColType: 14, + Flag: 1, + Bytes: 8, + ColID: 10, + Name: "c9", + }, + { + ColType: 6, + Flag: 1, + Bytes: 4, + ColID: 11, + Name: "c10", + }, + { + ColType: 7, + Flag: 1, + Bytes: 8, + ColID: 12, + Name: "c11", + }, + { + ColType: 8, + Flag: 1, + Bytes: 22, + ColID: 13, + Name: "c12", + }, + { + ColType: 10, + Flag: 1, + Bytes: 82, + ColID: 14, + Name: "c13", + }, + }, blockInfos[0].Schema) + assert.Equal(t, "t_all", blockInfos[0].TableName) + value := ReadBlockSimple(blockInfos[0].RawBlock, blockInfos[0].Precision) + expect := []driver.Value{ + time.Unix(0, 1713766021022000000).Local(), + true, + int8(2), + int16(3), + int32(4), + int64(5), + uint8(6), + uint16(7), + uint32(8), + uint64(9), + float32(10.123), + float64(11.123), + "binary", + "nchar", + } + assert.Equal(t, [][]driver.Value{expect}, value) +} diff --git a/driver/common/serializer/block.go b/driver/common/serializer/block.go new file mode 100644 index 00000000..ffaf7798 --- /dev/null +++ b/driver/common/serializer/block.go @@ -0,0 +1,552 @@ +package serializer + +import ( + "bytes" + "errors" + "math" + + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/common/param" + taosTypes "github.com/taosdata/taosadapter/v3/driver/types" +) + +const ( + Int16Size = int(common.Int16Size) + Int32Size = int(common.Int32Size) + Int64Size = int(common.Int64Size) + UInt16Size = int(common.UInt16Size) + UInt32Size = int(common.UInt32Size) + UInt64Size = int(common.UInt64Size) + Float32Size = int(common.Float32Size) + Float64Size = int(common.Float64Size) +) + +func BitmapLen(n int) int { + return ((n) + ((1 << 3) - 1)) >> 3 +} + +func BitPos(n int) int { + return n & ((1 << 3) - 1) +} + +func CharOffset(n int) int { + return n >> 3 +} + +func BMSetNull(c byte, n int) byte { + return c + (1 << (7 - BitPos(n))) +} + +var ErrColumnNumberNotMatch = errors.New("number of columns does not match") +var ErrDataTypeWrong = errors.New("wrong data type") + +func SerializeRawBlock(params []*param.Param, colType *param.ColumnType) ([]byte, error) { + columns := len(params) + rows := len(params[0].GetValues()) + colTypes, err := colType.GetValue() + if err != nil { + return nil, err + } + if len(colTypes) != columns { + return nil, ErrColumnNumberNotMatch + } + var block []byte + //version int32 + block = appendUint32(block, uint32(1)) + //length int32 + block = appendUint32(block, uint32(0)) + //rows int32 + block = appendUint32(block, uint32(rows)) + //columns int32 + block = appendUint32(block, uint32(columns)) + //flagSegment int32 + block = appendUint32(block, uint32(0)) + //groupID uint64 + block = appendUint64(block, uint64(0)) + colInfoData := make([]byte, 0, 5*columns) + lengthData := make([]byte, 0, 4*columns) + bitMapLen := BitmapLen(rows) + var data []byte + //colInfo(type+bytes) (int8+int32) * columns + buffer := bytes.NewBuffer(block) + for colIndex := 0; colIndex < columns; colIndex++ { + switch colTypes[colIndex].Type { + case taosTypes.TaosBoolType: + colInfoData = append(colInfoData, common.TSDB_DATA_TYPE_BOOL) + length := common.TypeLengthMap[common.TSDB_DATA_TYPE_BOOL] + colInfoData = appendUint32(colInfoData, uint32(length)) + lengthData = appendUint32(lengthData, uint32(length*rows)) + dataTmp := make([]byte, bitMapLen+rows) + rowData := params[colIndex].GetValues() + for rowIndex := 0; rowIndex < rows; rowIndex++ { + if rowData[rowIndex] == nil { + charOffset := CharOffset(rowIndex) + dataTmp[charOffset] = BMSetNull(dataTmp[charOffset], rowIndex) + } else { + v, is := rowData[rowIndex].(taosTypes.TaosBool) + if !is { + return nil, ErrDataTypeWrong + } + if v { + dataTmp[rowIndex+bitMapLen] = 1 + } + } + } + data = append(data, dataTmp...) + case taosTypes.TaosTinyintType: + colInfoData = append(colInfoData, common.TSDB_DATA_TYPE_TINYINT) + length := common.TypeLengthMap[common.TSDB_DATA_TYPE_TINYINT] + colInfoData = appendUint32(colInfoData, uint32(length)) + lengthData = appendUint32(lengthData, uint32(length*rows)) + dataTmp := make([]byte, bitMapLen+rows) + rowData := params[colIndex].GetValues() + for rowIndex := 0; rowIndex < rows; rowIndex++ { + if rowData[rowIndex] == nil { + charOffset := CharOffset(rowIndex) + dataTmp[charOffset] = BMSetNull(dataTmp[charOffset], rowIndex) + } else { + v, is := rowData[rowIndex].(taosTypes.TaosTinyint) + if !is { + return nil, ErrDataTypeWrong + } + dataTmp[rowIndex+bitMapLen] = byte(v) + } + } + data = append(data, dataTmp...) + case taosTypes.TaosSmallintType: + colInfoData = append(colInfoData, common.TSDB_DATA_TYPE_SMALLINT) + length := common.TypeLengthMap[common.TSDB_DATA_TYPE_SMALLINT] + colInfoData = appendUint32(colInfoData, uint32(length)) + lengthData = appendUint32(lengthData, uint32(length*rows)) + dataTmp := make([]byte, bitMapLen+rows*Int16Size) + rowData := params[colIndex].GetValues() + for rowIndex := 0; rowIndex < rows; rowIndex++ { + if rowData[rowIndex] == nil { + charOffset := CharOffset(rowIndex) + dataTmp[charOffset] = BMSetNull(dataTmp[charOffset], rowIndex) + } else { + v, is := rowData[rowIndex].(taosTypes.TaosSmallint) + if !is { + return nil, ErrDataTypeWrong + } + offset := rowIndex*Int16Size + bitMapLen + dataTmp[offset] = byte(v) + dataTmp[offset+1] = byte(v >> 8) + } + } + data = append(data, dataTmp...) + case taosTypes.TaosIntType: + colInfoData = append(colInfoData, common.TSDB_DATA_TYPE_INT) + length := common.TypeLengthMap[common.TSDB_DATA_TYPE_INT] + colInfoData = appendUint32(colInfoData, uint32(length)) + lengthData = appendUint32(lengthData, uint32(length*rows)) + dataTmp := make([]byte, bitMapLen+rows*Int32Size) + rowData := params[colIndex].GetValues() + for rowIndex := 0; rowIndex < rows; rowIndex++ { + if rowData[rowIndex] == nil { + charOffset := CharOffset(rowIndex) + dataTmp[charOffset] = BMSetNull(dataTmp[charOffset], rowIndex) + } else { + v, is := rowData[rowIndex].(taosTypes.TaosInt) + if !is { + return nil, ErrDataTypeWrong + } + offset := rowIndex*Int32Size + bitMapLen + dataTmp[offset] = byte(v) + dataTmp[offset+1] = byte(v >> 8) + dataTmp[offset+2] = byte(v >> 16) + dataTmp[offset+3] = byte(v >> 24) + } + } + data = append(data, dataTmp...) + case taosTypes.TaosBigintType: + colInfoData = append(colInfoData, common.TSDB_DATA_TYPE_BIGINT) + length := common.TypeLengthMap[common.TSDB_DATA_TYPE_BIGINT] + colInfoData = appendUint32(colInfoData, uint32(length)) + lengthData = appendUint32(lengthData, uint32(length*rows)) + dataTmp := make([]byte, bitMapLen+rows*Int64Size) + rowData := params[colIndex].GetValues() + for rowIndex := 0; rowIndex < rows; rowIndex++ { + if rowData[rowIndex] == nil { + charOffset := CharOffset(rowIndex) + dataTmp[charOffset] = BMSetNull(dataTmp[charOffset], rowIndex) + } else { + v, is := rowData[rowIndex].(taosTypes.TaosBigint) + if !is { + return nil, ErrDataTypeWrong + } + offset := rowIndex*Int64Size + bitMapLen + dataTmp[offset] = byte(v) + dataTmp[offset+1] = byte(v >> 8) + dataTmp[offset+2] = byte(v >> 16) + dataTmp[offset+3] = byte(v >> 24) + dataTmp[offset+4] = byte(v >> 32) + dataTmp[offset+5] = byte(v >> 40) + dataTmp[offset+6] = byte(v >> 48) + dataTmp[offset+7] = byte(v >> 56) + } + } + data = append(data, dataTmp...) + case taosTypes.TaosUTinyintType: + colInfoData = append(colInfoData, common.TSDB_DATA_TYPE_UTINYINT) + length := common.TypeLengthMap[common.TSDB_DATA_TYPE_UTINYINT] + colInfoData = appendUint32(colInfoData, uint32(length)) + lengthData = appendUint32(lengthData, uint32(length*rows)) + dataTmp := make([]byte, bitMapLen+rows) + rowData := params[colIndex].GetValues() + for rowIndex := 0; rowIndex < rows; rowIndex++ { + if rowData[rowIndex] == nil { + charOffset := CharOffset(rowIndex) + dataTmp[charOffset] = BMSetNull(dataTmp[charOffset], rowIndex) + } else { + v, is := rowData[rowIndex].(taosTypes.TaosUTinyint) + if !is { + return nil, ErrDataTypeWrong + } + dataTmp[rowIndex+bitMapLen] = uint8(v) + } + } + data = append(data, dataTmp...) + case taosTypes.TaosUSmallintType: + colInfoData = append(colInfoData, common.TSDB_DATA_TYPE_USMALLINT) + length := common.TypeLengthMap[common.TSDB_DATA_TYPE_USMALLINT] + colInfoData = appendUint32(colInfoData, uint32(length)) + lengthData = appendUint32(lengthData, uint32(length*rows)) + dataTmp := make([]byte, bitMapLen+rows*UInt16Size) + rowData := params[colIndex].GetValues() + for rowIndex := 0; rowIndex < rows; rowIndex++ { + if rowData[rowIndex] == nil { + charOffset := CharOffset(rowIndex) + dataTmp[charOffset] = BMSetNull(dataTmp[charOffset], rowIndex) + } else { + v, is := rowData[rowIndex].(taosTypes.TaosUSmallint) + if !is { + return nil, ErrDataTypeWrong + } + offset := rowIndex*UInt16Size + bitMapLen + dataTmp[offset] = byte(v) + dataTmp[offset+1] = byte(v >> 8) + } + } + data = append(data, dataTmp...) + case taosTypes.TaosUIntType: + colInfoData = append(colInfoData, common.TSDB_DATA_TYPE_UINT) + length := common.TypeLengthMap[common.TSDB_DATA_TYPE_UINT] + colInfoData = appendUint32(colInfoData, uint32(length)) + lengthData = appendUint32(lengthData, uint32(length*rows)) + dataTmp := make([]byte, bitMapLen+rows*UInt32Size) + rowData := params[colIndex].GetValues() + for rowIndex := 0; rowIndex < rows; rowIndex++ { + if rowData[rowIndex] == nil { + charOffset := CharOffset(rowIndex) + dataTmp[charOffset] = BMSetNull(dataTmp[charOffset], rowIndex) + } else { + v, is := rowData[rowIndex].(taosTypes.TaosUInt) + if !is { + return nil, ErrDataTypeWrong + } + offset := rowIndex*UInt32Size + bitMapLen + dataTmp[offset] = byte(v) + dataTmp[offset+1] = byte(v >> 8) + dataTmp[offset+2] = byte(v >> 16) + dataTmp[offset+3] = byte(v >> 24) + } + } + data = append(data, dataTmp...) + + case taosTypes.TaosUBigintType: + colInfoData = append(colInfoData, common.TSDB_DATA_TYPE_UBIGINT) + length := common.TypeLengthMap[common.TSDB_DATA_TYPE_UBIGINT] + colInfoData = appendUint32(colInfoData, uint32(length)) + lengthData = appendUint32(lengthData, uint32(length*rows)) + dataTmp := make([]byte, bitMapLen+rows*UInt64Size) + rowData := params[colIndex].GetValues() + for rowIndex := 0; rowIndex < rows; rowIndex++ { + if rowData[rowIndex] == nil { + charOffset := CharOffset(rowIndex) + dataTmp[charOffset] = BMSetNull(dataTmp[charOffset], rowIndex) + } else { + v, is := rowData[rowIndex].(taosTypes.TaosUBigint) + if !is { + return nil, ErrDataTypeWrong + } + offset := rowIndex*UInt64Size + bitMapLen + dataTmp[offset] = byte(v) + dataTmp[offset+1] = byte(v >> 8) + dataTmp[offset+2] = byte(v >> 16) + dataTmp[offset+3] = byte(v >> 24) + dataTmp[offset+4] = byte(v >> 32) + dataTmp[offset+5] = byte(v >> 40) + dataTmp[offset+6] = byte(v >> 48) + dataTmp[offset+7] = byte(v >> 56) + } + } + data = append(data, dataTmp...) + + case taosTypes.TaosFloatType: + colInfoData = append(colInfoData, common.TSDB_DATA_TYPE_FLOAT) + length := common.TypeLengthMap[common.TSDB_DATA_TYPE_FLOAT] + colInfoData = appendUint32(colInfoData, uint32(length)) + lengthData = appendUint32(lengthData, uint32(length*rows)) + dataTmp := make([]byte, bitMapLen+rows*Float32Size) + rowData := params[colIndex].GetValues() + for rowIndex := 0; rowIndex < rows; rowIndex++ { + if rowData[rowIndex] == nil { + charOffset := CharOffset(rowIndex) + dataTmp[charOffset] = BMSetNull(dataTmp[charOffset], rowIndex) + } else { + v, is := rowData[rowIndex].(taosTypes.TaosFloat) + if !is { + return nil, ErrDataTypeWrong + } + offset := rowIndex*Float32Size + bitMapLen + vv := math.Float32bits(float32(v)) + dataTmp[offset] = byte(vv) + dataTmp[offset+1] = byte(vv >> 8) + dataTmp[offset+2] = byte(vv >> 16) + dataTmp[offset+3] = byte(vv >> 24) + } + } + data = append(data, dataTmp...) + + case taosTypes.TaosDoubleType: + colInfoData = append(colInfoData, common.TSDB_DATA_TYPE_DOUBLE) + length := common.TypeLengthMap[common.TSDB_DATA_TYPE_DOUBLE] + colInfoData = appendUint32(colInfoData, uint32(length)) + lengthData = appendUint32(lengthData, uint32(length*rows)) + dataTmp := make([]byte, bitMapLen+rows*Float64Size) + rowData := params[colIndex].GetValues() + for rowIndex := 0; rowIndex < rows; rowIndex++ { + if rowData[rowIndex] == nil { + charOffset := CharOffset(rowIndex) + dataTmp[charOffset] = BMSetNull(dataTmp[charOffset], rowIndex) + } else { + v, is := rowData[rowIndex].(taosTypes.TaosDouble) + if !is { + return nil, ErrDataTypeWrong + } + offset := rowIndex*Float64Size + bitMapLen + vv := math.Float64bits(float64(v)) + dataTmp[offset] = byte(vv) + dataTmp[offset+1] = byte(vv >> 8) + dataTmp[offset+2] = byte(vv >> 16) + dataTmp[offset+3] = byte(vv >> 24) + dataTmp[offset+4] = byte(vv >> 32) + dataTmp[offset+5] = byte(vv >> 40) + dataTmp[offset+6] = byte(vv >> 48) + dataTmp[offset+7] = byte(vv >> 56) + } + } + data = append(data, dataTmp...) + case taosTypes.TaosBinaryType: + colInfoData = append(colInfoData, common.TSDB_DATA_TYPE_BINARY) + colInfoData = appendUint32(colInfoData, uint32(0)) + length := 0 + dataTmp := make([]byte, Int32Size*rows) + rowData := params[colIndex].GetValues() + for rowIndex := 0; rowIndex < rows; rowIndex++ { + offset := Int32Size * rowIndex + if rowData[rowIndex] == nil { + for i := 0; i < Int32Size; i++ { + // -1 + dataTmp[offset+i] = byte(255) + } + } else { + v, is := rowData[rowIndex].(taosTypes.TaosBinary) + if !is { + return nil, ErrDataTypeWrong + } + for i := 0; i < Int32Size; i++ { + dataTmp[offset+i] = byte(length >> (8 * i)) + } + dataTmp = appendUint16(dataTmp, uint16(len(v))) + dataTmp = append(dataTmp, v...) + length += len(v) + Int16Size + } + } + lengthData = appendUint32(lengthData, uint32(length)) + data = append(data, dataTmp...) + case taosTypes.TaosVarBinaryType: + colInfoData = append(colInfoData, common.TSDB_DATA_TYPE_VARBINARY) + colInfoData = appendUint32(colInfoData, uint32(0)) + length := 0 + dataTmp := make([]byte, Int32Size*rows) + rowData := params[colIndex].GetValues() + for rowIndex := 0; rowIndex < rows; rowIndex++ { + offset := Int32Size * rowIndex + if rowData[rowIndex] == nil { + for i := 0; i < Int32Size; i++ { + // -1 + dataTmp[offset+i] = byte(255) + } + } else { + v, is := rowData[rowIndex].(taosTypes.TaosVarBinary) + if !is { + return nil, ErrDataTypeWrong + } + for i := 0; i < Int32Size; i++ { + dataTmp[offset+i] = byte(length >> (8 * i)) + } + dataTmp = appendUint16(dataTmp, uint16(len(v))) + dataTmp = append(dataTmp, v...) + length += len(v) + Int16Size + } + } + lengthData = appendUint32(lengthData, uint32(length)) + data = append(data, dataTmp...) + case taosTypes.TaosGeometryType: + colInfoData = append(colInfoData, common.TSDB_DATA_TYPE_GEOMETRY) + colInfoData = appendUint32(colInfoData, uint32(0)) + length := 0 + dataTmp := make([]byte, Int32Size*rows) + rowData := params[colIndex].GetValues() + for rowIndex := 0; rowIndex < rows; rowIndex++ { + offset := Int32Size * rowIndex + if rowData[rowIndex] == nil { + for i := 0; i < Int32Size; i++ { + // -1 + dataTmp[offset+i] = byte(255) + } + } else { + v, is := rowData[rowIndex].(taosTypes.TaosGeometry) + if !is { + return nil, ErrDataTypeWrong + } + for i := 0; i < Int32Size; i++ { + dataTmp[offset+i] = byte(length >> (8 * i)) + } + dataTmp = appendUint16(dataTmp, uint16(len(v))) + dataTmp = append(dataTmp, v...) + length += len(v) + Int16Size + } + } + lengthData = appendUint32(lengthData, uint32(length)) + data = append(data, dataTmp...) + case taosTypes.TaosNcharType: + colInfoData = append(colInfoData, common.TSDB_DATA_TYPE_NCHAR) + colInfoData = appendUint32(colInfoData, uint32(0)) + length := 0 + dataTmp := make([]byte, Int32Size*rows) + rowData := params[colIndex].GetValues() + for rowIndex := 0; rowIndex < rows; rowIndex++ { + offset := Int32Size * rowIndex + if rowData[rowIndex] == nil { + for i := 0; i < Int32Size; i++ { + // -1 + dataTmp[offset+i] = byte(255) + } + } else { + v, is := rowData[rowIndex].(taosTypes.TaosNchar) + if !is { + return nil, ErrDataTypeWrong + } + for i := 0; i < Int32Size; i++ { + dataTmp[offset+i] = byte(length >> (8 * i)) + } + rs := []rune(v) + dataTmp = appendUint16(dataTmp, uint16(len(rs)*4)) + for _, r := range rs { + dataTmp = appendUint32(dataTmp, uint32(r)) + } + length += len(rs)*4 + Int16Size + } + } + lengthData = appendUint32(lengthData, uint32(length)) + data = append(data, dataTmp...) + case taosTypes.TaosTimestampType: + colInfoData = append(colInfoData, common.TSDB_DATA_TYPE_TIMESTAMP) + length := common.TypeLengthMap[common.TSDB_DATA_TYPE_TIMESTAMP] + colInfoData = appendUint32(colInfoData, uint32(length)) + lengthData = appendUint32(lengthData, uint32(length*rows)) + dataTmp := make([]byte, bitMapLen+rows*Int64Size) + rowData := params[colIndex].GetValues() + for rowIndex := 0; rowIndex < rows; rowIndex++ { + if rowData[rowIndex] == nil { + charOffset := CharOffset(rowIndex) + dataTmp[charOffset] = BMSetNull(dataTmp[charOffset], rowIndex) + } else { + v, is := rowData[rowIndex].(taosTypes.TaosTimestamp) + if !is { + return nil, ErrDataTypeWrong + } + vv := common.TimeToTimestamp(v.T, v.Precision) + offset := rowIndex*Int64Size + bitMapLen + dataTmp[offset] = byte(vv) + dataTmp[offset+1] = byte(vv >> 8) + dataTmp[offset+2] = byte(vv >> 16) + dataTmp[offset+3] = byte(vv >> 24) + dataTmp[offset+4] = byte(vv >> 32) + dataTmp[offset+5] = byte(vv >> 40) + dataTmp[offset+6] = byte(vv >> 48) + dataTmp[offset+7] = byte(vv >> 56) + } + } + data = append(data, dataTmp...) + case taosTypes.TaosJsonType: + colInfoData = append(colInfoData, common.TSDB_DATA_TYPE_JSON) + colInfoData = appendUint32(colInfoData, uint32(0)) + length := 0 + dataTmp := make([]byte, Int32Size*rows) + rowData := params[colIndex].GetValues() + for rowIndex := 0; rowIndex < rows; rowIndex++ { + offset := Int32Size * rowIndex + if rowData[rowIndex] == nil { + for i := 0; i < Int32Size; i++ { + // -1 + dataTmp[offset+i] = byte(255) + } + } else { + v, is := rowData[rowIndex].(taosTypes.TaosJson) + if !is { + return nil, ErrDataTypeWrong + } + for i := 0; i < Int32Size; i++ { + dataTmp[offset+i] = byte(length >> (8 * i)) + } + dataTmp = appendUint16(dataTmp, uint16(len(v))) + dataTmp = append(dataTmp, v...) + length += len(v) + Int16Size + } + } + lengthData = appendUint32(lengthData, uint32(length)) + data = append(data, dataTmp...) + } + } + buffer.Write(colInfoData) + buffer.Write(lengthData) + buffer.Write(data) + block = buffer.Bytes() + for i := 0; i < Int32Size; i++ { + block[4+i] = byte(len(block) >> (8 * i)) + } + return block, nil +} + +func appendUint16(b []byte, v uint16) []byte { + return append(b, + byte(v), + byte(v>>8), + ) +} + +func appendUint32(b []byte, v uint32) []byte { + return append(b, + byte(v), + byte(v>>8), + byte(v>>16), + byte(v>>24), + ) +} + +func appendUint64(b []byte, v uint64) []byte { + return append(b, + byte(v), + byte(v>>8), + byte(v>>16), + byte(v>>24), + byte(v>>32), + byte(v>>40), + byte(v>>48), + byte(v>>56), + ) +} diff --git a/driver/common/serializer/block_test.go b/driver/common/serializer/block_test.go new file mode 100644 index 00000000..db4bb1ff --- /dev/null +++ b/driver/common/serializer/block_test.go @@ -0,0 +1,397 @@ +package serializer + +import ( + "math" + "reflect" + "testing" + "time" + + "github.com/taosdata/taosadapter/v3/driver/common/param" +) + +// @author: xftan +// @date: 2023/10/13 11:19 +// @description: test block +func TestSerializeRawBlock(t *testing.T) { + type args struct { + params []*param.Param + colType *param.ColumnType + } + tests := []struct { + name string + args args + want []byte + wantErr bool + }{ + { + name: "all type", + args: args{ + params: []*param.Param{ + param.NewParam(1).AddTimestamp(time.Unix(0, 0), 0), + param.NewParam(1).AddBool(true), + param.NewParam(1).AddTinyint(127), + param.NewParam(1).AddSmallint(32767), + param.NewParam(1).AddInt(2147483647), + param.NewParam(1).AddBigint(9223372036854775807), + param.NewParam(1).AddUTinyint(255), + param.NewParam(1).AddUSmallint(65535), + param.NewParam(1).AddUInt(4294967295), + param.NewParam(1).AddUBigint(18446744073709551615), + param.NewParam(1).AddFloat(math.MaxFloat32), + param.NewParam(1).AddDouble(math.MaxFloat64), + param.NewParam(1).AddBinary([]byte("ABC")), + param.NewParam(1).AddNchar("涛思数据"), + }, + colType: param.NewColumnType(14). + AddTimestamp(). + AddBool(). + AddTinyint(). + AddSmallint(). + AddInt(). + AddBigint(). + AddUTinyint(). + AddUSmallint(). + AddUInt(). + AddUBigint(). + AddFloat(). + AddDouble(). + AddBinary(0). + AddNchar(0), + }, + want: []byte{ + 0x01, 0x00, 0x00, 0x00, //version + 0xf8, 0x00, 0x00, 0x00, //length + 0x01, 0x00, 0x00, 0x00, //rows + 0x0e, 0x00, 0x00, 0x00, //columns + 0x00, 0x00, 0x00, 0x00, //flagSegment + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //groupID + //types + 0x09, 0x08, 0x00, 0x00, 0x00, //1 + 0x01, 0x01, 0x00, 0x00, 0x00, //2 + 0x02, 0x01, 0x00, 0x00, 0x00, //3 + 0x03, 0x02, 0x00, 0x00, 0x00, //4 + 0x04, 0x04, 0x00, 0x00, 0x00, //5 + 0x05, 0x08, 0x00, 0x00, 0x00, //6 + 0x0b, 0x01, 0x00, 0x00, 0x00, //7 + 0x0c, 0x02, 0x00, 0x00, 0x00, //8 + 0x0d, 0x04, 0x00, 0x00, 0x00, //9 + 0x0e, 0x08, 0x00, 0x00, 0x00, //10 + 0x06, 0x04, 0x00, 0x00, 0x00, //11 + 0x07, 0x08, 0x00, 0x00, 0x00, //12 + 0x08, 0x00, 0x00, 0x00, 0x00, //13 + 0x0a, 0x00, 0x00, 0x00, 0x00, //14 + //lengths + 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, + 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //ts + 0x00, + 0x01, //bool + 0x00, + 0x7f, //i8 + 0x00, + 0xff, 0x7f, //i16 + 0x00, + 0xff, 0xff, 0xff, 0x7f, //i32 + 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, //i64 + 0x00, + 0xff, //u8 + 0x00, + 0xff, 0xff, //u16 + 0x00, + 0xff, 0xff, 0xff, 0xff, //u32 + 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, //u64 + 0x00, + 0xff, 0xff, 0x7f, 0x7f, //f32 + 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f, //f64 + 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, //binary + 0x41, 0x42, 0x43, + 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, //nchar + 0x9b, 0x6d, 0x00, 0x00, 0x1d, 0x60, 0x00, 0x00, 0x70, 0x65, 0x00, 0x00, 0x6e, 0x63, 0x00, 0x00, + }, + wantErr: false, + }, + { + name: "all with nil", + args: args{ + params: []*param.Param{ + param.NewParam(3).AddTimestamp(time.Unix(1666248065, 0), 0).AddNull().AddTimestamp(time.Unix(1666248067, 0), 0), + param.NewParam(3).AddBool(true).AddNull().AddBool(true), + param.NewParam(3).AddTinyint(1).AddNull().AddTinyint(1), + param.NewParam(3).AddSmallint(1).AddNull().AddSmallint(1), + param.NewParam(3).AddInt(1).AddNull().AddInt(1), + param.NewParam(3).AddBigint(1).AddNull().AddBigint(1), + param.NewParam(3).AddUTinyint(1).AddNull().AddUTinyint(1), + param.NewParam(3).AddUSmallint(1).AddNull().AddUSmallint(1), + param.NewParam(3).AddUInt(1).AddNull().AddUInt(1), + param.NewParam(3).AddUBigint(1).AddNull().AddUBigint(1), + param.NewParam(3).AddFloat(1).AddNull().AddFloat(1), + param.NewParam(3).AddDouble(1).AddNull().AddDouble(1), + param.NewParam(3).AddBinary([]byte("test_binary")).AddNull().AddBinary([]byte("test_binary")), + param.NewParam(3).AddNchar("test_nchar").AddNull().AddNchar("test_nchar"), + param.NewParam(3).AddVarBinary([]byte("test_varbinary")).AddNull().AddVarBinary([]byte("test_varbinary")), + param.NewParam(3).AddGeometry([]byte{ + 0x01, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x59, + 0x40, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x59, + 0x40, + }).AddNull().AddGeometry([]byte{ + 0x01, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x59, + 0x40, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x59, + 0x40, + }), + param.NewParam(3).AddJson([]byte("{\"a\":1}")).AddNull().AddJson([]byte("{\"a\":1}")), + }, + colType: param.NewColumnType(17). + AddTimestamp(). + AddBool(). + AddTinyint(). + AddSmallint(). + AddInt(). + AddBigint(). + AddUTinyint(). + AddUSmallint(). + AddUInt(). + AddUBigint(). + AddFloat(). + AddDouble(). + AddBinary(0). + AddNchar(0). + AddVarBinary(0). + AddGeometry(0). + AddJson(0), + }, + want: []byte{ + 0x01, 0x00, 0x00, 0x00, + 0x64, 0x02, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x11, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + //types + 0x09, 0x08, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x01, 0x00, 0x00, 0x00, + 0x03, 0x02, 0x00, 0x00, 0x00, + 0x04, 0x04, 0x00, 0x00, 0x00, + 0x05, 0x08, 0x00, 0x00, 0x00, + 0x0b, 0x01, 0x00, 0x00, 0x00, + 0x0c, 0x02, 0x00, 0x00, 0x00, + 0x0d, 0x04, 0x00, 0x00, 0x00, + 0x0e, 0x08, 0x00, 0x00, 0x00, + 0x06, 0x04, 0x00, 0x00, 0x00, + 0x07, 0x08, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x00, + //lengths + 0x18, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, + 0x54, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, + 0x2e, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, + // ts + 0x40, + 0xe8, 0xbf, 0x1f, 0xf4, 0x83, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xb8, 0xc7, 0x1f, 0xf4, 0x83, 0x01, 0x00, 0x00, + + // bool + 0x40, + 0x01, + 0x00, + 0x01, + + // i8 + 0x40, + 0x01, + 0x00, + 0x01, + + //int16 + 0x40, + 0x01, 0x00, + 0x00, 0x00, + 0x01, 0x00, + + //int32 + 0x40, + 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + + //int64 + 0x40, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + //uint8 + 0x40, + 0x01, + 0x00, + 0x01, + + //uint16 + 0x40, + 0x01, 0x00, + 0x00, 0x00, + 0x01, 0x00, + + //uint32 + 0x40, + 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + + //uint64 + 0x40, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + //float + 0x40, + 0x00, 0x00, 0x80, 0x3f, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x3f, + + //double + 0x40, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, + + //binary + 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, + 0x0d, 0x00, 0x00, 0x00, + 0x0b, 0x00, + 0x74, 0x65, 0x73, 0x74, 0x5f, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, + 0x0b, 0x00, + 0x74, 0x65, 0x73, 0x74, 0x5f, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, + + //nchar + 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, + 0x2a, 0x00, 0x00, 0x00, + 0x28, 0x00, + 0x74, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x73, 0x00, + 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x00, + 0x6e, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x68, 0x00, + 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, + 0x28, 0x00, + 0x74, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x73, 0x00, + 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x00, + 0x6e, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x68, 0x00, + 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, + + //varbinary + 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, + 0x10, 0x00, 0x00, 0x00, + 0x0e, 0x00, + 0x74, 0x65, 0x73, 0x74, 0x5f, 0x76, 0x61, 0x72, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, + 0x0e, 0x00, + 0x74, 0x65, 0x73, 0x74, 0x5f, 0x76, 0x61, 0x72, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, + + //geometry + 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, + 0x17, 0x00, 0x00, 0x00, + 0x15, 0x00, + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, + 0x15, 0x00, + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, + + //json + 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, + 0x09, 0x00, 0x00, 0x00, + 0x07, 0x00, + 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x31, 0x7d, + 0x07, 0x00, + 0x7b, 0x22, 0x61, 0x22, 0x3a, 0x31, 0x7d, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := SerializeRawBlock(tt.args.params, tt.args.colType) + if (err != nil) != tt.wantErr { + t.Errorf("SerializeRawBlock() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SerializeRawBlock() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/driver/common/stmt/field.go b/driver/common/stmt/field.go new file mode 100644 index 00000000..c15ab0ed --- /dev/null +++ b/driver/common/stmt/field.go @@ -0,0 +1,73 @@ +package stmt + +import ( + "database/sql/driver" + "fmt" + + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/types" +) + +type StmtField struct { + Name string `json:"name"` + FieldType int8 `json:"field_type"` + Precision uint8 `json:"precision"` + Scale uint8 `json:"scale"` + Bytes int32 `json:"bytes"` +} + +func (s *StmtField) GetType() (*types.ColumnType, error) { + switch s.FieldType { + case common.TSDB_DATA_TYPE_BOOL: + return &types.ColumnType{Type: types.TaosBoolType}, nil + case common.TSDB_DATA_TYPE_TINYINT: + return &types.ColumnType{Type: types.TaosTinyintType}, nil + case common.TSDB_DATA_TYPE_SMALLINT: + return &types.ColumnType{Type: types.TaosSmallintType}, nil + case common.TSDB_DATA_TYPE_INT: + return &types.ColumnType{Type: types.TaosIntType}, nil + case common.TSDB_DATA_TYPE_BIGINT: + return &types.ColumnType{Type: types.TaosBigintType}, nil + case common.TSDB_DATA_TYPE_UTINYINT: + return &types.ColumnType{Type: types.TaosUTinyintType}, nil + case common.TSDB_DATA_TYPE_USMALLINT: + return &types.ColumnType{Type: types.TaosUSmallintType}, nil + case common.TSDB_DATA_TYPE_UINT: + return &types.ColumnType{Type: types.TaosUIntType}, nil + case common.TSDB_DATA_TYPE_UBIGINT: + return &types.ColumnType{Type: types.TaosUBigintType}, nil + case common.TSDB_DATA_TYPE_FLOAT: + return &types.ColumnType{Type: types.TaosFloatType}, nil + case common.TSDB_DATA_TYPE_DOUBLE: + return &types.ColumnType{Type: types.TaosDoubleType}, nil + case common.TSDB_DATA_TYPE_BINARY: + return &types.ColumnType{Type: types.TaosBinaryType}, nil + case common.TSDB_DATA_TYPE_VARBINARY: + return &types.ColumnType{Type: types.TaosVarBinaryType}, nil + case common.TSDB_DATA_TYPE_NCHAR: + return &types.ColumnType{Type: types.TaosNcharType}, nil + case common.TSDB_DATA_TYPE_TIMESTAMP: + return &types.ColumnType{Type: types.TaosTimestampType}, nil + case common.TSDB_DATA_TYPE_JSON: + return &types.ColumnType{Type: types.TaosJsonType}, nil + case common.TSDB_DATA_TYPE_GEOMETRY: + return &types.ColumnType{Type: types.TaosGeometryType}, nil + } + return nil, fmt.Errorf("unsupported type: %d, name %s", s.FieldType, s.Name) +} + +//revive:disable +const ( + TAOS_FIELD_COL = iota + 1 + TAOS_FIELD_TAG + TAOS_FIELD_QUERY + TAOS_FIELD_TBNAME +) + +//revive:enable + +type TaosStmt2BindData struct { + TableName string + Tags []driver.Value // row format + Cols [][]driver.Value // column format +} diff --git a/driver/common/stmt/field_test.go b/driver/common/stmt/field_test.go new file mode 100644 index 00000000..9d1271bc --- /dev/null +++ b/driver/common/stmt/field_test.go @@ -0,0 +1,143 @@ +package stmt + +import ( + "testing" + + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/types" +) + +func TestGetType(t *testing.T) { + tests := []struct { + name string + fieldType int8 + want *types.ColumnType + wantErr bool + }{ + { + name: "Test Bool Type", + fieldType: common.TSDB_DATA_TYPE_BOOL, + want: &types.ColumnType{Type: types.TaosBoolType}, + wantErr: false, + }, + { + name: "Test TinyInt Type", + fieldType: common.TSDB_DATA_TYPE_TINYINT, + want: &types.ColumnType{Type: types.TaosTinyintType}, + wantErr: false, + }, + { + name: "Test SmallInt Type", + fieldType: common.TSDB_DATA_TYPE_SMALLINT, + want: &types.ColumnType{Type: types.TaosSmallintType}, + wantErr: false, + }, + { + name: "Test Int Type", + fieldType: common.TSDB_DATA_TYPE_INT, + want: &types.ColumnType{Type: types.TaosIntType}, + wantErr: false, + }, + { + name: "Test BigInt Type", + fieldType: common.TSDB_DATA_TYPE_BIGINT, + want: &types.ColumnType{Type: types.TaosBigintType}, + wantErr: false, + }, + { + name: "Test UTinyInt Type", + fieldType: common.TSDB_DATA_TYPE_UTINYINT, + want: &types.ColumnType{Type: types.TaosUTinyintType}, + wantErr: false, + }, + { + name: "Test USmallInt Type", + fieldType: common.TSDB_DATA_TYPE_USMALLINT, + want: &types.ColumnType{Type: types.TaosUSmallintType}, + wantErr: false, + }, + { + name: "Test UInt Type", + fieldType: common.TSDB_DATA_TYPE_UINT, + want: &types.ColumnType{Type: types.TaosUIntType}, + wantErr: false, + }, + { + name: "Test UBigInt Type", + fieldType: common.TSDB_DATA_TYPE_UBIGINT, + want: &types.ColumnType{Type: types.TaosUBigintType}, + wantErr: false, + }, + { + name: "Test Float Type", + fieldType: common.TSDB_DATA_TYPE_FLOAT, + want: &types.ColumnType{Type: types.TaosFloatType}, + wantErr: false, + }, + { + name: "Test Double Type", + fieldType: common.TSDB_DATA_TYPE_DOUBLE, + want: &types.ColumnType{Type: types.TaosDoubleType}, + wantErr: false, + }, + { + name: "Test Binary Type", + fieldType: common.TSDB_DATA_TYPE_BINARY, + want: &types.ColumnType{Type: types.TaosBinaryType}, + wantErr: false, + }, + { + name: "Test VarBinary Type", + fieldType: common.TSDB_DATA_TYPE_VARBINARY, + want: &types.ColumnType{Type: types.TaosVarBinaryType}, + wantErr: false, + }, + { + name: "Test Nchar Type", + fieldType: common.TSDB_DATA_TYPE_NCHAR, + want: &types.ColumnType{Type: types.TaosNcharType}, + wantErr: false, + }, + { + name: "Test Timestamp Type", + fieldType: common.TSDB_DATA_TYPE_TIMESTAMP, + want: &types.ColumnType{Type: types.TaosTimestampType}, + wantErr: false, + }, + { + name: "Test Json Type", + fieldType: common.TSDB_DATA_TYPE_JSON, + want: &types.ColumnType{Type: types.TaosJsonType}, + wantErr: false, + }, + { + name: "Test Geometry Type", + fieldType: common.TSDB_DATA_TYPE_GEOMETRY, + want: &types.ColumnType{Type: types.TaosGeometryType}, + wantErr: false, + }, + { + name: "Test Unsupported Type", + fieldType: 0, // An undefined type + want: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &StmtField{ + FieldType: tt.fieldType, + } + + got, err := s.GetType() + if (err != nil) != tt.wantErr { + t.Errorf("GetType() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != nil && tt.want != nil && got.Type != tt.want.Type { + t.Errorf("GetType() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/driver/common/stmt/stmt2.go b/driver/common/stmt/stmt2.go new file mode 100644 index 00000000..d1a97696 --- /dev/null +++ b/driver/common/stmt/stmt2.go @@ -0,0 +1,580 @@ +package stmt + +import ( + "bytes" + "database/sql/driver" + "encoding/binary" + "fmt" + "math" + "time" + + "github.com/taosdata/taosadapter/v3/driver/common" +) + +const ( + TotalLengthPosition = 0 + CountPosition = TotalLengthPosition + 4 + TagCountPosition = CountPosition + 4 + ColCountPosition = TagCountPosition + 4 + TableNamesOffsetPosition = ColCountPosition + 4 + TagsOffsetPosition = TableNamesOffsetPosition + 4 + ColsOffsetPosition = TagsOffsetPosition + 4 + DataPosition = ColsOffsetPosition + 4 +) + +const ( + BindDataTotalLengthOffset = 0 + BindDataTypeOffset = BindDataTotalLengthOffset + 4 + BindDataNumOffset = BindDataTypeOffset + 4 + BindDataIsNullOffset = BindDataNumOffset + 4 +) + +func MarshalStmt2Binary(bindData []*TaosStmt2BindData, isInsert bool, colType, tagType []*StmtField) ([]byte, error) { + // count + count := len(bindData) + if count == 0 { + return nil, fmt.Errorf("empty data") + } + needTableNames := false + needTags := false + needCols := false + tagCount := len(tagType) + colCount := len(colType) + if isInsert { + for i := 0; i < count; i++ { + data := bindData[i] + if data.TableName != "" { + needTableNames = true + } + if len(data.Tags) != tagCount { + return nil, fmt.Errorf("tag count not match, data count:%d, type count:%d", len(data.Tags), tagCount) + } + if len(data.Cols) != colCount { + return nil, fmt.Errorf("col count not match, data count:%d, type count:%d", len(data.Cols), colCount) + } + } + } else { + if tagCount != 0 { + return nil, fmt.Errorf("query not need tag types") + } + if colCount != 0 { + return nil, fmt.Errorf("query not need col types") + } + if count != 1 { + return nil, fmt.Errorf("query only need one data") + } + + data := bindData[0] + if data.TableName != "" { + return nil, fmt.Errorf("query not need table name") + } + if len(data.Tags) != 0 { + return nil, fmt.Errorf("query not need tag") + } + if len(data.Cols) == 0 { + return nil, fmt.Errorf("query need col") + } + colCount = len(data.Cols) + for j := 0; j < colCount; j++ { + if len(data.Cols[j]) != 1 { + return nil, fmt.Errorf("query col data must be one row, col:%d, count:%d", j, len(data.Cols[j])) + } + } + } + + header := make([]byte, DataPosition) + // count + binary.LittleEndian.PutUint32(header[CountPosition:], uint32(count)) + // tag count + if tagCount != 0 { + needTags = true + binary.LittleEndian.PutUint32(header[TagCountPosition:], uint32(tagCount)) + } + // col count + if colCount != 0 { + needCols = true + binary.LittleEndian.PutUint32(header[ColCountPosition:], uint32(colCount)) + } + if !needTableNames && !needTags && !needCols { + return nil, fmt.Errorf("no data") + } + tmpBuf := &bytes.Buffer{} + tableNameBuf := &bytes.Buffer{} + var tableNameLength []uint16 + if needTableNames { + tableNameLength = make([]uint16, count) + } + tagBuf := &bytes.Buffer{} + var tagDataLength []uint32 + if needTags { + tagDataLength = make([]uint32, count) + } + colBuf := &bytes.Buffer{} + var colDataLength []uint32 + if needCols { + colDataLength = make([]uint32, count) + } + for index, data := range bindData { + // table name + if needTableNames { + if data.TableName != "" { + if len(data.TableName) > math.MaxUint16-1 { + return nil, fmt.Errorf("table name too long, index:%d, length:%d", index, len(data.TableName)) + } + tableNameBuf.WriteString(data.TableName) + } + tableNameBuf.WriteByte(0) + tableNameLength[index] = uint16(len(data.TableName) + 1) + } + + // tag + if needTags { + length := 0 + for i := 0; i < len(data.Tags); i++ { + tag := data.Tags[i] + tagDataBuffer, err := generateBindColData([]driver.Value{tag}, tagType[i], tmpBuf) + if err != nil { + return nil, err + } + length += len(tagDataBuffer) + tagBuf.Write(tagDataBuffer) + } + tagDataLength[index] = uint32(length) + } + // col + if needCols { + length := 0 + for i := 0; i < len(data.Cols); i++ { + col := data.Cols[i] + var colDataBuffer []byte + var err error + if isInsert { + colDataBuffer, err = generateBindColData(col, colType[i], tmpBuf) + } else { + colDataBuffer, err = generateBindQueryData(col[0]) + } + if err != nil { + return nil, err + } + length += len(colDataBuffer) + colBuf.Write(colDataBuffer) + } + colDataLength[index] = uint32(length) + } + } + tableTotalLength := tableNameBuf.Len() + tagTotalLength := tagBuf.Len() + colTotalLength := colBuf.Len() + tagOffset := DataPosition + tableTotalLength + len(tableNameLength)*2 + colOffset := tagOffset + tagTotalLength + len(tagDataLength)*4 + totalLength := colOffset + colTotalLength + len(colDataLength)*4 + if needTableNames { + binary.LittleEndian.PutUint32(header[TableNamesOffsetPosition:], uint32(DataPosition)) + } + if needTags { + binary.LittleEndian.PutUint32(header[TagsOffsetPosition:], uint32(tagOffset)) + } + if needCols { + binary.LittleEndian.PutUint32(header[ColsOffsetPosition:], uint32(colOffset)) + } + binary.LittleEndian.PutUint32(header[TotalLengthPosition:], uint32(totalLength)) + buffer := make([]byte, totalLength) + copy(buffer, header) + if needTableNames { + offset := DataPosition + for _, length := range tableNameLength { + binary.LittleEndian.PutUint16(buffer[offset:], length) + offset += 2 + } + copy(buffer[offset:], tableNameBuf.Bytes()) + } + if needTags { + offset := tagOffset + for _, length := range tagDataLength { + binary.LittleEndian.PutUint32(buffer[offset:], length) + offset += 4 + } + copy(buffer[offset:], tagBuf.Bytes()) + } + if needCols { + offset := colOffset + for _, length := range colDataLength { + binary.LittleEndian.PutUint32(buffer[offset:], length) + offset += 4 + } + copy(buffer[offset:], colBuf.Bytes()) + } + return buffer, nil +} + +func getBindDataHeaderLength(num int, needLength bool) int { + length := 17 + num + if needLength { + length += num * 4 + } + return length +} + +func generateBindColData(data []driver.Value, colType *StmtField, tmpBuffer *bytes.Buffer) ([]byte, error) { + num := len(data) + tmpBuffer.Reset() + needLength := needLength(colType.FieldType) + headerLength := getBindDataHeaderLength(num, needLength) + tmpHeader := make([]byte, headerLength) + // type + binary.LittleEndian.PutUint32(tmpHeader[BindDataTypeOffset:], uint32(colType.FieldType)) + // num + binary.LittleEndian.PutUint32(tmpHeader[BindDataNumOffset:], uint32(num)) + // is null + isNull := tmpHeader[BindDataIsNullOffset : BindDataIsNullOffset+num] + // has length + if needLength { + tmpHeader[BindDataIsNullOffset+num] = 1 + } + bufferLengthOffset := BindDataIsNullOffset + num + 1 + isAllNull := checkAllNull(data) + if isAllNull { + for i := 0; i < num; i++ { + isNull[i] = 1 + } + } else { + switch colType.FieldType { + case common.TSDB_DATA_TYPE_BOOL: + for i := 0; i < num; i++ { + if data[i] == nil { + isNull[i] = 1 + tmpBuffer.WriteByte(0) + } else { + v, ok := data[i].(bool) + if !ok { + return nil, fmt.Errorf("data type not match, expect bool, but get %T, value:%v", data[i], data[i]) + } + if v { + tmpBuffer.WriteByte(1) + } else { + tmpBuffer.WriteByte(0) + } + } + } + case common.TSDB_DATA_TYPE_TINYINT: + for i := 0; i < num; i++ { + if data[i] == nil { + isNull[i] = 1 + tmpBuffer.WriteByte(0) + } else { + v, ok := data[i].(int8) + if !ok { + return nil, fmt.Errorf("data type not match, expect int8, but get %T, value:%v", data[i], data[i]) + } + tmpBuffer.WriteByte(byte(v)) + } + } + + case common.TSDB_DATA_TYPE_SMALLINT: + for i := 0; i < num; i++ { + if data[i] == nil { + isNull[i] = 1 + writeUint16(tmpBuffer, uint16(0)) + } else { + v, ok := data[i].(int16) + if !ok { + return nil, fmt.Errorf("data type not match, expect int16, but get %T, value:%v", data[i], data[i]) + } + writeUint16(tmpBuffer, uint16(v)) + } + } + + case common.TSDB_DATA_TYPE_INT: + for i := 0; i < num; i++ { + if data[i] == nil { + isNull[i] = 1 + writeUint32(tmpBuffer, uint32(0)) + } else { + v, ok := data[i].(int32) + if !ok { + return nil, fmt.Errorf("data type not match, expect int32, but get %T, value:%v", data[i], data[i]) + } + writeUint32(tmpBuffer, uint32(v)) + } + } + case common.TSDB_DATA_TYPE_BIGINT: + for i := 0; i < num; i++ { + if data[i] == nil { + isNull[i] = 1 + writeUint64(tmpBuffer, 0) + } else { + v, ok := data[i].(int64) + if !ok { + return nil, fmt.Errorf("data type not match, expect int64, but get %T, value:%v", data[i], data[i]) + } + writeUint64(tmpBuffer, uint64(v)) + } + } + case common.TSDB_DATA_TYPE_FLOAT: + for i := 0; i < num; i++ { + if data[i] == nil { + isNull[i] = 1 + writeUint32(tmpBuffer, 0) + } else { + v, ok := data[i].(float32) + if !ok { + return nil, fmt.Errorf("data type not match, expect float32, but get %T, value:%v", data[i], data[i]) + } + writeUint32(tmpBuffer, math.Float32bits(v)) + } + } + case common.TSDB_DATA_TYPE_DOUBLE: + for i := 0; i < num; i++ { + if data[i] == nil { + isNull[i] = 1 + writeUint64(tmpBuffer, 0) + } else { + v, ok := data[i].(float64) + if !ok { + return nil, fmt.Errorf("data type not match, expect float64, but get %T, value:%v", data[i], data[i]) + } + writeUint64(tmpBuffer, math.Float64bits(v)) + } + } + case common.TSDB_DATA_TYPE_TIMESTAMP: + precision := int(colType.Precision) + for i := 0; i < num; i++ { + if data[i] == nil { + isNull[i] = 1 + writeUint64(tmpBuffer, 0) + } else { + switch v := data[i].(type) { + case int64: + writeUint64(tmpBuffer, uint64(v)) + case time.Time: + ts := common.TimeToTimestamp(v, precision) + writeUint64(tmpBuffer, uint64(ts)) + default: + return nil, fmt.Errorf("data type not match, expect int64 or time.Time, but get %T, value:%v", data[i], data[i]) + } + } + } + case common.TSDB_DATA_TYPE_BINARY, common.TSDB_DATA_TYPE_NCHAR, common.TSDB_DATA_TYPE_VARBINARY, common.TSDB_DATA_TYPE_GEOMETRY, common.TSDB_DATA_TYPE_JSON: + for i := 0; i < num; i++ { + if data[i] == nil { + isNull[i] = 1 + } else { + switch v := data[i].(type) { + case string: + tmpBuffer.WriteString(v) + binary.LittleEndian.PutUint32(tmpHeader[bufferLengthOffset+i*4:], uint32(len(v))) + case []byte: + tmpBuffer.Write(v) + binary.LittleEndian.PutUint32(tmpHeader[bufferLengthOffset+i*4:], uint32(len(v))) + default: + return nil, fmt.Errorf("data type not match, expect string or []byte, but get %T, value:%v", data[i], data[i]) + } + } + } + case common.TSDB_DATA_TYPE_UTINYINT: + for i := 0; i < num; i++ { + if data[i] == nil { + isNull[i] = 1 + tmpBuffer.WriteByte(0) + } else { + v, ok := data[i].(uint8) + if !ok { + return nil, fmt.Errorf("data type not match, expect uint8, but get %T, value:%v", data[i], data[i]) + } + tmpBuffer.WriteByte(v) + } + } + case common.TSDB_DATA_TYPE_USMALLINT: + for i := 0; i < num; i++ { + if data[i] == nil { + isNull[i] = 1 + writeUint16(tmpBuffer, 0) + } else { + v, ok := data[i].(uint16) + if !ok { + return nil, fmt.Errorf("data type not match, expect uint16, but get %T, value:%v", data[i], data[i]) + } + writeUint16(tmpBuffer, v) + } + } + case common.TSDB_DATA_TYPE_UINT: + for i := 0; i < num; i++ { + if data[i] == nil { + isNull[i] = 1 + writeUint32(tmpBuffer, 0) + } else { + v, ok := data[i].(uint32) + if !ok { + return nil, fmt.Errorf("data type not match, expect uint32, but get %T, value:%v", data[i], data[i]) + } + writeUint32(tmpBuffer, v) + } + } + case common.TSDB_DATA_TYPE_UBIGINT: + for i := 0; i < num; i++ { + if data[i] == nil { + isNull[i] = 1 + writeUint64(tmpBuffer, 0) + } else { + v, ok := data[i].(uint64) + if !ok { + return nil, fmt.Errorf("data type not match, expect uint64, but get %T, value:%v", data[i], data[i]) + } + writeUint64(tmpBuffer, v) + } + } + default: + return nil, fmt.Errorf("unsupported type: %d", colType.FieldType) + } + } + buffer := tmpBuffer.Bytes() + // bufferLength + binary.LittleEndian.PutUint32(tmpHeader[headerLength-4:], uint32(len(buffer))) + totalLength := len(buffer) + headerLength + binary.LittleEndian.PutUint32(tmpHeader[BindDataTotalLengthOffset:], uint32(totalLength)) + dataBuffer := make([]byte, totalLength) + copy(dataBuffer, tmpHeader) + copy(dataBuffer[headerLength:], buffer) + return dataBuffer, nil +} + +func checkAllNull(data []driver.Value) bool { + for i := 0; i < len(data); i++ { + if data[i] != nil { + return false + } + } + return true +} + +func generateBindQueryData(data driver.Value) ([]byte, error) { + var colType uint32 + var haveLength = false + var length = 0 + var buf []byte + switch v := data.(type) { + case string: + colType = common.TSDB_DATA_TYPE_BINARY + haveLength = true + length = len(v) + buf = make([]byte, length) + copy(buf, v) + case []byte: + colType = common.TSDB_DATA_TYPE_BINARY + haveLength = true + length = len(v) + buf = make([]byte, length) + copy(buf, v) + case int8: + colType = common.TSDB_DATA_TYPE_TINYINT + buf = make([]byte, 1) + buf[0] = byte(v) + case int16: + colType = common.TSDB_DATA_TYPE_SMALLINT + buf = make([]byte, 2) + binary.LittleEndian.PutUint16(buf, uint16(v)) + case int32: + colType = common.TSDB_DATA_TYPE_INT + buf = make([]byte, 4) + binary.LittleEndian.PutUint32(buf, uint32(v)) + case int64: + colType = common.TSDB_DATA_TYPE_BIGINT + buf = make([]byte, 8) + binary.LittleEndian.PutUint64(buf, uint64(v)) + case uint8: + colType = common.TSDB_DATA_TYPE_UTINYINT + buf = make([]byte, 1) + buf[0] = byte(v) + case uint16: + colType = common.TSDB_DATA_TYPE_USMALLINT + buf = make([]byte, 2) + binary.LittleEndian.PutUint16(buf, v) + case uint32: + colType = common.TSDB_DATA_TYPE_UINT + buf = make([]byte, 4) + binary.LittleEndian.PutUint32(buf, v) + case uint64: + colType = common.TSDB_DATA_TYPE_UBIGINT + buf = make([]byte, 8) + binary.LittleEndian.PutUint64(buf, v) + case float32: + colType = common.TSDB_DATA_TYPE_FLOAT + buf = make([]byte, 4) + binary.LittleEndian.PutUint32(buf, math.Float32bits(v)) + case float64: + colType = common.TSDB_DATA_TYPE_DOUBLE + buf = make([]byte, 8) + binary.LittleEndian.PutUint64(buf, math.Float64bits(v)) + case bool: + colType = common.TSDB_DATA_TYPE_BOOL + buf = make([]byte, 1) + if v { + buf[0] = 1 + } else { + buf[0] = 0 + } + case time.Time: + buf = make([]byte, 0, 35) + colType = common.TSDB_DATA_TYPE_BINARY + haveLength = true + buf = v.AppendFormat(buf, time.RFC3339Nano) + length = len(buf) + default: + return nil, fmt.Errorf("unsupported type: %T", data) + } + headerLength := getBindDataHeaderLength(1, haveLength) + totalLength := len(buf) + headerLength + dataBuf := make([]byte, totalLength) + // type + binary.LittleEndian.PutUint32(dataBuf[BindDataTypeOffset:], colType) + // num + binary.LittleEndian.PutUint32(dataBuf[BindDataNumOffset:], 1) + // is null + dataBuf[BindDataIsNullOffset] = 0 + // has length + if haveLength { + dataBuf[BindDataIsNullOffset+1] = 1 + binary.LittleEndian.PutUint32(dataBuf[BindDataIsNullOffset+2:], uint32(length)) + + } + // bufferLength + binary.LittleEndian.PutUint32(dataBuf[headerLength-4:], uint32(len(buf))) + copy(dataBuf[headerLength:], buf) + binary.LittleEndian.PutUint32(dataBuf[BindDataTotalLengthOffset:], uint32(totalLength)) + return dataBuf, nil +} + +func writeUint64(buffer *bytes.Buffer, v uint64) { + buffer.WriteByte(byte(v)) + buffer.WriteByte(byte(v >> 8)) + buffer.WriteByte(byte(v >> 16)) + buffer.WriteByte(byte(v >> 24)) + buffer.WriteByte(byte(v >> 32)) + buffer.WriteByte(byte(v >> 40)) + buffer.WriteByte(byte(v >> 48)) + buffer.WriteByte(byte(v >> 56)) +} + +func writeUint32(buffer *bytes.Buffer, v uint32) { + buffer.WriteByte(byte(v)) + buffer.WriteByte(byte(v >> 8)) + buffer.WriteByte(byte(v >> 16)) + buffer.WriteByte(byte(v >> 24)) +} + +func writeUint16(buffer *bytes.Buffer, v uint16) { + buffer.WriteByte(byte(v)) + buffer.WriteByte(byte(v >> 8)) +} + +func needLength(colType int8) bool { + switch colType { + case common.TSDB_DATA_TYPE_BINARY, + common.TSDB_DATA_TYPE_NCHAR, + common.TSDB_DATA_TYPE_JSON, + common.TSDB_DATA_TYPE_VARBINARY, + common.TSDB_DATA_TYPE_GEOMETRY: + return true + } + return false +} diff --git a/driver/common/stmt/stmt2_test.go b/driver/common/stmt/stmt2_test.go new file mode 100644 index 00000000..957d592f --- /dev/null +++ b/driver/common/stmt/stmt2_test.go @@ -0,0 +1,2437 @@ +package stmt + +import ( + "database/sql/driver" + "math" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/taosdata/taosadapter/v3/driver/common" +) + +type customInt int + +func TestMarshalBinary(t *testing.T) { + largeTableName := "" + for i := 0; i < math.MaxUint16; i++ { + largeTableName += "a" + } + type args struct { + t []*TaosStmt2BindData + isInsert bool + tagType []*StmtField + colType []*StmtField + } + tests := []struct { + name string + args args + want []byte + wantErr bool + }{ + { + name: "TestSetTableName", + args: args{ + t: []*TaosStmt2BindData{ + { + TableName: "test1", + }, + { + TableName: "", + }, + { + TableName: "test2", + }, + }, + isInsert: true, + tagType: nil, + colType: nil, + }, + want: []byte{ + // total Length + 0x2f, 0x00, 0x00, 0x00, + // tableCount + 0x03, 0x00, 0x00, 0x00, + // TagCount + 0x00, 0x00, 0x00, 0x00, + // ColCount + 0x00, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x1c, 0x00, 0x00, 0x00, + // TagsOffset + 0x00, 0x00, 0x00, 0x00, + // ColOffset + 0x00, 0x00, 0x00, 0x00, + // table names + // TableNameLength + 0x06, 0x00, + 0x01, 0x00, + 0x06, 0x00, + // test1 + 0x74, 0x65, 0x73, 0x74, 0x31, 0x00, + // nil + 0x00, + // test2 + 0x74, 0x65, 0x73, 0x74, 0x32, 0x00, + }, + wantErr: false, + }, + { + name: "wrong TableName length", + args: args{ + t: []*TaosStmt2BindData{ + { + TableName: largeTableName, + }, + }, + isInsert: true, + tagType: nil, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "TestSetTableNameAndTags", + args: args{ + t: []*TaosStmt2BindData{ + { + TableName: "test1", + Tags: []driver.Value{ + // ts 1726803356466 + time.Unix(1726803356, 466000000), + // bool + true, + // tinyint + int8(1), + // smallint + int16(2), + // int + int32(3), + // bigint + int64(4), + // float + float32(5.5), + // double + float64(6.6), + // utinyint + uint8(7), + // usmallint + uint16(8), + // uint + uint32(9), + // ubigint + uint64(10), + // binary + []byte("binary"), + // nchar + "nchar", + // geometry + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + // varbinary + []byte("varbinary"), + }, + }, + { + TableName: "testnil", + Tags: []driver.Value{ + // ts 1726803356466 + nil, + // bool + nil, + // tinyint + nil, + // smallint + nil, + // int + nil, + // bigint + nil, + // float + nil, + // double + nil, + // utinyint + nil, + // usmallint + nil, + // uint + nil, + // ubigint + nil, + // binary + nil, + // nchar + nil, + // geometry + nil, + // varbinary + nil, + }, + }, + { + TableName: "test2", + Tags: []driver.Value{ + // ts 1726803356466 + time.Unix(1726803356, 466000000), + // bool + true, + // tinyint + int8(1), + // smallint + int16(2), + // int + int32(3), + // bigint + int64(4), + // float + float32(5.5), + // double + float64(6.6), + // utinyint + uint8(7), + // usmallint + uint16(8), + // uint + uint32(9), + // ubigint + uint64(10), + // binary + []byte("binary"), + // nchar + "nchar", + // geometry + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + // varbinary + []byte("varbinary"), + }, + }, + }, + isInsert: true, + tagType: []*StmtField{ + { + FieldType: common.TSDB_DATA_TYPE_TIMESTAMP, + Precision: common.PrecisionMilliSecond, + }, + { + FieldType: common.TSDB_DATA_TYPE_BOOL, + }, + { + FieldType: common.TSDB_DATA_TYPE_TINYINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_SMALLINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_INT, + }, + { + FieldType: common.TSDB_DATA_TYPE_BIGINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_FLOAT, + }, + { + FieldType: common.TSDB_DATA_TYPE_DOUBLE, + }, + { + FieldType: common.TSDB_DATA_TYPE_UTINYINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_USMALLINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_UINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_UBIGINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_BINARY, + }, + { + FieldType: common.TSDB_DATA_TYPE_NCHAR, + }, + { + FieldType: common.TSDB_DATA_TYPE_GEOMETRY, + }, + { + FieldType: common.TSDB_DATA_TYPE_VARBINARY, + }, + }, + colType: nil, + }, + want: []byte{ + // total Length + 0x8a, 0x04, 0x00, 0x00, + // tableCount + 0x03, 0x00, 0x00, 0x00, + // TagCount + 0x10, 0x00, 0x00, 0x00, + // ColCount + 0x00, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x1c, 0x00, 0x00, 0x00, + // TagsOffset + 0x36, 0x00, 0x00, 0x00, + // ColOffset + 0x00, 0x00, 0x00, 0x00, + // table names + // TableNameLength + 0x06, 0x00, + 0x08, 0x00, + 0x06, 0x00, + // test1 + 0x74, 0x65, 0x73, 0x74, 0x31, 0x00, + // testnil + 0x74, 0x65, 0x73, 0x74, 0x6e, 0x69, 0x6c, 0x00, + // test2 + 0x74, 0x65, 0x73, 0x74, 0x32, 0x00, + + // tags + + // tagsDataLength + // table1 DataLength + 0x8c, 0x01, 0x00, 0x00, + // table2 DataLength + 0x30, 0x01, 0x00, 0x00, + // table3 DataLength + 0x8c, 0x01, 0x00, 0x00, + + // tagsData + // table1 tags + // tag1 timestamp + // totalLength + 0x1a, 0x00, 0x00, 0x00, + + // type + 0x09, 0x00, 0x00, 0x00, + + // num + 0x01, 0x00, 0x00, 0x00, + + // isnull + 0x00, + + // haveLength + 0x00, + + //buffer length + 0x08, 0x00, 0x00, 0x00, + + // buffer + 0x32, 0x2b, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, + + // tag2 bool + 0x13, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + + // tag3 tinyint + 0x13, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + + // tag4 smallint + 0x14, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x02, 0x00, + + // tag5 int + 0x16, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + + // tag6 bigint + 0x1a, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + // tag7 float + 0x16, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xb0, 0x40, + + // tag8 double + 0x1a, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x1a, 0x40, + + // tag9 utinyint + 0x13, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x07, + + // tag10 usmallint + 0x14, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x08, 0x00, + + // tag11 uint + 0x16, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, + + // tag12 ubigint + 0x1a, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + // tag13 binary + 0x1c, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x01, + // length + 0x06, 0x00, 0x00, 0x00, + // buffer length + 0x06, 0x00, 0x00, 0x00, + //buffer + 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, + + // tag14 nchar + 0x1b, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x01, + 0x05, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x6e, 0x63, 0x68, 0x61, 0x72, + + // tag15 geometry + 0x2b, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x01, + 0x15, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, + + // tag16 varbinary + 0x1f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x01, + 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, + 0x76, 0x61, 0x72, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, + + // table 2 tags + // tag1 timestamp nil + // TotalLength + 0x12, 0x00, 0x00, 0x00, + // type + 0x09, 0x00, 0x00, 0x00, + // num + 0x01, 0x00, 0x00, 0x00, + // isnull + 0x01, + // haveLength + 0x00, + // buffer length + 0x00, 0x00, 0x00, 0x00, + + // tag2 bool nil + 0x12, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + 0x00, + 0x00, 0x00, 0x00, 0x00, + + // tag3 tinyint nil + 0x12, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + 0x00, + 0x00, 0x00, 0x00, 0x00, + + // tag4 smallint nil + 0x12, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + 0x00, + 0x00, 0x00, 0x00, 0x00, + + // tag5 int nil + 0x12, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + 0x00, + 0x00, 0x00, 0x00, 0x00, + + // tag6 bigint nil + 0x12, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + 0x00, + 0x00, 0x00, 0x00, 0x00, + + // tag7 float nil + 0x12, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + 0x00, + 0x00, 0x00, 0x00, 0x00, + + // tag8 double nil + 0x12, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + 0x00, + 0x00, 0x00, 0x00, 0x00, + + // tag9 utinyint nil + 0x12, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + 0x00, + 0x00, 0x00, 0x00, 0x00, + + // tag10 usmallint nil + 0x12, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + 0x00, + 0x00, 0x00, 0x00, 0x00, + + // tag11 uint nil + 0x12, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + 0x00, + 0x00, 0x00, 0x00, 0x00, + + // tag12 ubigint nil + 0x12, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + 0x00, + 0x00, 0x00, 0x00, 0x00, + + // tag13 binary nil + 0x16, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + // tag14 nchar nil + 0x16, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + // tag15 geometry nil + 0x16, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + // tag16 varbinary nil + 0x16, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + // table 3 tags + // tag1 timestamp + 0x1a, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x32, 0x2b, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, + + // tag2 bool + 0x13, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + + // tag3 tinyint + 0x13, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + + // tag4 smallint + 0x14, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x02, 0x00, + + // tag5 int + 0x16, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + + // tag6 bigint + 0x1a, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + + // tag7 float + 0x00, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, + 0x01, + 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xb0, 0x40, + + // tag8 double + 0x1a, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x1a, 0x40, + + // tag9 utinyint + 0x13, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x07, + + // tag10 usmallint + 0x14, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x08, 0x00, + + // tag11 uint + 0x16, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, + + // tag12 ubigint + 0x1a, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + // tag13 binary + 0x1c, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x01, + 0x06, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, + 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, + + // tag14 nchar + 0x1b, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x01, + 0x05, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x6e, 0x63, 0x68, 0x61, 0x72, + + // tag15 geometry + 0x2b, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x01, + 0x15, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, + + // tag16 varbinary + 0x1f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x01, + 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, + 0x76, 0x61, 0x72, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, + }, + wantErr: false, + }, + { + name: "TestAllData", + args: args{ + t: []*TaosStmt2BindData{ + { + TableName: "test1", + Tags: []driver.Value{ + // ts 1726803356466 + time.Unix(1726803356, 466000000), + // bool + true, + // tinyint + int8(1), + // smallint + int16(2), + // int + int32(3), + // bigint + int64(4), + // float + float32(5.5), + // double + float64(6.6), + // utinyint + uint8(7), + // usmallint + uint16(8), + // uint + uint32(9), + // ubigint + uint64(10), + // binary + []byte("binary"), + // nchar + "nchar", + // geometry + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + // varbinary + []byte("varbinary"), + }, + Cols: [][]driver.Value{ + { + // ts 1726803356466 + time.Unix(1726803356, 466000000), + // ts 1726803357466 + time.Unix(1726803357, 466000000), + // ts 1726803358466 + time.Unix(1726803358, 466000000), + }, + { + // BOOL + true, + nil, + false, + }, + { + // TINYINT + int8(11), + nil, + int8(12), + }, + { + // SMALLINT + int16(11), + nil, + int16(12), + }, + { + // INT + int32(11), + nil, + int32(12), + }, + { + // BIGINT + int64(11), + nil, + int64(12), + }, + { + // FLOAT + float32(11.2), + nil, + float32(12.2), + }, + { + // DOUBLE + float64(11.2), + nil, + float64(12.2), + }, + { + // TINYINT UNSIGNED + uint8(11), + nil, + uint8(12), + }, + { + // SMALLINT UNSIGNED + uint16(11), + nil, + uint16(12), + }, + { + // INT UNSIGNED + uint32(11), + nil, + uint32(12), + }, + { + // BIGINT UNSIGNED + uint64(11), + nil, + uint64(12), + }, + { + // BINARY + []byte("binary1"), + nil, + []byte("binary2"), + }, + { + // NCHAR + "nchar1", + nil, + "nchar2", + }, + { + // GEOMETRY `point(100 100)` + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + nil, + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + }, + { + // VARBINARY + []byte("varbinary1"), + nil, + []byte("varbinary2"), + }, + }, + }, + }, + isInsert: true, + tagType: []*StmtField{ + { + FieldType: common.TSDB_DATA_TYPE_TIMESTAMP, + Precision: common.PrecisionMilliSecond, + }, + { + FieldType: common.TSDB_DATA_TYPE_BOOL, + }, + { + FieldType: common.TSDB_DATA_TYPE_TINYINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_SMALLINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_INT, + }, + { + FieldType: common.TSDB_DATA_TYPE_BIGINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_FLOAT, + }, + { + FieldType: common.TSDB_DATA_TYPE_DOUBLE, + }, + { + FieldType: common.TSDB_DATA_TYPE_UTINYINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_USMALLINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_UINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_UBIGINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_BINARY, + }, + { + FieldType: common.TSDB_DATA_TYPE_NCHAR, + }, + { + FieldType: common.TSDB_DATA_TYPE_GEOMETRY, + }, + { + FieldType: common.TSDB_DATA_TYPE_VARBINARY, + }, + }, + colType: []*StmtField{ + { + FieldType: common.TSDB_DATA_TYPE_TIMESTAMP, + Precision: common.PrecisionMilliSecond, + }, + { + FieldType: common.TSDB_DATA_TYPE_BOOL, + }, + { + FieldType: common.TSDB_DATA_TYPE_TINYINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_SMALLINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_INT, + }, + { + FieldType: common.TSDB_DATA_TYPE_BIGINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_FLOAT, + }, + { + FieldType: common.TSDB_DATA_TYPE_DOUBLE, + }, + { + FieldType: common.TSDB_DATA_TYPE_UTINYINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_USMALLINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_UINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_UBIGINT, + }, + { + FieldType: common.TSDB_DATA_TYPE_BINARY, + }, + { + FieldType: common.TSDB_DATA_TYPE_NCHAR, + }, + { + FieldType: common.TSDB_DATA_TYPE_GEOMETRY, + }, + { + FieldType: common.TSDB_DATA_TYPE_VARBINARY, + }, + }, + }, + want: []byte{ + // TotalLength + 0x19, 0x04, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x10, 0x00, 0x00, 0x00, + // ColCount + 0x10, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x1c, 0x00, 0x00, 0x00, + // TagsOffset + 0x24, 0x00, 0x00, 0x00, + // ColsOffset + 0xb4, 0x01, 0x00, 0x00, + + // TableNameLength + 0x06, 0x00, + // TableNameBuffer + 0x74, 0x65, 0x73, 0x74, 0x31, 0x00, + + // TagsDataLength + 0x8c, 0x01, 0x00, 0x00, + + // TagsBuffer + + // tag1 timestamp + // TotalLength + 0x1a, 0x00, 0x00, 0x00, + // type + 0x09, 0x00, 0x00, 0x00, + // num + 0x01, 0x00, 0x00, 0x00, + // isnull + 0x00, + // haveLength + 0x00, + // buffer length + 0x08, 0x00, 0x00, 0x00, + // buffer + 0x32, 0x2b, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, + + // tag2 bool + 0x13, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + + // tag3 tinyint + 0x13, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, + + // tag4 smallint + 0x14, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x02, 0x00, + + // tag5 int + 0x16, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + + // tag6 bigint + 0x1a, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + // tag7 float + 0x16, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xb0, 0x40, + + // tag8 double + 0x1a, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x1a, 0x40, + + // tag9 utinyint + 0x13, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x07, + + // tag10 usmallint + 0x14, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x08, 0x00, + + // tag11 uint + 0x16, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, + + // tag12 ubigint + 0x1a, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + // tag13 binary + 0x1c, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + // haveLength + 0x01, + // length + 0x06, 0x00, 0x00, 0x00, + //buffer length + 0x06, 0x00, 0x00, 0x00, + 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, + + // tag14 nchar + 0x1b, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x01, + 0x05, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x6e, 0x63, 0x68, 0x61, 0x72, + + // tag15 geometry + 0x2b, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x01, + 0x15, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, + + // tag16 varbinary + 0x1f, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x01, + 0x09, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, + 0x76, 0x61, 0x72, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, + + // ColDataLength + 0x61, 0x02, 0x00, 0x00, + + // ColBuffer + // col1 timestamp + // TotalLength + 0x2c, 0x00, 0x00, 0x00, + // Type + 0x09, 0x00, 0x00, 0x00, + // Num + 0x03, 0x00, 0x00, 0x00, + // IsNull + 0x00, 0x00, 0x00, + //haveLength + 0x00, + // BufferLength + 0x18, 0x00, 0x00, 0x00, + // Buffer + 0x32, 0x2b, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, + 0x1a, 0x2f, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, + 0x02, 0x33, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, + + // col2 bool + 0x17, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + // is null, row index 1 is null + 0x00, 0x01, 0x00, + 0x00, + 0x03, 0x00, 0x00, 0x00, + + // row0 + 0x01, + // row1 + 0x00, + // row2 + 0x00, + + // col3 tinyint + 0x17, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, + 0x00, + 0x03, 0x00, 0x00, 0x00, + + 0x0b, + 0x00, + 0x0c, + + // col4 smallint + 0x1a, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, + 0x00, + 0x06, 0x00, 0x00, 0x00, + + 0x0b, 0x00, + 0x00, 0x00, + 0x0c, 0x00, + + // col5 int + 0x20, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, + 0x00, + 0x0c, 0x00, 0x00, 0x00, + + 0x0b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, + + // col6 bigint + 0x2c, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, + 0x00, + 0x18, 0x00, 0x00, 0x00, + + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + // col7 float + 0x20, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, + 0x00, + 0x0c, 0x00, 0x00, 0x00, + 0x33, 0x33, 0x33, 0x41, + 0x00, 0x00, 0x00, 0x00, + 0x33, 0x33, 0x43, 0x41, + + // col8 double + 0x2c, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, + 0x00, + 0x18, 0x00, 0x00, 0x00, + + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x26, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x28, 0x40, + + // col9 utinyint + 0x17, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, + 0x00, + 0x03, 0x00, 0x00, 0x00, + + 0x0b, + 0x00, + 0x0c, + + // col10 usmallint + 0x1a, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, + 0x00, + 0x06, 0x00, 0x00, 0x00, + + 0x0b, 0x00, + 0x00, 0x00, + 0x0c, 0x00, + + // col11 uint + 0x20, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, + 0x00, + 0x0c, 0x00, 0x00, 0x00, + + 0x0b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, + + // col12 ubigint + 0x2C, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, + 0x00, + 0x18, 0x00, 0x00, 0x00, + + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + // col13 binary + 0x2e, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, + // have length + 0x01, + // length + 0x07, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, + // buffer length + 0x0e, 0x00, 0x00, 0x00, + // buffer + 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x31, + 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x32, + + // col14 nchar + 0x2c, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, + 0x01, + // length + 0x06, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, + // buffer length + 0x0c, 0x00, 0x00, 0x00, + // buffer + 0x6e, 0x63, 0x68, 0x61, 0x72, 0x31, + 0x6e, 0x63, 0x68, 0x61, 0x72, 0x32, + + // col15 geometry + 0x4a, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, + 0x01, + // length + 0x15, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x00, 0x00, + // buffer length + 0x2a, 0x00, 0x00, 0x00, + // buffer + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, + + // col16 varbinary + 0x34, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, + 0x01, + // length + 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, + // buffer length + 0x14, 0x00, 0x00, 0x00, + // buffer + 0x76, 0x61, 0x72, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x31, + 0x76, 0x61, 0x72, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x32, + }, + wantErr: false, + }, + { + name: "TestQuery", + args: args{ + t: []*TaosStmt2BindData{ + { + Cols: [][]driver.Value{ + { + // ts 1726803356466 + time.Unix(1726803356, 466000000).UTC(), + }, + { + // BOOL + true, + }, + { + // TINYINT + int8(11), + }, + { + // SMALLINT + int16(11), + }, + { + // INT + int32(11), + }, + { + // BIGINT + int64(11), + }, + { + // FLOAT + float32(11.2), + }, + { + // DOUBLE + float64(11.2), + }, + { + // TINYINT UNSIGNED + uint8(11), + }, + { + // SMALLINT UNSIGNED + uint16(11), + }, + { + // INT UNSIGNED + uint32(11), + }, + { + // BIGINT UNSIGNED + uint64(11), + }, + { + // Bytes + []byte("binary1"), + }, + { + // String + "nchar1", + }, + }, + }, + }, + isInsert: false, + }, + want: []byte{ + // total Length + 0x78, 0x01, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x00, 0x00, 0x00, 0x00, + // ColCount + 0x0e, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x00, 0x00, 0x00, 0x00, + // TagsOffset + 0x00, 0x00, 0x00, 0x00, + // ColOffset + 0x1c, 0x00, 0x00, 0x00, + // cols + // col length + 0x58, 0x01, 0x00, 0x00, + //table 0 cols + //col 0 + //total length + 0x2e, 0x00, 0x00, 0x00, + //type + 0x08, 0x00, 0x00, 0x00, + //num + 0x01, 0x00, 0x00, 0x00, + //is null + 0x00, + // haveLength + 0x01, + // length + 0x18, 0x00, 0x00, 0x00, + // buffer length + 0x18, 0x00, 0x00, 0x00, + 0x32, 0x30, 0x32, 0x34, 0x2d, 0x30, 0x39, 0x2d, 0x32, 0x30, 0x54, 0x30, 0x33, 0x3a, 0x33, 0x35, 0x3a, 0x35, 0x36, 0x2e, 0x34, 0x36, 0x36, 0x5a, + + //col 1 + //total length + 0x13, 0x00, 0x00, 0x00, + //type + 0x01, 0x00, 0x00, 0x00, + //num + 0x01, 0x00, 0x00, 0x00, + //is null + 0x00, + // haveLength + 0x00, + // buffer length + 0x01, 0x00, 0x00, 0x00, + 0x01, + + //col 2 + //total length + 0x13, 0x00, 0x00, 0x00, + //type + 0x02, 0x00, 0x00, 0x00, + //num + 0x01, 0x00, 0x00, 0x00, + //is null + 0x00, + // haveLength + 0x00, + // buffer length + 0x01, 0x00, 0x00, 0x00, + 0x0b, + + //col 3 + //total length + 0x14, 0x00, 0x00, 0x00, + //type + 0x03, 0x00, 0x00, 0x00, + //num + 0x01, 0x00, 0x00, 0x00, + //is null + 0x00, + // haveLength + 0x00, + // buffer length + 0x02, 0x00, 0x00, 0x00, + 0x0b, 0x00, + + //col 4 + //total length + 0x16, 0x00, 0x00, 0x00, + //type + 0x04, 0x00, 0x00, 0x00, + //num + 0x01, 0x00, 0x00, 0x00, + //is null + 0x00, + // haveLength + 0x00, + // buffer length + 0x04, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, + + //col 5 + //total length + 0x1a, 0x00, 0x00, 0x00, + //type + 0x05, 0x00, 0x00, 0x00, + //num + 0x01, 0x00, 0x00, 0x00, + //is null + 0x00, + // haveLength + 0x00, + // buffer length + 0x08, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + //col 6 + //total length + 0x16, 0x00, 0x00, 0x00, + //type + 0x06, 0x00, 0x00, 0x00, + //num + 0x01, 0x00, 0x00, 0x00, + //is null + 0x00, + // haveLength + 0x00, + // buffer length + 0x04, 0x00, 0x00, 0x00, + 0x33, 0x33, 0x33, 0x41, + + //col 7 + //total length + 0x1a, 0x00, 0x00, 0x00, + //type + 0x07, 0x00, 0x00, 0x00, + //num + 0x01, 0x00, 0x00, 0x00, + //is null + 0x00, + // haveLength + 0x00, + // buffer length + 0x08, 0x00, 0x00, 0x00, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x26, 0x40, + + //col 8 + //total length + 0x13, 0x00, 0x00, 0x00, + //type + 0x0b, 0x00, 0x00, 0x00, + //num + 0x01, 0x00, 0x00, 0x00, + //is null + 0x00, + // haveLength + 0x00, + // buffer length + 0x01, 0x00, 0x00, 0x00, + 0x0b, + + //col 9 + //total length + 0x14, 0x00, 0x00, 0x00, + //type + 0x0c, 0x00, 0x00, 0x00, + //num + 0x01, 0x00, 0x00, 0x00, + //is null + 0x00, + // haveLength + 0x00, + // buffer length + 0x02, 0x00, 0x00, 0x00, + 0x0b, 0x00, + + //col 10 + //total length + 0x16, 0x00, 0x00, 0x00, + //type + 0x0d, 0x00, 0x00, 0x00, + //num + 0x01, 0x00, 0x00, 0x00, + //is null + 0x00, + // haveLength + 0x00, + // buffer length + 0x04, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, + + //col 11 + //total length + 0x1a, 0x00, 0x00, 0x00, + //type + 0x0e, 0x00, 0x00, 0x00, + //num + 0x01, 0x00, 0x00, 0x00, + //is null + 0x00, + // haveLength + 0x00, + // buffer length + 0x08, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + //col 12 + //total length + 0x1d, 0x00, 0x00, 0x00, + //type + 0x08, 0x00, 0x00, 0x00, + //num + 0x01, 0x00, 0x00, 0x00, + //is null + 0x00, + // haveLength + 0x01, + // length + 0x07, 0x00, 0x00, 0x00, + // buffer length + 0x07, 0x00, 0x00, 0x00, + 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x31, + + //col 13 + //total length + 0x1c, 0x00, 0x00, 0x00, + //type + 0x08, 0x00, 0x00, 0x00, + //num + 0x01, 0x00, 0x00, 0x00, + //is null + 0x00, + // haveLength + 0x01, + // length + 0x06, 0x00, 0x00, 0x00, + // buffer length + 0x06, 0x00, 0x00, 0x00, + 0x6e, 0x63, 0x68, 0x61, 0x72, 0x31, + }, + wantErr: false, + }, + { + name: "Three Table", + args: args{ + t: []*TaosStmt2BindData{ + { + TableName: "table1", + Cols: [][]driver.Value{ + { + // ts 1726803356466 + time.Unix(1726803356, 466000000), + }, + { + int64(1), + }, + }, + Tags: []driver.Value{int32(1)}, + }, + { + TableName: "table2", + Cols: [][]driver.Value{ + { + // ts 1726803356466 + time.Unix(1726803356, 466000000), + }, + { + int64(2), + }, + }, + Tags: []driver.Value{int32(2)}, + }, + { + TableName: "table3", + Cols: [][]driver.Value{ + { + // ts 1726803356466 + time.Unix(1726803356, 466000000), + }, + { + int64(3), + }, + }, + Tags: []driver.Value{int32(3)}, + }, + }, + colType: []*StmtField{ + { + FieldType: common.TSDB_DATA_TYPE_TIMESTAMP, + Precision: common.PrecisionMilliSecond, + }, + { + FieldType: common.TSDB_DATA_TYPE_BIGINT, + }, + }, + tagType: []*StmtField{ + { + FieldType: common.TSDB_DATA_TYPE_INT, + }, + }, + isInsert: true, + }, + want: []byte{ + // TotalLength + 0x2d, 0x01, 0x00, 0x00, + // tableCount + 0x03, 0x00, 0x00, 0x00, + // TagCount + 0x01, 0x00, 0x00, 0x00, + // ColCount + 0x02, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x1c, 0x00, 0x00, 0x00, + // TagsOffset + 0x37, 0x00, 0x00, 0x00, + // ColsOffset + 0x85, 0x00, 0x00, 0x00, + // TableNameLength + 0x07, 0x00, + 0x07, 0x00, + 0x07, 0x00, + // TableNameBuffer + 0x74, 0x61, 0x62, 0x6c, 0x65, 0x31, 0x00, + 0x74, 0x61, 0x62, 0x6c, 0x65, 0x32, 0x00, + 0x74, 0x61, 0x62, 0x6c, 0x65, 0x33, 0x00, + // TagsDataLength + 0x16, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, + // TagsBuffer + 0x16, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + + 0x16, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, + + 0x16, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, + + // ColDataLength + 0x34, 0x00, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, + + // ColBuffer + 0x1a, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x32, 0x2b, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, + + 0x1a, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x1a, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x32, 0x2b, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, + + 0x1a, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x1a, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x32, 0x2b, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, + + 0x1a, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + wantErr: false, + }, + { + name: "empty", + args: args{ + t: nil, + isInsert: false, + tagType: nil, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "wrong tag count", + args: args{ + t: []*TaosStmt2BindData{ + { + Tags: []driver.Value{int32(1)}, + }, + }, + isInsert: true, + tagType: nil, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "wrong col count", + args: args{ + t: []*TaosStmt2BindData{ + { + Cols: [][]driver.Value{ + { + int32(1), + }, + }, + }, + }, + isInsert: true, + tagType: nil, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "query has tag type", + args: args{ + t: []*TaosStmt2BindData{ + { + Cols: [][]driver.Value{ + { + int32(1), + }, + }, + }, + }, + isInsert: false, + tagType: []*StmtField{{ + FieldType: common.TSDB_DATA_TYPE_INT, + }}, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "query has col type", + args: args{ + t: []*TaosStmt2BindData{ + { + Cols: [][]driver.Value{ + { + int32(1), + }, + }, + }, + }, + isInsert: false, + tagType: nil, + colType: []*StmtField{{ + FieldType: common.TSDB_DATA_TYPE_INT, + }}, + }, + want: nil, + wantErr: true, + }, + { + name: "query has multi data", + args: args{ + t: []*TaosStmt2BindData{ + { + Cols: [][]driver.Value{ + { + int32(1), + }, + }, + }, + { + Cols: [][]driver.Value{ + { + int32(1), + }, + }, + }, + }, + isInsert: false, + tagType: nil, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "query has tablename", + args: args{ + t: []*TaosStmt2BindData{ + { + TableName: "table1", + }, + }, + isInsert: false, + tagType: nil, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "query has tag", + args: args{ + t: []*TaosStmt2BindData{ + { + Tags: []driver.Value{int32(1)}, + }, + }, + isInsert: false, + tagType: nil, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "query without data", + args: args{ + t: []*TaosStmt2BindData{ + {}, + }, + isInsert: false, + tagType: nil, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "query with multi rows", + args: args{ + t: []*TaosStmt2BindData{ + { + Cols: [][]driver.Value{ + { + int32(1), + int32(1), + }, + }, + }, + }, + isInsert: false, + tagType: nil, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "wrong bool", + args: args{ + t: []*TaosStmt2BindData{{ + Tags: []driver.Value{int32(1)}, + }}, + isInsert: true, + tagType: []*StmtField{{FieldType: common.TSDB_DATA_TYPE_BOOL}}, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "wrong tinyint", + args: args{ + t: []*TaosStmt2BindData{{ + Tags: []driver.Value{true}, + }}, + isInsert: true, + tagType: []*StmtField{{FieldType: common.TSDB_DATA_TYPE_TINYINT}}, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "wrong smallint", + args: args{ + t: []*TaosStmt2BindData{{ + Tags: []driver.Value{true}, + }}, + isInsert: true, + tagType: []*StmtField{{FieldType: common.TSDB_DATA_TYPE_SMALLINT}}, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "wrong int", + args: args{ + t: []*TaosStmt2BindData{{ + Tags: []driver.Value{true}, + }}, + isInsert: true, + tagType: []*StmtField{{FieldType: common.TSDB_DATA_TYPE_INT}}, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "wrong bigint", + args: args{ + t: []*TaosStmt2BindData{{ + Tags: []driver.Value{true}, + }}, + isInsert: true, + tagType: []*StmtField{{FieldType: common.TSDB_DATA_TYPE_BIGINT}}, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "wrong tinyint unsigned", + args: args{ + t: []*TaosStmt2BindData{{ + Tags: []driver.Value{true}, + }}, + isInsert: true, + tagType: []*StmtField{{FieldType: common.TSDB_DATA_TYPE_UTINYINT}}, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "wrong smallint unsigned", + args: args{ + t: []*TaosStmt2BindData{{ + Tags: []driver.Value{true}, + }}, + isInsert: true, + tagType: []*StmtField{{FieldType: common.TSDB_DATA_TYPE_USMALLINT}}, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "wrong int unsigned", + args: args{ + t: []*TaosStmt2BindData{{ + Tags: []driver.Value{true}, + }}, + isInsert: true, + tagType: []*StmtField{{FieldType: common.TSDB_DATA_TYPE_UINT}}, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "wrong bigint unsigned", + args: args{ + t: []*TaosStmt2BindData{{ + Tags: []driver.Value{true}, + }}, + isInsert: true, + tagType: []*StmtField{{FieldType: common.TSDB_DATA_TYPE_UBIGINT}}, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "wrong float", + args: args{ + t: []*TaosStmt2BindData{{ + Tags: []driver.Value{true}, + }}, + isInsert: true, + tagType: []*StmtField{{FieldType: common.TSDB_DATA_TYPE_FLOAT}}, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "wrong double", + args: args{ + t: []*TaosStmt2BindData{{ + Tags: []driver.Value{true}, + }}, + isInsert: true, + tagType: []*StmtField{{FieldType: common.TSDB_DATA_TYPE_DOUBLE}}, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "wrong binary", + args: args{ + t: []*TaosStmt2BindData{{ + Tags: []driver.Value{true}, + }}, + isInsert: true, + tagType: []*StmtField{{FieldType: common.TSDB_DATA_TYPE_BINARY}}, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "wrong timestamp", + args: args{ + t: []*TaosStmt2BindData{{ + Tags: []driver.Value{true}, + }}, + isInsert: true, + tagType: []*StmtField{{FieldType: common.TSDB_DATA_TYPE_TIMESTAMP}}, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "insert nil timestamp", + args: args{ + t: []*TaosStmt2BindData{ + { + Cols: [][]driver.Value{ + { + time.Unix(1726803356, 466000000), + nil, + }, + }, + }, + }, + isInsert: true, + tagType: nil, + colType: []*StmtField{{FieldType: common.TSDB_DATA_TYPE_TIMESTAMP}}, + }, + want: []byte{ + // total Length + 0x43, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x00, 0x00, 0x00, 0x00, + // ColCount + 0x01, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x00, 0x00, 0x00, 0x00, + // TagsOffset + 0x00, 0x00, 0x00, 0x00, + // ColOffset + 0x1c, 0x00, 0x00, 0x00, + // cols + // col length + 0x23, 0x00, 0x00, 0x00, + //table 0 cols + //col 0 + //total length + 0x23, 0x00, 0x00, 0x00, + //type + 0x09, 0x00, 0x00, 0x00, + //num + 0x02, 0x00, 0x00, 0x00, + //is null + 0x00, + 0x01, + // haveLength + 0x00, + // buffer length + 0x10, 0x00, 0x00, 0x00, + 0x32, 0x2b, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + wantErr: false, + }, + { + name: "query bool false", + args: args{ + t: []*TaosStmt2BindData{{ + Cols: [][]driver.Value{ + {false}, + }, + }}, + isInsert: false, + tagType: nil, + colType: nil, + }, + want: []byte{ + // total Length + 0x33, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x00, 0x00, 0x00, 0x00, + // ColCount + 0x01, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x00, 0x00, 0x00, 0x00, + // TagsOffset + 0x00, 0x00, 0x00, 0x00, + // ColOffset + 0x1c, 0x00, 0x00, 0x00, + // cols + // col length + 0x13, 0x00, 0x00, 0x00, + //table 0 cols + //col 0 + //total length + 0x13, 0x00, 0x00, 0x00, + //type + 0x01, 0x00, 0x00, 0x00, + //num + 0x01, 0x00, 0x00, 0x00, + //is null + 0x00, + // haveLength + 0x00, + // buffer length + 0x01, 0x00, 0x00, 0x00, + 0x00, + }, + wantErr: false, + }, + { + name: "query unsupported type", + args: args{ + t: []*TaosStmt2BindData{{ + Cols: [][]driver.Value{ + {customInt(1)}, + }, + }}, + isInsert: false, + tagType: nil, + colType: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "insert unsupported type", + args: args{ + t: []*TaosStmt2BindData{{ + Cols: [][]driver.Value{ + {int32(1)}, + }, + }}, + isInsert: true, + tagType: nil, + colType: []*StmtField{{FieldType: common.TSDB_DATA_TYPE_NULL}}, + }, + want: nil, + wantErr: true, + }, + { + name: "nil", + args: args{ + t: []*TaosStmt2BindData{ + { + Cols: nil, + }, + }, + isInsert: true, + tagType: nil, + colType: []*StmtField{}, + }, + want: nil, + wantErr: true, + }, + { + name: "int64 timestamp", + args: args{ + t: []*TaosStmt2BindData{{ + Tags: []driver.Value{int64(1726803356466)}, + }}, + isInsert: true, + tagType: []*StmtField{{FieldType: common.TSDB_DATA_TYPE_TIMESTAMP}}, + colType: nil, + }, + want: []byte{ + // total Length + 0x3a, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x01, 0x00, 0x00, 0x00, + // ColCount + 0x00, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x00, 0x00, 0x00, 0x00, + // TagsOffset + 0x1c, 0x00, 0x00, 0x00, + // ColOffset + 0x00, 0x00, 0x00, 0x00, + // tags + // table length + 0x1a, 0x00, 0x00, 0x00, + //table 0 tags + //tag 0 + //total length + 0x1a, 0x00, 0x00, 0x00, + //type + 0x09, 0x00, 0x00, 0x00, + //num + 0x01, 0x00, 0x00, 0x00, + //is null + 0x00, + // haveLength + 0x00, + // buffer length + 0x08, 0x00, 0x00, 0x00, + 0x32, 0x2b, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := MarshalStmt2Binary(tt.args.t, tt.args.isInsert, tt.args.colType, tt.args.tagType) + if (err != nil) != tt.wantErr { + t.Errorf("MarshalStmt2Binary() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.want, got) + }) + } +} + +func TestT(t *testing.T) { + +} diff --git a/driver/common/tmq/config.go b/driver/common/tmq/config.go new file mode 100644 index 00000000..b17084cb --- /dev/null +++ b/driver/common/tmq/config.go @@ -0,0 +1,34 @@ +package tmq + +import ( + "fmt" + "reflect" +) + +type ConfigValue interface{} +type ConfigMap map[string]ConfigValue + +func (m ConfigMap) Get(key string, defval ConfigValue) (ConfigValue, error) { + return m.get(key, defval) +} + +func (m ConfigMap) get(key string, defval ConfigValue) (ConfigValue, error) { + v, ok := m[key] + if !ok { + return defval, nil + } + + if defval != nil && reflect.TypeOf(defval) != reflect.TypeOf(v) { + return nil, fmt.Errorf("%s expects type %T, not %T", key, defval, v) + } + + return v, nil +} + +func (m ConfigMap) Clone() ConfigMap { + m2 := make(ConfigMap) + for k, v := range m { + m2[k] = v + } + return m2 +} diff --git a/driver/common/tmq/config_test.go b/driver/common/tmq/config_test.go new file mode 100644 index 00000000..c5a62d5c --- /dev/null +++ b/driver/common/tmq/config_test.go @@ -0,0 +1,52 @@ +package tmq + +import ( + "fmt" + "reflect" + "testing" +) + +func TestConfigMap_Get(t *testing.T) { + t.Parallel() + + config := ConfigMap{ + "key1": "value1", + "key2": 123, + } + + t.Run("Existing Key", func(t *testing.T) { + want := "value1" + if got, err := config.Get("key1", nil); err != nil || got != want { + t.Errorf("Get() = %v, want %v (error: %v)", got, want, err) + } + }) + + t.Run("Type Mismatch", func(t *testing.T) { + wantErr := fmt.Errorf("key2 expects type string, not int") + if got, err := config.Get("key2", "default"); err == nil || got != nil || err.Error() != wantErr.Error() { + t.Errorf("Get() = %v, want error: %v", got, wantErr) + } + }) + + t.Run("Non-Existing Key with Default Value", func(t *testing.T) { + want := "default" + if got, err := config.Get("key3", "default"); err != nil || got != want { + t.Errorf("Get() = %v, want %v (error: %v)", got, want, err) + } + }) +} + +func TestConfigMap_Clone(t *testing.T) { + t.Parallel() + + config := ConfigMap{ + "key1": "value1", + "key2": 123, + } + + clone := config.Clone() + + if !reflect.DeepEqual(config, clone) { + t.Errorf("Clone() = %v, want %v", clone, config) + } +} diff --git a/driver/common/tmq/event.go b/driver/common/tmq/event.go new file mode 100644 index 00000000..e64d4302 --- /dev/null +++ b/driver/common/tmq/event.go @@ -0,0 +1,204 @@ +package tmq + +import ( + "database/sql/driver" + "encoding/json" + "fmt" + + taosError "github.com/taosdata/taosadapter/v3/driver/errors" +) + +type Data struct { + TableName string + Data [][]driver.Value +} +type Event interface { + String() string +} + +type Error struct { + code int + str string +} + +const ErrorOther = 0xffff + +func NewTMQError(code int, str string) Error { + return Error{ + code: code, + str: str, + } +} + +func NewTMQErrorWithErr(err error) Error { + tErr, ok := err.(*taosError.TaosError) + if ok { + return Error{ + code: int(tErr.Code), + str: tErr.ErrStr, + } + } + return Error{ + code: ErrorOther, + str: err.Error(), + } +} + +func (e Error) String() string { + return fmt.Sprintf("[0x%x] %s", e.code, e.str) +} + +func (e Error) Error() string { + return e.String() +} + +func (e Error) Code() int { + return e.code +} + +type Message interface { + Topic() string + DBName() string + Value() interface{} + Offset() int64 +} + +type DataMessage struct { + TopicPartition TopicPartition + dbName string + topic string + data []*Data + offset Offset +} + +func (m *DataMessage) String() string { + data, _ := json.Marshal(m.data) + return fmt.Sprintf("DataMessage: %s[%s]:%s", m.topic, m.dbName, string(data)) +} + +func (m *DataMessage) SetDbName(dbName string) { + m.dbName = dbName +} + +func (m *DataMessage) SetTopic(topic string) { + m.topic = topic +} + +func (m *DataMessage) SetData(data []*Data) { + m.data = data +} + +func (m *DataMessage) SetOffset(offset Offset) { + m.offset = offset +} + +func (m *DataMessage) Topic() string { + return m.topic +} + +func (m *DataMessage) DBName() string { + return m.dbName +} + +func (m *DataMessage) Value() interface{} { + return m.data +} + +func (m *DataMessage) Offset() Offset { + return m.offset +} + +type MetaMessage struct { + TopicPartition TopicPartition + dbName string + topic string + offset Offset + meta *Meta +} + +func (m *MetaMessage) Offset() Offset { + return m.offset +} + +func (m *MetaMessage) String() string { + data, _ := json.Marshal(m.meta) + return fmt.Sprintf("MetaMessage: %s[%s]:%s", m.topic, m.dbName, string(data)) +} + +func (m *MetaMessage) SetDbName(dbName string) { + m.dbName = dbName +} + +func (m *MetaMessage) SetTopic(topic string) { + m.topic = topic +} + +func (m *MetaMessage) SetOffset(offset Offset) { + m.offset = offset +} + +func (m *MetaMessage) SetMeta(meta *Meta) { + m.meta = meta +} + +func (m *MetaMessage) Topic() string { + return m.topic +} + +func (m *MetaMessage) DBName() string { + return m.dbName +} + +func (m *MetaMessage) Value() interface{} { + return m.meta +} + +type MetaDataMessage struct { + TopicPartition TopicPartition + dbName string + topic string + offset Offset + metaData *MetaData +} + +func (m *MetaDataMessage) Offset() Offset { + return m.offset +} + +func (m *MetaDataMessage) String() string { + data, _ := json.Marshal(m.metaData) + return fmt.Sprintf("MetaDataMessage: %s[%s]:%s", m.topic, m.dbName, string(data)) +} + +func (m *MetaDataMessage) SetDbName(dbName string) { + m.dbName = dbName +} + +func (m *MetaDataMessage) SetTopic(topic string) { + m.topic = topic +} + +func (m *MetaDataMessage) SetOffset(offset Offset) { + m.offset = offset +} + +func (m *MetaDataMessage) SetMetaData(metaData *MetaData) { + m.metaData = metaData +} + +type MetaData struct { + Meta *Meta + Data []*Data +} + +func (m *MetaDataMessage) Topic() string { + return m.topic +} + +func (m *MetaDataMessage) DBName() string { + return m.dbName +} + +func (m *MetaDataMessage) Value() interface{} { + return m.metaData +} diff --git a/driver/common/tmq/event_test.go b/driver/common/tmq/event_test.go new file mode 100644 index 00000000..12812fd9 --- /dev/null +++ b/driver/common/tmq/event_test.go @@ -0,0 +1,352 @@ +package tmq + +import ( + "database/sql/driver" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + taosError "github.com/taosdata/taosadapter/v3/driver/errors" +) + +func TestDataMessage_String(t *testing.T) { + t.Parallel() + + data := []*Data{ + {TableName: "table1", Data: [][]driver.Value{{1, "data1"}}}, + {TableName: "table2", Data: [][]driver.Value{{2, "data2"}}}, + } + message := &DataMessage{ + TopicPartition: TopicPartition{ + Topic: stringPtr("test-topic"), + Partition: 0, + Offset: 100, + }, + dbName: "test-db", + topic: "test-topic", + data: data, + offset: 100, + } + + want := `DataMessage: test-topic[test-db]:[{"TableName":"table1","Data":[[1,"data1"]]},{"TableName":"table2","Data":[[2,"data2"]]}]` + + if got := message.String(); got != want { + t.Errorf("DataMessage.String() = %v, want %v", got, want) + } +} + +func TestMetaMessage_String(t *testing.T) { + t.Parallel() + + meta := &Meta{ + Type: "type", + TableName: "table", + TableType: "tableType", + } + message := &MetaMessage{ + TopicPartition: TopicPartition{ + Topic: stringPtr("test-topic"), + Partition: 0, + Offset: 100, + }, + dbName: "test-db", + topic: "test-topic", + offset: 100, + meta: meta, + } + + want := `MetaMessage: test-topic[test-db]:{"type":"type","tableName":"table","tableType":"tableType","createList":null,"columns":null,"using":"","tagNum":0,"tags":null,"tableNameList":null,"alterType":0,"colName":"","colNewName":"","colType":0,"colLength":0,"colValue":"","colValueNull":false}` + + if got := message.String(); got != want { + t.Errorf("MetaMessage.String() = %v, want %v", got, want) + } +} + +func TestMetaDataMessage_String(t *testing.T) { + t.Parallel() + + meta := &Meta{ + Type: "type", + TableName: "table", + TableType: "tableType", + } + data := []*Data{ + {TableName: "table1", Data: [][]driver.Value{{1, "data1"}}}, + {TableName: "table2", Data: [][]driver.Value{{2, "data2"}}}, + } + metaData := &MetaData{ + Meta: meta, + Data: data, + } + message := &MetaDataMessage{ + TopicPartition: TopicPartition{ + Topic: stringPtr("test-topic"), + Partition: 0, + Offset: 100, + }, + dbName: "test-db", + topic: "test-topic", + offset: 100, + metaData: metaData, + } + + want := `MetaDataMessage: test-topic[test-db]:{"Meta":{"type":"type","tableName":"table","tableType":"tableType","createList":null,"columns":null,"using":"","tagNum":0,"tags":null,"tableNameList":null,"alterType":0,"colName":"","colNewName":"","colType":0,"colLength":0,"colValue":"","colValueNull":false},"Data":[{"TableName":"table1","Data":[[1,"data1"]]},{"TableName":"table2","Data":[[2,"data2"]]}]}` + if got := message.String(); got != want { + t.Errorf("MetaDataMessage.String() = %v, want %v", got, want) + } +} + +func TestNewTMQError(t *testing.T) { + t.Parallel() + + code := 123 + str := "test error" + err := NewTMQError(code, str) + + if err.code != code { + t.Errorf("NewTMQError() code = %v, want %v", err.code, code) + } + + if err.str != str { + t.Errorf("NewTMQError() str = %v, want %v", err.str, str) + } +} + +func TestNewTMQErrorWithErr(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + err error + code int + str string + }{ + { + name: "TaosError", + err: &taosError.TaosError{ + Code: 456, + ErrStr: "taos error", + }, + code: 456, + str: "taos error", + }, + { + name: "OtherError", + err: fmt.Errorf("other error"), + code: ErrorOther, + str: "other error", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := NewTMQErrorWithErr(tc.err) + + if err.code != tc.code { + t.Errorf("NewTMQErrorWithErr() code = %v, want %v", err.code, tc.code) + } + + if err.str != tc.str { + t.Errorf("NewTMQErrorWithErr() str = %v, want %v", err.str, tc.str) + } + }) + } +} + +func TestError_String(t *testing.T) { + t.Parallel() + + code := 789 + str := "test error" + err := Error{code: code, str: str} + want := fmt.Sprintf("[0x%x] %s", code, str) + + if got := err.String(); got != want { + t.Errorf("Error.String() = %v, want %v", got, want) + } +} + +func TestError_Error(t *testing.T) { + t.Parallel() + + code := 789 + str := "test error" + err := Error{code: code, str: str} + want := fmt.Sprintf("[0x%x] %s", code, str) + + if got := err.Error(); got != want { + t.Errorf("Error.Error() = %v, want %v", got, want) + } +} + +func TestError_Code(t *testing.T) { + t.Parallel() + + code := 789 + err := Error{code: code} + + if got := err.Code(); got != code { + t.Errorf("Error.Code() = %v, want %v", got, code) + } +} + +func TestMetaMessage_Offset(t *testing.T) { + t.Parallel() + + message := &MetaMessage{ + offset: 100, + } + + want := Offset(100) + if got := message.Offset(); got != want { + t.Errorf("Offset() = %v, want %v", got, want) + } +} + +func TestMetaMessage_SetDbName(t *testing.T) { + t.Parallel() + + message := &MetaMessage{} + message.SetDbName("test-db") + + want := "test-db" + if got := message.DBName(); got != want { + t.Errorf("DBName() = %v, want %v", got, want) + } +} + +func TestMetaMessage_SetTopic(t *testing.T) { + t.Parallel() + + message := &MetaMessage{} + message.SetTopic("test-topic") + + want := "test-topic" + if got := message.Topic(); got != want { + t.Errorf("Topic() = %v, want %v", got, want) + } +} + +func TestMetaMessage_SetOffset(t *testing.T) { + t.Parallel() + + message := &MetaMessage{} + message.SetOffset(200) + + want := Offset(200) + if got := message.Offset(); got != want { + t.Errorf("Offset() = %v, want %v", got, want) + } +} + +func TestMetaMessage_SetMeta(t *testing.T) { + t.Parallel() + + meta := &Meta{} + message := &MetaMessage{} + message.SetMeta(meta) + + want := meta + if got := message.Value(); got != want { + t.Errorf("Value() = %v, want %v", got, want) + } +} + +func TestDataMessage_SetDbName(t *testing.T) { + t.Parallel() + + message := &DataMessage{} + message.SetDbName("test-db") + + want := "test-db" + if got := message.DBName(); got != want { + t.Errorf("DBName() = %v, want %v", got, want) + } +} + +func TestDataMessage_SetTopic(t *testing.T) { + t.Parallel() + + message := &DataMessage{} + message.SetTopic("test-topic") + + want := "test-topic" + if got := message.Topic(); got != want { + t.Errorf("Topic() = %v, want %v", got, want) + } +} + +func TestDataMessage_SetData(t *testing.T) { + t.Parallel() + + data := []*Data{ + {TableName: "table1", Data: [][]driver.Value{{1, "data1"}}}, + {TableName: "table2", Data: [][]driver.Value{{2, "data2"}}}, + } + message := &DataMessage{} + message.SetData(data) + + want := data + assert.Equal(t, want, message.Value()) +} + +func TestDataMessage_SetOffset(t *testing.T) { + t.Parallel() + + message := &DataMessage{} + message.SetOffset(200) + + want := Offset(200) + if got := message.Offset(); got != want { + t.Errorf("Offset() = %v, want %v", got, want) + } +} + +func TestMetaDataMessage_SetDbName(t *testing.T) { + t.Parallel() + + message := &MetaDataMessage{} + message.SetDbName("test-db") + + want := "test-db" + if got := message.DBName(); got != want { + t.Errorf("DBName() = %v, want %v", got, want) + } +} + +func TestMetaDataMessage_SetTopic(t *testing.T) { + t.Parallel() + + message := &MetaDataMessage{} + message.SetTopic("test-topic") + + want := "test-topic" + if got := message.Topic(); got != want { + t.Errorf("Topic() = %v, want %v", got, want) + } +} + +func TestMetaDataMessage_SetOffset(t *testing.T) { + t.Parallel() + + message := &MetaDataMessage{} + message.SetOffset(200) + + want := Offset(200) + if got := message.Offset(); got != want { + t.Errorf("Offset() = %v, want %v", got, want) + } +} + +func TestMetaDataMessage_SetMetaData(t *testing.T) { + t.Parallel() + + metaData := &MetaData{} + message := &MetaDataMessage{} + message.SetMetaData(metaData) + + want := metaData + if got := message.Value(); got != want { + t.Errorf("Value() = %v, want %v", got, want) + } +} diff --git a/driver/common/tmq/tmq.go b/driver/common/tmq/tmq.go new file mode 100644 index 00000000..a6e5617c --- /dev/null +++ b/driver/common/tmq/tmq.go @@ -0,0 +1,87 @@ +package tmq + +import "fmt" + +type Meta struct { + Type string `json:"type"` + TableName string `json:"tableName"` + TableType string `json:"tableType"` + CreateList []*CreateItem `json:"createList"` + Columns []*Column `json:"columns"` + Using string `json:"using"` + TagNum int `json:"tagNum"` + Tags []*Tag `json:"tags"` + TableNameList []string `json:"tableNameList"` + AlterType int `json:"alterType"` + ColName string `json:"colName"` + ColNewName string `json:"colNewName"` + ColType int `json:"colType"` + ColLength int `json:"colLength"` + ColValue string `json:"colValue"` + ColValueNull bool `json:"colValueNull"` +} + +type Tag struct { + Name string `json:"name"` + Type int `json:"type"` + Value interface{} `json:"value"` +} + +type Column struct { + Name string `json:"name"` + Type int `json:"type"` + Length int `json:"length"` +} + +type CreateItem struct { + TableName string `json:"tableName"` + Using string `json:"using"` + TagNum int `json:"tagNum"` + Tags []*Tag `json:"tags"` +} + +type Offset int64 + +const OffsetInvalid = Offset(-2147467247) + +func (o Offset) String() string { + if o == OffsetInvalid { + return "unset" + } + return fmt.Sprintf("%d", int64(o)) +} + +func (o Offset) Valid() bool { + if o < 0 && o != OffsetInvalid { + return false + } + return true +} + +type TopicPartition struct { + Topic *string + Partition int32 + Offset Offset + Metadata *string + Error error +} + +func (p TopicPartition) String() string { + topic := "" + if p.Topic != nil { + topic = *p.Topic + } + if p.Error != nil { + return fmt.Sprintf("%s[%d]@%s(%s)", + topic, p.Partition, p.Offset, p.Error) + } + return fmt.Sprintf("%s[%d]@%s", + topic, p.Partition, p.Offset) +} + +type Assignment struct { + VGroupID int32 `json:"vgroup_id"` + Offset int64 `json:"offset"` + Begin int64 `json:"begin"` + End int64 `json:"end"` +} diff --git a/driver/common/tmq/tmq_test.go b/driver/common/tmq/tmq_test.go new file mode 100644 index 00000000..ae9fefb5 --- /dev/null +++ b/driver/common/tmq/tmq_test.go @@ -0,0 +1,197 @@ +package tmq + +import ( + "encoding/json" + "errors" + "reflect" + "testing" +) + +const createJson = `{ + "type": "create", + "tableName": "t1", + "tableType": "super", + "columns": [ + { + "name": "c1", + "type": 0, + "length": 0 + }, + { + "name": "c2", + "type": 8, + "length": 8 + } + ], + "tags": [ + { + "name": "t1", + "type": 0, + "length": 0 + }, + { + "name": "t2", + "type": 8, + "length": 8 + } + ] +}` +const dropJson = `{ + "type":"drop", + "tableName":"t1", + "tableType":"super", + "tableNameList":["t1", "t2"] +}` + +// @author: xftan +// @date: 2023/10/13 11:19 +// @description: test json +func TestCreateJson(t *testing.T) { + var obj Meta + err := json.Unmarshal([]byte(createJson), &obj) + if err != nil { + t.Log(err) + return + } + t.Log(obj) +} + +// @author: xftan +// @date: 2023/10/13 11:19 +// @description: test drop json +func TestDropJson(t *testing.T) { + var obj Meta + err := json.Unmarshal([]byte(dropJson), &obj) + if err != nil { + t.Log(err) + return + } + t.Log(obj) +} + +func TestOffset_String(t *testing.T) { + tests := []struct { + name string + o Offset + want string + }{ + { + name: "Valid Offset", + o: 100, + want: "100", + }, + { + name: "Invalid Offset", + o: OffsetInvalid, + want: "unset", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.o.String(); got != tt.want { + t.Errorf("Offset.String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOffset_Valid(t *testing.T) { + tests := []struct { + name string + o Offset + want bool + }{ + { + name: "Valid Offset", + o: 100, + want: true, + }, + { + name: "Invalid Offset", + o: OffsetInvalid, + want: true, + }, + { + name: "Negative Offset", + o: -100, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.o.Valid(); got != tt.want { + t.Errorf("Offset.Valid() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTopicPartition_String(t *testing.T) { + tests := []struct { + name string + tp TopicPartition + want string + }{ + { + name: "With Error", + tp: TopicPartition{ + Topic: stringPtr("test-topic"), + Partition: 0, + Offset: 100, + Error: errors.New("error message"), + }, + want: "test-topic[0]@100(error message)", + }, + { + name: "Without Error", + tp: TopicPartition{ + Topic: stringPtr("test-topic"), + Partition: 0, + Offset: 100, + }, + want: "test-topic[0]@100", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.tp.String(); got != tt.want { + t.Errorf("TopicPartition.String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestAssignment_MarshalJSON(t *testing.T) { + tests := []struct { + name string + a Assignment + want string + }{ + { + name: "Marshal Assignment", + a: Assignment{ + VGroupID: 1, + Offset: 100, + Begin: 50, + End: 150, + }, + want: `{"vgroup_id":1,"offset":100,"begin":50,"end":150}`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := json.Marshal(tt.a) + if err != nil { + t.Errorf("MarshalJSON error: %v", err) + return + } + if !reflect.DeepEqual(string(got), tt.want) { + t.Errorf("MarshalJSON = %v, want %v", string(got), tt.want) + } + }) + } +} + +func stringPtr(s string) *string { + return &s +} diff --git a/driver/errors/errors.go b/driver/errors/errors.go new file mode 100644 index 00000000..7c932d89 --- /dev/null +++ b/driver/errors/errors.go @@ -0,0 +1,30 @@ +//! TDengine error codes. +//! THIS IS AUTO GENERATED FROM TDENGINE , MAKE SURE YOU KNOW WHAT YOU ARE CHANING. + +package errors + +import "fmt" + +type TaosError struct { + Code int32 + ErrStr string +} + +const ( + SUCCESS int32 = 0 + UNKNOWN int32 = 0xffff +) + +func (e *TaosError) Error() string { + if e.Code != UNKNOWN { + return fmt.Sprintf("[0x%x] %s", e.Code, e.ErrStr) + } + return e.ErrStr +} + +func NewError(code int, errStr string) error { + return &TaosError{ + Code: int32(code) & 0xffff, + ErrStr: errStr, + } +} diff --git a/driver/errors/errors_test.go b/driver/errors/errors_test.go new file mode 100644 index 00000000..2a426c51 --- /dev/null +++ b/driver/errors/errors_test.go @@ -0,0 +1,52 @@ +package errors + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// @author: xftan +// @date: 2023/10/13 11:20 +// @description: test new error +func TestNewError(t *testing.T) { + type args struct { + code int + errStr string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "common", + args: args{ + code: 0, + errStr: "success", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := NewError(tt.args.code, tt.args.errStr); (err != nil) != tt.wantErr { + t.Errorf("NewError() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestError(t *testing.T) { + var ErrTscInvalidConnection = &TaosError{ + Code: 0x020B, + ErrStr: "Invalid connection", + } + invalidError := ErrTscInvalidConnection.Error() + assert.Equal(t, "[0x20b] Invalid connection", invalidError) + unknownError := &TaosError{ + Code: 0xffff, + ErrStr: "unknown error", + } + assert.Equal(t, "unknown error", unknownError.Error()) +} diff --git a/driver/types/taostype.go b/driver/types/taostype.go new file mode 100644 index 00000000..f1bbcc26 --- /dev/null +++ b/driver/types/taostype.go @@ -0,0 +1,54 @@ +package types + +import ( + "reflect" + "time" +) + +type ( + TaosBool bool + TaosTinyint int8 + TaosSmallint int16 + TaosInt int32 + TaosBigint int64 + TaosUTinyint uint8 + TaosUSmallint uint16 + TaosUInt uint32 + TaosUBigint uint64 + TaosFloat float32 + TaosDouble float64 + TaosBinary []byte + TaosVarBinary []byte + TaosNchar string + TaosTimestamp struct { + T time.Time + Precision int + } + TaosJson []byte + TaosGeometry []byte +) + +var ( + TaosBoolType = reflect.TypeOf(TaosBool(false)) + TaosTinyintType = reflect.TypeOf(TaosTinyint(0)) + TaosSmallintType = reflect.TypeOf(TaosSmallint(0)) + TaosIntType = reflect.TypeOf(TaosInt(0)) + TaosBigintType = reflect.TypeOf(TaosBigint(0)) + TaosUTinyintType = reflect.TypeOf(TaosUTinyint(0)) + TaosUSmallintType = reflect.TypeOf(TaosUSmallint(0)) + TaosUIntType = reflect.TypeOf(TaosUInt(0)) + TaosUBigintType = reflect.TypeOf(TaosUBigint(0)) + TaosFloatType = reflect.TypeOf(TaosFloat(0)) + TaosDoubleType = reflect.TypeOf(TaosDouble(0)) + TaosBinaryType = reflect.TypeOf(TaosBinary(nil)) + TaosVarBinaryType = reflect.TypeOf(TaosVarBinary(nil)) + TaosNcharType = reflect.TypeOf(TaosNchar("")) + TaosTimestampType = reflect.TypeOf(TaosTimestamp{}) + TaosJsonType = reflect.TypeOf(TaosJson("")) + TaosGeometryType = reflect.TypeOf(TaosGeometry(nil)) +) + +type ColumnType struct { + Type reflect.Type + MaxLen int +} diff --git a/driver/types/types.go b/driver/types/types.go new file mode 100644 index 00000000..59b94d39 --- /dev/null +++ b/driver/types/types.go @@ -0,0 +1,492 @@ +package types + +import ( + "database/sql/driver" + "fmt" + "time" + + "github.com/taosdata/taosadapter/v3/driver/errors" +) + +type NullInt64 struct { + Inner int64 + Valid bool // Valid is true if Inner is not NULL +} + +// Scan implements the Scanner interface. +func (n *NullInt64) Scan(value interface{}) error { + if value == nil { + n.Inner, n.Valid = 0, false + return nil + } + n.Valid = true + v, ok := value.(int64) + if !ok { + return &errors.TaosError{Code: 0xffff, ErrStr: "taosSql parse int64 error"} + } + n.Inner = v + return nil +} + +// Value implements the driver Valuer interface. +func (n NullInt64) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Inner, nil +} + +func (n NullInt64) String() string { + if n.Valid { + return fmt.Sprintf("%v", n.Inner) + } + return "NULL" +} + +type NullInt32 struct { + Inner int32 + Valid bool // Valid is true if Inner is not NULL +} + +// Scan implements the Scanner interface. +func (n *NullInt32) Scan(value interface{}) error { + if value == nil { + n.Inner, n.Valid = 0, false + return nil + } + n.Valid = true + v, ok := value.(int32) + if !ok { + return &errors.TaosError{Code: 0xffff, ErrStr: "taosSql parse int32 error"} + } + n.Inner = v + return nil +} + +// Value implements the driver Valuer interface. +func (n NullInt32) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Inner, nil +} + +func (n NullInt32) String() string { + if n.Valid { + return fmt.Sprintf("%v", n.Inner) + } + return "NULL" +} + +type NullInt16 struct { + Inner int16 + Valid bool // Valid is true if Inner is not NULL +} + +// Scan implements the Scanner interface. +func (n *NullInt16) Scan(value interface{}) error { + if value == nil { + n.Inner, n.Valid = 0, false + return nil + } + n.Valid = true + v, ok := value.(int16) + if !ok { + return &errors.TaosError{Code: 0xffff, ErrStr: "taosSql parse int16 error"} + } + n.Inner = v + return nil +} + +// Value implements the driver Valuer interface. +func (n NullInt16) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Inner, nil +} + +func (n NullInt16) String() string { + if n.Valid { + return fmt.Sprintf("%v", n.Inner) + } + return "NULL" +} + +type NullInt8 struct { + Inner int8 + Valid bool // Valid is true if Inner is not NULL +} + +// Scan implements the Scanner interface. +func (n *NullInt8) Scan(value interface{}) error { + if value == nil { + n.Inner, n.Valid = 0, false + return nil + } + n.Valid = true + v, ok := value.(int8) + if !ok { + return &errors.TaosError{Code: 0xffff, ErrStr: "taosSql parse int8 error"} + } + n.Inner = v + return nil +} + +// Value implements the driver Valuer interface. +func (n NullInt8) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Inner, nil +} + +func (n NullInt8) String() string { + if n.Valid { + return fmt.Sprintf("%v", n.Inner) + } + return "NULL" +} + +type NullUInt64 struct { + Inner uint64 + Valid bool // Valid is true if Inner is not NULL +} + +// Scan implements the Scanner interface. +func (n *NullUInt64) Scan(value interface{}) error { + if value == nil { + n.Inner, n.Valid = 0, false + return nil + } + n.Valid = true + v, ok := value.(uint64) + if !ok { + return &errors.TaosError{Code: 0xffff, ErrStr: "taosSql parse uint64 error"} + } + n.Inner = v + return nil +} + +// Value implements the driver Valuer interface. +func (n NullUInt64) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Inner, nil +} + +func (n NullUInt64) String() string { + if n.Valid { + return fmt.Sprintf("%v", n.Inner) + } + return "NULL" +} + +type NullUInt32 struct { + Inner uint32 + Valid bool // Valid is true if Inner is not NULL +} + +// Scan implements the Scanner interface. +func (n *NullUInt32) Scan(value interface{}) error { + if value == nil { + n.Inner, n.Valid = 0, false + return nil + } + n.Valid = true + v, ok := value.(uint32) + if !ok { + return &errors.TaosError{Code: 0xffff, ErrStr: "taosSql parse uint32 error"} + } + n.Inner = v + return nil +} + +// Value implements the driver Valuer interface. +func (n NullUInt32) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Inner, nil +} + +func (n NullUInt32) String() string { + if n.Valid { + return fmt.Sprintf("%v", n.Inner) + } + return "NULL" +} + +type NullUInt16 struct { + Inner uint16 + Valid bool // Valid is true if Inner is not NULL +} + +// Scan implements the Scanner interface. +func (n *NullUInt16) Scan(value interface{}) error { + if value == nil { + n.Inner, n.Valid = 0, false + return nil + } + n.Valid = true + v, ok := value.(uint16) + if !ok { + return &errors.TaosError{Code: 0xffff, ErrStr: "taosSql parse uint16 error"} + } + n.Inner = v + return nil +} + +// Value implements the driver Valuer interface. +func (n NullUInt16) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Inner, nil +} + +func (n NullUInt16) String() string { + if n.Valid { + return fmt.Sprintf("%v", n.Inner) + } + return "NULL" +} + +type NullUInt8 struct { + Inner uint8 + Valid bool // Valid is true if Inner is not NULL +} + +// Scan implements the Scanner interface. +func (n *NullUInt8) Scan(value interface{}) error { + if value == nil { + n.Inner, n.Valid = 0, false + return nil + } + n.Valid = true + v, ok := value.(uint8) + if !ok { + return &errors.TaosError{Code: 0xffff, ErrStr: "taosSql parse uint8 error"} + } + n.Inner = v + return nil +} + +// Value implements the driver Valuer interface. +func (n NullUInt8) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Inner, nil +} + +func (n NullUInt8) String() string { + if n.Valid { + return fmt.Sprintf("%v", n.Inner) + } + return "NULL" +} + +type NullFloat32 struct { + Inner float32 + Valid bool // Valid is true if Inner is not NULL +} + +// Scan implements the Scanner interface. +func (n *NullFloat32) Scan(value interface{}) error { + if value == nil { + n.Inner, n.Valid = 0, false + return nil + } + n.Valid = true + v, ok := value.(float32) + if !ok { + return &errors.TaosError{Code: 0xffff, ErrStr: "taosSql parse float32 error"} + } + n.Inner = v + return nil +} + +func (n NullFloat32) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Inner, nil +} + +func (n NullFloat32) String() string { + if n.Valid { + return fmt.Sprintf("%v", n.Inner) + } + return "NULL" +} + +type NullFloat64 struct { + Inner float64 + Valid bool // Valid is true if Inner is not NULL +} + +// Scan implements the Scanner interface. +func (n *NullFloat64) Scan(value interface{}) error { + if value == nil { + n.Inner, n.Valid = 0, false + return nil + } + n.Valid = true + v, ok := value.(float64) + if !ok { + return &errors.TaosError{Code: 0xffff, ErrStr: "taosSql parse float64 error"} + } + n.Inner = v + return nil +} + +func (n NullFloat64) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Inner, nil +} + +func (n NullFloat64) String() string { + if n.Valid { + return fmt.Sprintf("%v", n.Inner) + } + return "NULL" +} + +type NullBool struct { + Inner bool + Valid bool // Valid is true if Inner is not NULL +} + +func (n *NullBool) Scan(value interface{}) error { + if value == nil { + n.Valid = false + return nil + } + n.Valid = true + v, ok := value.(bool) + if !ok { + return &errors.TaosError{Code: 0xffff, ErrStr: "taosSql parse bool error"} + } + n.Inner = v + return nil +} + +func (n NullBool) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Inner, nil +} + +type NullString struct { + Inner string + Valid bool // Valid is true if Inner is not NULL +} + +func (n *NullString) Scan(value interface{}) error { + if value == nil { + n.Valid = false + return nil + } + n.Valid = true + v, ok := value.(string) + if !ok { + return &errors.TaosError{Code: 0xffff, ErrStr: "taosSql parse string error"} + } + n.Inner = v + return nil +} + +func (n NullString) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Inner, nil +} + +type NullTime struct { + Time time.Time + Valid bool // Valid is true if Time is not NULL +} + +// Scan implements the Scanner interface. +// The value type must be time.Time or string / []byte (formatted time-string), +// otherwise Scan fails. +func (nt *NullTime) Scan(value interface{}) (err error) { + if value == nil { + nt.Time, nt.Valid = time.Time{}, false + return + } + + switch v := value.(type) { + case time.Time: + nt.Time, nt.Valid = v, true + return + case []byte: + nt.Time, err = time.Parse(time.RFC3339Nano, string(v)) + nt.Valid = err == nil + return + case string: + nt.Time, err = time.Parse(time.RFC3339Nano, v) + nt.Valid = err == nil + return + } + + nt.Valid = false + return fmt.Errorf("can't convert %T to time.Time", value) +} + +// Value implements the driver Valuer interface. +func (nt NullTime) Value() (driver.Value, error) { + if !nt.Valid { + return nil, nil + } + return nt.Time, nil +} + +type NullJson struct { + Inner RawMessage + Valid bool +} + +func (n *NullJson) Scan(value interface{}) error { + if value == nil { + n.Valid = false + return nil + } + n.Valid = true + v, ok := value.([]byte) + if !ok { + return &errors.TaosError{Code: 0xffff, ErrStr: "taosSql parse json error"} + } + n.Inner = v + return nil +} + +func (n NullJson) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Inner, nil +} + +type RawMessage []byte + +func (m RawMessage) MarshalJSON() ([]byte, error) { + if m == nil { + return []byte("null"), nil + } + return m, nil +} + +func (m *RawMessage) UnmarshalJSON(data []byte) error { + if m == nil { + return &errors.TaosError{Code: 0xffff, ErrStr: "json.RawMessage: UnmarshalJSON on nil pointer"} + } + *m = append((*m)[0:0], data...) + return nil +} diff --git a/driver/types/types_test.go b/driver/types/types_test.go new file mode 100644 index 00000000..487da516 --- /dev/null +++ b/driver/types/types_test.go @@ -0,0 +1,2122 @@ +package types + +import ( + "database/sql/driver" + "reflect" + "testing" + "time" +) + +// @author: xftan +// @date: 2022/1/27 16:20 +// @description: test null bool type Scan() +func TestNullBool_Scan(t *testing.T) { + type fields struct { + Inner bool + Valid bool + } + type args struct { + value interface{} + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "true", + fields: fields{ + Inner: true, + Valid: true, + }, + args: args{ + value: true, + }, + wantErr: false, + }, + { + name: "error", + fields: fields{ + Inner: true, + Valid: false, + }, + args: args{ + value: 1, + }, + wantErr: true, + }, + { + name: "nil", + fields: fields{ + Inner: false, + Valid: false, + }, + args: args{ + value: nil, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := &NullBool{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if err := n.Scan(tt.args.value); (err != nil) != tt.wantErr { + t.Errorf("Scan() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:20 +// @description: test null bool type Value() +func TestNullBool_Value(t *testing.T) { + type fields struct { + Inner bool + Valid bool + } + tests := []struct { + name string + fields fields + want driver.Value + wantErr bool + }{ + { + name: "ture", + fields: fields{ + Inner: true, + Valid: true, + }, + want: true, + wantErr: false, + }, + { + name: "false", + fields: fields{ + Inner: false, + Valid: true, + }, + want: false, + wantErr: false, + }, + { + name: "nil", + fields: fields{ + Inner: false, + Valid: false, + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullBool{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + got, err := n.Value() + if (err != nil) != tt.wantErr { + t.Errorf("Value() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Value() got = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:21 +// @description: test null float32 type Scan() +func TestNullFloat32_Scan(t *testing.T) { + type fields struct { + Inner float32 + Valid bool + } + type args struct { + value interface{} + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: 1, + Valid: true, + }, + args: args{ + value: float32(1), + }, + wantErr: false, + }, + { + name: "error", + fields: fields{ + Inner: 1, + Valid: true, + }, + args: args{ + value: 1, + }, + wantErr: true, + }, + { + name: "nil", + fields: fields{ + Inner: 0, + Valid: false, + }, + args: args{ + value: nil, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := &NullFloat32{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if err := n.Scan(tt.args.value); (err != nil) != tt.wantErr { + t.Errorf("Scan() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:21 +// @description: test null float32 type String() +func TestNullFloat32_String(t *testing.T) { + type fields struct { + Inner float32 + Valid bool + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "common", + fields: fields{ + Inner: 123, + Valid: true, + }, + want: "123", + }, + { + name: "null", + fields: fields{ + Inner: 0, + Valid: false, + }, + want: "NULL", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullFloat32{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if got := n.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:21 +// @description: test null float32 type Value() +func TestNullFloat32_Value(t *testing.T) { + type fields struct { + Inner float32 + Valid bool + } + tests := []struct { + name string + fields fields + want driver.Value + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: 123, + Valid: true, + }, + want: float32(123), + wantErr: false, + }, + { + name: "nil", + fields: fields{ + Inner: 0, + Valid: false, + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullFloat32{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + got, err := n.Value() + if (err != nil) != tt.wantErr { + t.Errorf("Value() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Value() got = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:21 +// @description: test null float64 type Scan() +func TestNullFloat64_Scan(t *testing.T) { + type fields struct { + Inner float64 + Valid bool + } + type args struct { + value interface{} + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: 1, + Valid: true, + }, + args: args{ + value: float64(1), + }, + wantErr: false, + }, + { + name: "error", + fields: fields{ + Inner: 1, + Valid: true, + }, + args: args{ + value: 1, + }, + wantErr: true, + }, + { + name: "nil", + fields: fields{ + Inner: 0, + Valid: false, + }, + args: args{ + value: nil, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := &NullFloat64{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if err := n.Scan(tt.args.value); (err != nil) != tt.wantErr { + t.Errorf("Scan() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:22 +// @description: test null float64 type String() +func TestNullFloat64_String(t *testing.T) { + type fields struct { + Inner float64 + Valid bool + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "common", + fields: fields{ + Inner: 123, + Valid: true, + }, + want: "123", + }, + { + name: "null", + fields: fields{ + Inner: 0, + Valid: false, + }, + want: "NULL", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullFloat64{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if got := n.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:22 +// @description: test null float64 type Value() +func TestNullFloat64_Value(t *testing.T) { + type fields struct { + Inner float64 + Valid bool + } + tests := []struct { + name string + fields fields + want driver.Value + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: 123, + Valid: true, + }, + want: float64(123), + wantErr: false, + }, + { + name: "nil", + fields: fields{ + Inner: 0, + Valid: false, + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullFloat64{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + got, err := n.Value() + if (err != nil) != tt.wantErr { + t.Errorf("Value() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Value() got = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:22 +// @description: test null int16 type Scan() +func TestNullInt16_Scan(t *testing.T) { + type fields struct { + Inner int16 + Valid bool + } + type args struct { + value interface{} + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: 1, + Valid: true, + }, + args: args{ + value: int16(1), + }, + wantErr: false, + }, + { + name: "error", + fields: fields{ + Inner: 1, + Valid: true, + }, + args: args{ + value: 1, + }, + wantErr: true, + }, + { + name: "nil", + fields: fields{ + Inner: 0, + Valid: false, + }, + args: args{ + value: nil, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := &NullInt16{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if err := n.Scan(tt.args.value); (err != nil) != tt.wantErr { + t.Errorf("Scan() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:23 +// @description: test null int16 type String() +func TestNullInt16_String(t *testing.T) { + type fields struct { + Inner int16 + Valid bool + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "common", + fields: fields{ + Inner: 123, + Valid: true, + }, + want: "123", + }, + { + name: "null", + fields: fields{ + Inner: 0, + Valid: false, + }, + want: "NULL", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullInt16{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if got := n.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:23 +// @description: test null int16 type Value() +func TestNullInt16_Value(t *testing.T) { + type fields struct { + Inner int16 + Valid bool + } + tests := []struct { + name string + fields fields + want driver.Value + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: 123, + Valid: true, + }, + want: int16(123), + wantErr: false, + }, + { + name: "nil", + fields: fields{ + Inner: 0, + Valid: false, + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullInt16{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + got, err := n.Value() + if (err != nil) != tt.wantErr { + t.Errorf("Value() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Value() got = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:23 +// @description: test null int32 type Scan() +func TestNullInt32_Scan(t *testing.T) { + type fields struct { + Inner int32 + Valid bool + } + type args struct { + value interface{} + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: 1, + Valid: true, + }, + args: args{ + value: int32(1), + }, + wantErr: false, + }, + { + name: "error", + fields: fields{ + Inner: 1, + Valid: true, + }, + args: args{ + value: 1, + }, + wantErr: true, + }, + { + name: "nil", + fields: fields{ + Inner: 0, + Valid: false, + }, + args: args{ + value: nil, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := &NullInt32{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if err := n.Scan(tt.args.value); (err != nil) != tt.wantErr { + t.Errorf("Scan() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:25 +// @description: test null int32 type String() +func TestNullInt32_String(t *testing.T) { + type fields struct { + Inner int32 + Valid bool + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "common", + fields: fields{ + Inner: 123, + Valid: true, + }, + want: "123", + }, + { + name: "null", + fields: fields{ + Inner: 0, + Valid: false, + }, + want: "NULL", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullInt32{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if got := n.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 17:04 +// @description: test null int32 type Value() +func TestNullInt32_Value(t *testing.T) { + type fields struct { + Inner int32 + Valid bool + } + tests := []struct { + name string + fields fields + want driver.Value + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: 123, + Valid: true, + }, + want: int32(123), + wantErr: false, + }, + { + name: "nil", + fields: fields{ + Inner: 0, + Valid: false, + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullInt32{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + got, err := n.Value() + if (err != nil) != tt.wantErr { + t.Errorf("Value() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Value() got = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:23 +// @description: test null int64 type Scan() +func TestNullInt64_Scan(t *testing.T) { + type fields struct { + Inner int64 + Valid bool + } + type args struct { + value interface{} + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: 1, + Valid: true, + }, + args: args{ + value: int64(1), + }, + wantErr: false, + }, + { + name: "error", + fields: fields{ + Inner: 1, + Valid: true, + }, + args: args{ + value: 1, + }, + wantErr: true, + }, + { + name: "nil", + fields: fields{ + Inner: 0, + Valid: false, + }, + args: args{ + value: nil, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := &NullInt64{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if err := n.Scan(tt.args.value); (err != nil) != tt.wantErr { + t.Errorf("Scan() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:47 +// @description: test null int64 type String() +func TestNullInt64_String(t *testing.T) { + type fields struct { + Inner int64 + Valid bool + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "common", + fields: fields{ + Inner: 123, + Valid: true, + }, + want: "123", + }, + { + name: "null", + fields: fields{ + Inner: 0, + Valid: false, + }, + want: "NULL", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullInt64{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if got := n.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 17:19 +// @description: test null int64 type Value() +func TestNullInt64_Value(t *testing.T) { + type fields struct { + Inner int64 + Valid bool + } + tests := []struct { + name string + fields fields + want driver.Value + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: 123, + Valid: true, + }, + want: int64(123), + wantErr: false, + }, + { + name: "nil", + fields: fields{ + Inner: 0, + Valid: false, + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullInt64{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + got, err := n.Value() + if (err != nil) != tt.wantErr { + t.Errorf("Value() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Value() got = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:23 +// @description: test null int8 type Scan() +func TestNullInt8_Scan(t *testing.T) { + type fields struct { + Inner int8 + Valid bool + } + type args struct { + value interface{} + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: 1, + Valid: true, + }, + args: args{ + value: int8(1), + }, + wantErr: false, + }, + { + name: "error", + fields: fields{ + Inner: 1, + Valid: true, + }, + args: args{ + value: 1, + }, + wantErr: true, + }, + { + name: "nil", + fields: fields{ + Inner: 0, + Valid: false, + }, + args: args{ + value: nil, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := &NullInt8{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if err := n.Scan(tt.args.value); (err != nil) != tt.wantErr { + t.Errorf("Scan() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:48 +// @description: test null int8 type String() +func TestNullInt8_String(t *testing.T) { + type fields struct { + Inner int8 + Valid bool + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "common", + fields: fields{ + Inner: 123, + Valid: true, + }, + want: "123", + }, + { + name: "null", + fields: fields{ + Inner: 0, + Valid: false, + }, + want: "NULL", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullInt8{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if got := n.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 17:19 +// @description: test null int8 type Value() +func TestNullInt8_Value(t *testing.T) { + type fields struct { + Inner int8 + Valid bool + } + tests := []struct { + name string + fields fields + want driver.Value + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: 123, + Valid: true, + }, + want: int8(123), + wantErr: false, + }, + { + name: "nil", + fields: fields{ + Inner: 0, + Valid: false, + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullInt8{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + got, err := n.Value() + if (err != nil) != tt.wantErr { + t.Errorf("Value() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Value() got = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:24 +// @description: test null json type Scan() +func TestNullJson_Scan(t *testing.T) { + type fields struct { + Inner RawMessage + Valid bool + } + type args struct { + value interface{} + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "common", + fields: fields{}, + args: args{ + value: []byte{'1', '2', '3'}, + }, + wantErr: false, + }, + { + name: "error", + fields: fields{}, + args: args{ + value: 123, + }, + wantErr: true, + }, + { + name: "nil", + fields: fields{}, + args: args{ + value: nil, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := &NullJson{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if err := n.Scan(tt.args.value); (err != nil) != tt.wantErr { + t.Errorf("Scan() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 17:19 +// @description: test null json type Value() +func TestNullJson_Value(t *testing.T) { + type fields struct { + Inner RawMessage + Valid bool + } + tests := []struct { + name string + fields fields + want driver.Value + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: RawMessage("123"), + Valid: true, + }, + want: RawMessage("123"), + wantErr: false, + }, + { + name: "nil", + fields: fields{ + Inner: nil, + Valid: false, + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullJson{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + got, err := n.Value() + if (err != nil) != tt.wantErr { + t.Errorf("Value() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Value() got = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:24 +// @description: test null string type Scan() +func TestNullString_Scan(t *testing.T) { + type fields struct { + Inner string + Valid bool + } + type args struct { + value interface{} + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "common", + fields: fields{}, + args: args{ + value: "123", + }, + wantErr: false, + }, + { + name: "error", + fields: fields{}, + args: args{ + value: 123, + }, + wantErr: true, + }, + { + name: "nil", + fields: fields{}, + args: args{ + value: nil, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := &NullString{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if err := n.Scan(tt.args.value); (err != nil) != tt.wantErr { + t.Errorf("Scan() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 17:19 +// @description: test null string type Value() +func TestNullString_Value(t *testing.T) { + type fields struct { + Inner string + Valid bool + } + tests := []struct { + name string + fields fields + want driver.Value + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: "123", + Valid: true, + }, + want: "123", + wantErr: false, + }, + { + name: "nil", + fields: fields{ + Inner: "", + Valid: false, + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullString{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + got, err := n.Value() + if (err != nil) != tt.wantErr { + t.Errorf("Value() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Value() got = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:24 +// @description: test null time type Scan() +func TestNullTime_Scan(t *testing.T) { + type fields struct { + Time time.Time + Valid bool + } + type args struct { + value interface{} + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "time", + fields: fields{}, + args: args{ + value: time.Now(), + }, + wantErr: false, + }, + { + name: "bytes", + fields: fields{}, + args: args{ + value: []byte("2022-01-27T15:34:52.9368423+08:00"), + }, + wantErr: false, + }, + { + name: "string", + fields: fields{}, + args: args{ + value: "2022-01-27T15:34:52.9368423+08:00", + }, + wantErr: false, + }, + { + name: "error", + fields: fields{}, + args: args{ + value: 123, + }, + wantErr: true, + }, + { + name: "nil", + fields: fields{}, + args: args{ + value: nil, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + nt := &NullTime{ + Time: tt.fields.Time, + Valid: tt.fields.Valid, + } + if err := nt.Scan(tt.args.value); (err != nil) != tt.wantErr { + t.Errorf("Scan() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 17:20 +// @description: test null time type Value() +func TestNullTime_Value(t *testing.T) { + now := time.Now() + type fields struct { + Time time.Time + Valid bool + } + tests := []struct { + name string + fields fields + want driver.Value + wantErr bool + }{ + { + name: "common", + fields: fields{ + Time: now, + Valid: true, + }, + want: now, + wantErr: false, + }, + { + name: "nil", + fields: fields{ + Time: now, + Valid: false, + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + nt := NullTime{ + Time: tt.fields.Time, + Valid: tt.fields.Valid, + } + got, err := nt.Value() + if (err != nil) != tt.wantErr { + t.Errorf("Value() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Value() got = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:24 +// @description: test null uint16 type Scan() +func TestNullUInt16_Scan(t *testing.T) { + type fields struct { + Inner uint16 + Valid bool + } + type args struct { + value interface{} + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: 1, + Valid: true, + }, + args: args{ + value: uint16(1), + }, + wantErr: false, + }, + { + name: "error", + fields: fields{ + Inner: 1, + Valid: true, + }, + args: args{ + value: 1, + }, + wantErr: true, + }, + { + name: "nil", + fields: fields{ + Inner: 0, + Valid: false, + }, + args: args{ + value: nil, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := &NullUInt16{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if err := n.Scan(tt.args.value); (err != nil) != tt.wantErr { + t.Errorf("Scan() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:48 +// @description: test null uint16 type String() +func TestNullUInt16_String(t *testing.T) { + type fields struct { + Inner uint16 + Valid bool + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "common", + fields: fields{ + Inner: 123, + Valid: true, + }, + want: "123", + }, + { + name: "null", + fields: fields{ + Inner: 0, + Valid: false, + }, + want: "NULL", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullUInt16{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if got := n.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 17:20 +// @description: test null uint16 type Value() +func TestNullUInt16_Value(t *testing.T) { + type fields struct { + Inner uint16 + Valid bool + } + tests := []struct { + name string + fields fields + want driver.Value + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: 123, + Valid: true, + }, + want: uint16(123), + wantErr: false, + }, + { + name: "nil", + fields: fields{ + Inner: 0, + Valid: false, + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullUInt16{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + got, err := n.Value() + if (err != nil) != tt.wantErr { + t.Errorf("Value() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Value() got = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:24 +// @description: test null uint32 type Scan() +func TestNullUInt32_Scan(t *testing.T) { + type fields struct { + Inner uint32 + Valid bool + } + type args struct { + value interface{} + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: 1, + Valid: true, + }, + args: args{ + value: uint32(1), + }, + wantErr: false, + }, + { + name: "error", + fields: fields{ + Inner: 1, + Valid: true, + }, + args: args{ + value: 1, + }, + wantErr: true, + }, + { + name: "nil", + fields: fields{ + Inner: 0, + Valid: false, + }, + args: args{ + value: nil, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := &NullUInt32{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if err := n.Scan(tt.args.value); (err != nil) != tt.wantErr { + t.Errorf("Scan() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:48 +// @description: test null uint32 type String() +func TestNullUInt32_String(t *testing.T) { + type fields struct { + Inner uint32 + Valid bool + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "common", + fields: fields{ + Inner: 123, + Valid: true, + }, + want: "123", + }, + { + name: "null", + fields: fields{ + Inner: 0, + Valid: false, + }, + want: "NULL", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullUInt32{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if got := n.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 17:20 +// @description: test null uint32 type Value() +func TestNullUInt32_Value(t *testing.T) { + type fields struct { + Inner uint32 + Valid bool + } + tests := []struct { + name string + fields fields + want driver.Value + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: 123, + Valid: true, + }, + want: uint32(123), + wantErr: false, + }, + { + name: "nil", + fields: fields{ + Inner: 0, + Valid: false, + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullUInt32{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + got, err := n.Value() + if (err != nil) != tt.wantErr { + t.Errorf("Value() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Value() got = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:24 +// @description: test null uint64 type Scan() +func TestNullUInt64_Scan(t *testing.T) { + type fields struct { + Inner uint64 + Valid bool + } + type args struct { + value interface{} + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: 1, + Valid: true, + }, + args: args{ + value: uint64(1), + }, + wantErr: false, + }, + { + name: "error", + fields: fields{ + Inner: 1, + Valid: true, + }, + args: args{ + value: 1, + }, + wantErr: true, + }, + { + name: "nil", + fields: fields{ + Inner: 0, + Valid: false, + }, + args: args{ + value: nil, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := &NullUInt64{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if err := n.Scan(tt.args.value); (err != nil) != tt.wantErr { + t.Errorf("Scan() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:48 +// @description: test null uint64 type String() +func TestNullUInt64_String(t *testing.T) { + type fields struct { + Inner uint64 + Valid bool + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "common", + fields: fields{ + Inner: 123, + Valid: true, + }, + want: "123", + }, + { + name: "null", + fields: fields{ + Inner: 0, + Valid: false, + }, + want: "NULL", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullUInt64{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if got := n.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 17:20 +// @description: test null uint64 type Value() +func TestNullUInt64_Value(t *testing.T) { + type fields struct { + Inner uint64 + Valid bool + } + tests := []struct { + name string + fields fields + want driver.Value + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: 123, + Valid: true, + }, + want: uint64(123), + wantErr: false, + }, + { + name: "nil", + fields: fields{ + Inner: 0, + Valid: false, + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullUInt64{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + got, err := n.Value() + if (err != nil) != tt.wantErr { + t.Errorf("Value() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Value() got = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:25 +// @description: test null uint8 type Scan() +func TestNullUInt8_Scan(t *testing.T) { + type fields struct { + Inner uint8 + Valid bool + } + type args struct { + value interface{} + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: 1, + Valid: true, + }, + args: args{ + value: uint8(1), + }, + wantErr: false, + }, + { + name: "error", + fields: fields{ + Inner: 1, + Valid: true, + }, + args: args{ + value: 1, + }, + wantErr: true, + }, + { + name: "nil", + fields: fields{ + Inner: 0, + Valid: false, + }, + args: args{ + value: nil, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := &NullUInt8{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if err := n.Scan(tt.args.value); (err != nil) != tt.wantErr { + t.Errorf("Scan() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 16:48 +// @description: test null uint8 type String() +func TestNullUInt8_String(t *testing.T) { + type fields struct { + Inner uint8 + Valid bool + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "common", + fields: fields{ + Inner: 123, + Valid: true, + }, + want: "123", + }, + { + name: "null", + fields: fields{ + Inner: 0, + Valid: false, + }, + want: "NULL", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullUInt8{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + if got := n.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 17:20 +// @description: test null uint8 type Value() +func TestNullUInt8_Value(t *testing.T) { + type fields struct { + Inner uint8 + Valid bool + } + tests := []struct { + name string + fields fields + want driver.Value + wantErr bool + }{ + { + name: "common", + fields: fields{ + Inner: 123, + Valid: true, + }, + want: uint8(123), + wantErr: false, + }, + { + name: "nil", + fields: fields{ + Inner: 0, + Valid: false, + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := NullUInt8{ + Inner: tt.fields.Inner, + Valid: tt.fields.Valid, + } + got, err := n.Value() + if (err != nil) != tt.wantErr { + t.Errorf("Value() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Value() got = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 17:20 +// @description: test raw message type MarshalJson() interface +func TestRawMessage_MarshalJSON(t *testing.T) { + tests := []struct { + name string + m RawMessage + want []byte + wantErr bool + }{ + { + name: "common", + m: RawMessage(`{"a":"b"}`), + want: []byte(`{"a":"b"}`), + wantErr: false, + }, + { + name: "nil", + m: nil, + want: []byte("null"), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.m.MarshalJSON() + if (err != nil) != tt.wantErr { + t.Errorf("MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("MarshalJSON() got = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 17:21 +// @description: test raw message type UnmarshalJson() interface +func TestRawMessage_UnmarshalJSON(t *testing.T) { + common := RawMessage(`{"a":"b"}`) + type args struct { + data []byte + } + tests := []struct { + name string + m *RawMessage + args args + wantErr bool + }{ + { + name: "common", + m: &common, + args: args{ + data: []byte(`{"a":"b"}`), + }, + wantErr: false, + }, + { + name: "error", + m: nil, + args: args{}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.m.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { + t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/driver/wrapper/asynccb.go b/driver/wrapper/asynccb.go new file mode 100644 index 00000000..ad9d58a1 --- /dev/null +++ b/driver/wrapper/asynccb.go @@ -0,0 +1,38 @@ +package wrapper + +/* +#include +#include +#include +#include + +*/ +import "C" +import ( + "unsafe" + + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" +) + +type Caller interface { + QueryCall(res unsafe.Pointer, code int) + FetchCall(res unsafe.Pointer, numOfRows int) +} + +//export QueryCallback +func QueryCallback(p unsafe.Pointer, res *C.TAOS_RES, code C.int) { + caller := (*(*cgo.Handle)(p)).Value().(Caller) + caller.QueryCall(unsafe.Pointer(res), int(code)) +} + +//export FetchRowsCallback +func FetchRowsCallback(p unsafe.Pointer, res *C.TAOS_RES, numOfRows C.int) { + caller := (*(*cgo.Handle)(p)).Value().(Caller) + caller.FetchCall(unsafe.Pointer(res), int(numOfRows)) +} + +//export FetchRawBlockCallback +func FetchRawBlockCallback(p unsafe.Pointer, res *C.TAOS_RES, numOfRows C.int) { + caller := (*(*cgo.Handle)(p)).Value().(Caller) + caller.FetchCall(unsafe.Pointer(res), int(numOfRows)) +} diff --git a/driver/wrapper/block.go b/driver/wrapper/block.go new file mode 100644 index 00000000..eacf894f --- /dev/null +++ b/driver/wrapper/block.go @@ -0,0 +1,49 @@ +package wrapper + +/* +#include +#include +#include +#include +*/ +import "C" +import ( + "unsafe" +) + +// TaosFetchRawBlock int taos_fetch_raw_block(TAOS_RES *res, int* numOfRows, void** pData); +func TaosFetchRawBlock(result unsafe.Pointer) (int, int, unsafe.Pointer) { + var cSize int + size := unsafe.Pointer(&cSize) + var block unsafe.Pointer + errCode := int(C.taos_fetch_raw_block(result, (*C.int)(size), &block)) + return cSize, errCode, block +} + +// TaosWriteRawBlock DLL_EXPORT int taos_write_raw_block(TAOS *taos, int numOfRows, char *pData, const char* tbname); +func TaosWriteRawBlock(conn unsafe.Pointer, numOfRows int, pData unsafe.Pointer, tableName string) int { + cStr := C.CString(tableName) + defer C.free(unsafe.Pointer(cStr)) + return int(C.taos_write_raw_block(conn, (C.int)(numOfRows), (*C.char)(pData), cStr)) +} + +// TaosWriteRawBlockWithFields DLL_EXPORT int taos_write_raw_block_with_fields(TAOS* taos, int rows, char* pData, const char* tbname, TAOS_FIELD *fields, int numFields); +func TaosWriteRawBlockWithFields(conn unsafe.Pointer, numOfRows int, pData unsafe.Pointer, tableName string, fields unsafe.Pointer, numFields int) int { + cStr := C.CString(tableName) + defer C.free(unsafe.Pointer(cStr)) + return int(C.taos_write_raw_block_with_fields(conn, (C.int)(numOfRows), (*C.char)(pData), cStr, (*C.struct_taosField)(fields), (C.int)(numFields))) +} + +// DLL_EXPORT int taos_write_raw_block_with_reqid(TAOS *taos, int numOfRows, char *pData, const char *tbname, int64_t reqid); +func TaosWriteRawBlockWithReqID(conn unsafe.Pointer, numOfRows int, pData unsafe.Pointer, tableName string, reqID int64) int { + cStr := C.CString(tableName) + defer C.free(unsafe.Pointer(cStr)) + return int(C.taos_write_raw_block_with_reqid(conn, (C.int)(numOfRows), (*C.char)(pData), cStr, (C.int64_t)(reqID))) +} + +// DLL_EXPORT int taos_write_raw_block_with_fields_with_reqid(TAOS *taos, int rows, char *pData, const char *tbname,TAOS_FIELD *fields, int numFields, int64_t reqid); +func TaosWriteRawBlockWithFieldsWithReqID(conn unsafe.Pointer, numOfRows int, pData unsafe.Pointer, tableName string, fields unsafe.Pointer, numFields int, reqID int64) int { + cStr := C.CString(tableName) + defer C.free(unsafe.Pointer(cStr)) + return int(C.taos_write_raw_block_with_fields_with_reqid(conn, (C.int)(numOfRows), (*C.char)(pData), cStr, (*C.struct_taosField)(fields), (C.int)(numFields), (C.int64_t)(reqID))) +} diff --git a/driver/wrapper/block_test.go b/driver/wrapper/block_test.go new file mode 100644 index 00000000..85523d78 --- /dev/null +++ b/driver/wrapper/block_test.go @@ -0,0 +1,924 @@ +package wrapper + +import ( + "database/sql/driver" + "fmt" + "math" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + "github.com/taosdata/taosadapter/v3/driver/errors" +) + +// @author: xftan +// @date: 2023/10/13 11:27 +// @description: test read block +func TestReadBlock(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + + defer TaosClose(conn) + res := TaosQuery(conn, "drop database if exists test_block_raw") + code := TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + defer func() { + res = TaosQuery(conn, "drop database if exists test_block_raw") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + }() + res = TaosQuery(conn, "create database test_block_raw") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + res = TaosQuery(conn, "create table if not exists test_block_raw.all_type (ts timestamp,"+ + "c1 bool,"+ + "c2 tinyint,"+ + "c3 smallint,"+ + "c4 int,"+ + "c5 bigint,"+ + "c6 tinyint unsigned,"+ + "c7 smallint unsigned,"+ + "c8 int unsigned,"+ + "c9 bigint unsigned,"+ + "c10 float,"+ + "c11 double,"+ + "c12 binary(20),"+ + "c13 nchar(20)"+ + ") tags (info json)") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + now := time.Now() + after1s := now.Add(time.Second) + after2s := now.Add(2 * time.Second) + sql := fmt.Sprintf("insert into test_block_raw.t0 using test_block_raw.all_type tags('{\"a\":1}') values"+ + "('%s',1,1,1,1,1,1,1,1,1,1,1,'test_binary','test_nchar')"+ + "('%s',null,null,null,null,null,null,null,null,null,null,null,null,null)"+ + "('%s',true,%d,%d,%d,%d,%d,%d,%d,%v,%f,%f,'b','n')", + now.Format(time.RFC3339Nano), + after1s.Format(time.RFC3339Nano), + after2s.Format(time.RFC3339Nano), + math.MaxInt8, + math.MaxInt16, + math.MaxInt32, + math.MaxInt64, + math.MaxUint8, + math.MaxUint16, + math.MaxUint32, + uint64(math.MaxUint64), + math.MaxFloat32, + math.MaxFloat64, + ) + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + sql = "select * from test_block_raw.all_type" + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + fileCount := TaosNumFields(res) + rh, err := ReadColumn(res, fileCount) + if err != nil { + t.Error(err) + return + } + precision := TaosResultPrecision(res) + var data [][]driver.Value + for { + blockSize, errCode, block := TaosFetchRawBlock(res) + if errCode != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + if blockSize == 0 { + break + } + d := parser.ReadBlock(block, blockSize, rh.ColTypes, precision) + data = append(data, d...) + } + TaosFreeResult(res) + assert.Equal(t, 3, len(data)) + row1 := data[0] + assert.Equal(t, now.UnixNano()/1e6, row1[0].(time.Time).UnixNano()/1e6) + assert.Equal(t, true, row1[1].(bool)) + assert.Equal(t, int8(1), row1[2].(int8)) + assert.Equal(t, int16(1), row1[3].(int16)) + assert.Equal(t, int32(1), row1[4].(int32)) + assert.Equal(t, int64(1), row1[5].(int64)) + assert.Equal(t, uint8(1), row1[6].(uint8)) + assert.Equal(t, uint16(1), row1[7].(uint16)) + assert.Equal(t, uint32(1), row1[8].(uint32)) + assert.Equal(t, uint64(1), row1[9].(uint64)) + assert.Equal(t, float32(1), row1[10].(float32)) + assert.Equal(t, float64(1), row1[11].(float64)) + assert.Equal(t, "test_binary", row1[12].(string)) + assert.Equal(t, "test_nchar", row1[13].(string)) + assert.Equal(t, []byte(`{"a":1}`), row1[14].([]byte)) + row2 := data[1] + assert.Equal(t, after1s.UnixNano()/1e6, row2[0].(time.Time).UnixNano()/1e6) + for i := 1; i < 14; i++ { + assert.Nil(t, row2[i]) + } + assert.Equal(t, []byte(`{"a":1}`), row2[14].([]byte)) + row3 := data[2] + assert.Equal(t, after2s.UnixNano()/1e6, row3[0].(time.Time).UnixNano()/1e6) + assert.Equal(t, true, row3[1].(bool)) + assert.Equal(t, int8(math.MaxInt8), row3[2].(int8)) + assert.Equal(t, int16(math.MaxInt16), row3[3].(int16)) + assert.Equal(t, int32(math.MaxInt32), row3[4].(int32)) + assert.Equal(t, int64(math.MaxInt64), row3[5].(int64)) + assert.Equal(t, uint8(math.MaxUint8), row3[6].(uint8)) + assert.Equal(t, uint16(math.MaxUint16), row3[7].(uint16)) + assert.Equal(t, uint32(math.MaxUint32), row3[8].(uint32)) + assert.Equal(t, uint64(math.MaxUint64), row3[9].(uint64)) + assert.Equal(t, float32(math.MaxFloat32), row3[10].(float32)) + assert.Equal(t, float64(math.MaxFloat64), row3[11].(float64)) + assert.Equal(t, "b", row3[12].(string)) + assert.Equal(t, "n", row3[13].(string)) + assert.Equal(t, []byte(`{"a":1}`), row3[14].([]byte)) +} + +// @author: xftan +// @date: 2023/10/13 11:27 +// @description: test write raw block +func TestTaosWriteRawBlock(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + + defer TaosClose(conn) + res := TaosQuery(conn, "drop database if exists test_write_block_raw") + code := TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + defer func() { + res = TaosQuery(conn, "drop database if exists test_write_block_raw") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + }() + res = TaosQuery(conn, "create database test_write_block_raw") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + res = TaosQuery(conn, "create table if not exists test_write_block_raw.all_type (ts timestamp,"+ + "c1 bool,"+ + "c2 tinyint,"+ + "c3 smallint,"+ + "c4 int,"+ + "c5 bigint,"+ + "c6 tinyint unsigned,"+ + "c7 smallint unsigned,"+ + "c8 int unsigned,"+ + "c9 bigint unsigned,"+ + "c10 float,"+ + "c11 double,"+ + "c12 binary(20),"+ + "c13 nchar(20)"+ + ") tags (info json)") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + now := time.Now() + after1s := now.Add(time.Second) + sql := fmt.Sprintf("insert into test_write_block_raw.t0 using test_write_block_raw.all_type tags('{\"a\":1}') values('%s',1,1,1,1,1,1,1,1,1,1,1,'test_binary','test_nchar')('%s',null,null,null,null,null,null,null,null,null,null,null,null,null)", now.Format(time.RFC3339Nano), after1s.Format(time.RFC3339Nano)) + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + sql = "create table test_write_block_raw.t1 using test_write_block_raw.all_type tags('{\"a\":2}')" + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + sql = "use test_write_block_raw" + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + sql = "select * from test_write_block_raw.t0" + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + for { + blockSize, errCode, block := TaosFetchRawBlock(res) + if errCode != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(errCode, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + if blockSize == 0 { + break + } + + errCode = TaosWriteRawBlock(conn, blockSize, block, "t1") + if errCode != int(errors.SUCCESS) { + errStr := TaosErrorStr(nil) + err := errors.NewError(errCode, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + } + TaosFreeResult(res) + + sql = "select * from test_write_block_raw.t1" + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + fileCount := TaosNumFields(res) + rh, err := ReadColumn(res, fileCount) + if err != nil { + t.Error(err) + return + } + precision := TaosResultPrecision(res) + var data [][]driver.Value + for { + blockSize, errCode, block := TaosFetchRawBlock(res) + if errCode != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + if blockSize == 0 { + break + } + d := parser.ReadBlock(block, blockSize, rh.ColTypes, precision) + data = append(data, d...) + } + TaosFreeResult(res) + + assert.Equal(t, 2, len(data)) + row1 := data[0] + assert.Equal(t, now.UnixNano()/1e6, row1[0].(time.Time).UnixNano()/1e6) + assert.Equal(t, true, row1[1].(bool)) + assert.Equal(t, int8(1), row1[2].(int8)) + assert.Equal(t, int16(1), row1[3].(int16)) + assert.Equal(t, int32(1), row1[4].(int32)) + assert.Equal(t, int64(1), row1[5].(int64)) + assert.Equal(t, uint8(1), row1[6].(uint8)) + assert.Equal(t, uint16(1), row1[7].(uint16)) + assert.Equal(t, uint32(1), row1[8].(uint32)) + assert.Equal(t, uint64(1), row1[9].(uint64)) + assert.Equal(t, float32(1), row1[10].(float32)) + assert.Equal(t, float64(1), row1[11].(float64)) + assert.Equal(t, "test_binary", row1[12].(string)) + assert.Equal(t, "test_nchar", row1[13].(string)) + row2 := data[1] + assert.Equal(t, after1s.UnixNano()/1e6, row2[0].(time.Time).UnixNano()/1e6) + for i := 1; i < 14; i++ { + assert.Nil(t, row2[i]) + } +} + +// @author: xftan +// @date: 2023/10/13 11:28 +// @description: test write raw block with fields +func TestTaosWriteRawBlockWithFields(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + + defer TaosClose(conn) + res := TaosQuery(conn, "drop database if exists test_write_block_raw_fields") + code := TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + defer func() { + res = TaosQuery(conn, "drop database if exists test_write_block_raw_fields") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + }() + res = TaosQuery(conn, "create database test_write_block_raw_fields") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + res = TaosQuery(conn, "create table if not exists test_write_block_raw_fields.all_type (ts timestamp,"+ + "c1 bool,"+ + "c2 tinyint,"+ + "c3 smallint,"+ + "c4 int,"+ + "c5 bigint,"+ + "c6 tinyint unsigned,"+ + "c7 smallint unsigned,"+ + "c8 int unsigned,"+ + "c9 bigint unsigned,"+ + "c10 float,"+ + "c11 double,"+ + "c12 binary(20),"+ + "c13 nchar(20)"+ + ") tags (info json)") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + now := time.Now() + after1s := now.Add(time.Second) + sql := fmt.Sprintf("insert into test_write_block_raw_fields.t0 using test_write_block_raw_fields.all_type tags('{\"a\":1}') values('%s',1,1,1,1,1,1,1,1,1,1,1,'test_binary','test_nchar')('%s',null,null,null,null,null,null,null,null,null,null,null,null,null)", now.Format(time.RFC3339Nano), after1s.Format(time.RFC3339Nano)) + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + sql = "create table test_write_block_raw_fields.t1 using test_write_block_raw_fields.all_type tags('{\"a\":2}')" + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + sql = "use test_write_block_raw_fields" + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + sql = "select ts,c1 from test_write_block_raw_fields.t0" + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + for { + blockSize, errCode, block := TaosFetchRawBlock(res) + if errCode != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(errCode, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + if blockSize == 0 { + break + } + fieldsCount := TaosNumFields(res) + fields := TaosFetchFields(res) + + errCode = TaosWriteRawBlockWithFields(conn, blockSize, block, "t1", fields, fieldsCount) + if errCode != int(errors.SUCCESS) { + errStr := TaosErrorStr(nil) + err := errors.NewError(errCode, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + } + TaosFreeResult(res) + + sql = "select * from test_write_block_raw_fields.t1" + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + fileCount := TaosNumFields(res) + rh, err := ReadColumn(res, fileCount) + if err != nil { + t.Error(err) + return + } + precision := TaosResultPrecision(res) + var data [][]driver.Value + for { + blockSize, errCode, block := TaosFetchRawBlock(res) + if errCode != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + if blockSize == 0 { + break + } + d := parser.ReadBlock(block, blockSize, rh.ColTypes, precision) + data = append(data, d...) + } + TaosFreeResult(res) + + assert.Equal(t, 2, len(data)) + row1 := data[0] + assert.Equal(t, now.UnixNano()/1e6, row1[0].(time.Time).UnixNano()/1e6) + assert.Equal(t, true, row1[1].(bool)) + for i := 2; i < 14; i++ { + assert.Nil(t, row1[i]) + } + row2 := data[1] + assert.Equal(t, after1s.UnixNano()/1e6, row2[0].(time.Time).UnixNano()/1e6) + for i := 1; i < 14; i++ { + assert.Nil(t, row2[i]) + } +} + +// @author: xftan +// @date: 2023/11/17 9:39 +// @description: test write raw block with reqid +func TestTaosWriteRawBlockWithReqID(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + + defer TaosClose(conn) + res := TaosQuery(conn, "drop database if exists test_write_block_raw_with_reqid") + code := TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + defer func() { + res = TaosQuery(conn, "drop database if exists test_write_block_raw_with_reqid") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + }() + res = TaosQuery(conn, "create database test_write_block_raw_with_reqid") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + res = TaosQuery(conn, "create table if not exists test_write_block_raw_with_reqid.all_type (ts timestamp,"+ + "c1 bool,"+ + "c2 tinyint,"+ + "c3 smallint,"+ + "c4 int,"+ + "c5 bigint,"+ + "c6 tinyint unsigned,"+ + "c7 smallint unsigned,"+ + "c8 int unsigned,"+ + "c9 bigint unsigned,"+ + "c10 float,"+ + "c11 double,"+ + "c12 binary(20),"+ + "c13 nchar(20)"+ + ") tags (info json)") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + now := time.Now() + after1s := now.Add(time.Second) + sql := fmt.Sprintf("insert into test_write_block_raw_with_reqid.t0 using test_write_block_raw_with_reqid.all_type tags('{\"a\":1}') values('%s',1,1,1,1,1,1,1,1,1,1,1,'test_binary','test_nchar')('%s',null,null,null,null,null,null,null,null,null,null,null,null,null)", now.Format(time.RFC3339Nano), after1s.Format(time.RFC3339Nano)) + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + sql = "create table test_write_block_raw_with_reqid.t1 using test_write_block_raw_with_reqid.all_type tags('{\"a\":2}')" + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + sql = "use test_write_block_raw_with_reqid" + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + sql = "select * from test_write_block_raw_with_reqid.t0" + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + for { + blockSize, errCode, block := TaosFetchRawBlock(res) + if errCode != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(errCode, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + if blockSize == 0 { + break + } + + errCode = TaosWriteRawBlockWithReqID(conn, blockSize, block, "t1", 1) + if errCode != int(errors.SUCCESS) { + errStr := TaosErrorStr(nil) + err := errors.NewError(errCode, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + } + TaosFreeResult(res) + + sql = "select * from test_write_block_raw_with_reqid.t1" + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + fileCount := TaosNumFields(res) + rh, err := ReadColumn(res, fileCount) + if err != nil { + t.Error(err) + return + } + precision := TaosResultPrecision(res) + var data [][]driver.Value + for { + blockSize, errCode, block := TaosFetchRawBlock(res) + if errCode != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + if blockSize == 0 { + break + } + d := parser.ReadBlock(block, blockSize, rh.ColTypes, precision) + data = append(data, d...) + } + TaosFreeResult(res) + + assert.Equal(t, 2, len(data)) + row1 := data[0] + assert.Equal(t, now.UnixNano()/1e6, row1[0].(time.Time).UnixNano()/1e6) + assert.Equal(t, true, row1[1].(bool)) + assert.Equal(t, int8(1), row1[2].(int8)) + assert.Equal(t, int16(1), row1[3].(int16)) + assert.Equal(t, int32(1), row1[4].(int32)) + assert.Equal(t, int64(1), row1[5].(int64)) + assert.Equal(t, uint8(1), row1[6].(uint8)) + assert.Equal(t, uint16(1), row1[7].(uint16)) + assert.Equal(t, uint32(1), row1[8].(uint32)) + assert.Equal(t, uint64(1), row1[9].(uint64)) + assert.Equal(t, float32(1), row1[10].(float32)) + assert.Equal(t, float64(1), row1[11].(float64)) + assert.Equal(t, "test_binary", row1[12].(string)) + assert.Equal(t, "test_nchar", row1[13].(string)) + row2 := data[1] + assert.Equal(t, after1s.UnixNano()/1e6, row2[0].(time.Time).UnixNano()/1e6) + for i := 1; i < 14; i++ { + assert.Nil(t, row2[i]) + } +} + +// @author: xftan +// @date: 2023/11/17 9:37 +// @description: test write raw block with fields and reqid +func TestTaosWriteRawBlockWithFieldsWithReqID(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + + defer TaosClose(conn) + res := TaosQuery(conn, "drop database if exists test_write_block_raw_fields_with_reqid") + code := TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + defer func() { + res = TaosQuery(conn, "drop database if exists test_write_block_raw_fields_with_reqid") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + }() + res = TaosQuery(conn, "create database test_write_block_raw_fields_with_reqid") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + res = TaosQuery(conn, "create table if not exists test_write_block_raw_fields_with_reqid.all_type (ts timestamp,"+ + "c1 bool,"+ + "c2 tinyint,"+ + "c3 smallint,"+ + "c4 int,"+ + "c5 bigint,"+ + "c6 tinyint unsigned,"+ + "c7 smallint unsigned,"+ + "c8 int unsigned,"+ + "c9 bigint unsigned,"+ + "c10 float,"+ + "c11 double,"+ + "c12 binary(20),"+ + "c13 nchar(20)"+ + ") tags (info json)") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + now := time.Now() + after1s := now.Add(time.Second) + sql := fmt.Sprintf("insert into test_write_block_raw_fields_with_reqid.t0 using test_write_block_raw_fields_with_reqid.all_type tags('{\"a\":1}') values('%s',1,1,1,1,1,1,1,1,1,1,1,'test_binary','test_nchar')('%s',null,null,null,null,null,null,null,null,null,null,null,null,null)", now.Format(time.RFC3339Nano), after1s.Format(time.RFC3339Nano)) + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + sql = "create table test_write_block_raw_fields_with_reqid.t1 using test_write_block_raw_fields_with_reqid.all_type tags('{\"a\":2}')" + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + sql = "use test_write_block_raw_fields_with_reqid" + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + sql = "select ts,c1 from test_write_block_raw_fields_with_reqid.t0" + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + for { + blockSize, errCode, block := TaosFetchRawBlock(res) + if errCode != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(errCode, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + if blockSize == 0 { + break + } + fieldsCount := TaosNumFields(res) + fields := TaosFetchFields(res) + + errCode = TaosWriteRawBlockWithFieldsWithReqID(conn, blockSize, block, "t1", fields, fieldsCount, 1) + if errCode != int(errors.SUCCESS) { + errStr := TaosErrorStr(nil) + err := errors.NewError(errCode, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + } + TaosFreeResult(res) + + sql = "select * from test_write_block_raw_fields_with_reqid.t1" + res = TaosQuery(conn, sql) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + fileCount := TaosNumFields(res) + rh, err := ReadColumn(res, fileCount) + if err != nil { + t.Error(err) + return + } + precision := TaosResultPrecision(res) + var data [][]driver.Value + for { + blockSize, errCode, block := TaosFetchRawBlock(res) + if errCode != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + if blockSize == 0 { + break + } + d := parser.ReadBlock(block, blockSize, rh.ColTypes, precision) + data = append(data, d...) + } + TaosFreeResult(res) + + assert.Equal(t, 2, len(data)) + row1 := data[0] + assert.Equal(t, now.UnixNano()/1e6, row1[0].(time.Time).UnixNano()/1e6) + assert.Equal(t, true, row1[1].(bool)) + for i := 2; i < 14; i++ { + assert.Nil(t, row1[i]) + } + row2 := data[1] + assert.Equal(t, after1s.UnixNano()/1e6, row2[0].(time.Time).UnixNano()/1e6) + for i := 1; i < 14; i++ { + assert.Nil(t, row2[i]) + } +} diff --git a/driver/wrapper/cgo/README.md b/driver/wrapper/cgo/README.md new file mode 100644 index 00000000..329a9baa --- /dev/null +++ b/driver/wrapper/cgo/README.md @@ -0,0 +1 @@ +Copy from go 1.17.2 runtime/cgo. In order to be compatible with lower versions \ No newline at end of file diff --git a/driver/wrapper/cgo/handle.go b/driver/wrapper/cgo/handle.go new file mode 100644 index 00000000..586adcf3 --- /dev/null +++ b/driver/wrapper/cgo/handle.go @@ -0,0 +1,81 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cgo + +import ( + "sync" + "sync/atomic" + "unsafe" +) + +// Handle provides a way to pass values that contain Go pointers +// (pointers to memory allocated by Go) between Go and C without +// breaking the cgo pointer passing rules. A Handle is an integer +// value that can represent any Go value. A Handle can be passed +// through C and back to Go, and Go code can use the Handle to +// retrieve the original Go value. +// +// The underlying type of Handle is guaranteed to fit in an integer type +// that is large enough to hold the bit pattern of any pointer. The zero +// value of a Handle is not valid, and thus is safe to use as a sentinel +// in C APIs. + +type Handle uintptr + +// NewHandle returns a handle for a given value. +// +// The handle is valid until the program calls Delete on it. The handle +// uses resources, and this package assumes that C code may hold on to +// the handle, so a program must explicitly call Delete when the handle +// is no longer needed. +// +// The intended use is to pass the returned handle to C code, which +// passes it back to Go, which calls Value. +func NewHandle(v interface{}) Handle { + h := atomic.AddUintptr(&handleIdx, 1) + if h == 0 { + panic("runtime/cgo: ran out of handle space") + } + + handles.Store(h, v) + handle := Handle(h) + handlePointers.Store(h, &handle) + return handle +} + +// Value returns the associated Go value for a valid handle. +// +// The method panics if the handle is invalid. +func (h Handle) Value() interface{} { + v, ok := handles.Load(uintptr(h)) + if !ok { + panic("runtime/cgo: misuse of an invalid Handle") + } + return v +} + +func (h Handle) Pointer() unsafe.Pointer { + p, ok := handlePointers.Load(uintptr(h)) + if !ok { + panic("runtime/cgo: misuse of an invalid Handle") + } + return unsafe.Pointer(p.(*Handle)) +} + +// Delete invalidates a handle. This method should only be called once +// the program no longer needs to pass the handle to C and the C code +// no longer has a copy of the handle value. +// +// The method panics if the handle is invalid. +func (h Handle) Delete() { + handles.Delete(uintptr(h)) + handlePointers.Delete(uintptr(h)) +} + +var ( + handles = sync.Map{} // map[Handle]interface{} + handlePointers = sync.Map{} // map[Handle]*Handle + handleIdx uintptr // atomic +) diff --git a/driver/wrapper/cgo/handle_test.go b/driver/wrapper/cgo/handle_test.go new file mode 100644 index 00000000..46cdc52d --- /dev/null +++ b/driver/wrapper/cgo/handle_test.go @@ -0,0 +1,107 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cgo + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" +) + +// @author: xftan +// @date: 2022/1/27 17:21 +// @description: test cgo handler +func TestHandle(t *testing.T) { + v := 42 + + tests := []struct { + v1 interface{} + v2 interface{} + }{ + {v1: v, v2: v}, + {v1: &v, v2: &v}, + {v1: nil, v2: nil}, + } + + for _, tt := range tests { + h1 := NewHandle(tt.v1) + h2 := NewHandle(tt.v2) + + if uintptr(h1) == 0 || uintptr(h2) == 0 { + t.Fatalf("NewHandle returns zero") + } + + if uintptr(h1) == uintptr(h2) { + t.Fatalf("Duplicated Go values should have different handles, but got equal") + } + + h1v := h1.Value() + h2v := h2.Value() + if !reflect.DeepEqual(h1v, h2v) || !reflect.DeepEqual(h1v, tt.v1) { + t.Fatalf("Value of a Handle got wrong, got %+v %+v, want %+v", h1v, h2v, tt.v1) + } + + h1.Delete() + h2.Delete() + } + + siz := 0 + handles.Range(func(k, v interface{}) bool { + siz++ + return true + }) + if siz != 0 { + t.Fatalf("handles are not cleared, got %d, want %d", siz, 0) + } +} + +func TestPointer(t *testing.T) { + v := 42 + h := NewHandle(&v) + p := h.Pointer() + assert.Equal(t, *(*Handle)(p), h) + h.Delete() + defer func() { + if r := recover(); r != nil { + return + } + t.Fatalf("Pointer should panic") + }() + h.Pointer() +} + +func TestInvalidValue(t *testing.T) { + v := 42 + h := NewHandle(&v) + h.Delete() + defer func() { + if r := recover(); r != nil { + return + } + t.Fatalf("Value should panic") + }() + h.Value() +} + +func BenchmarkHandle(b *testing.B) { + b.Run("non-concurrent", func(b *testing.B) { + for i := 0; i < b.N; i++ { + h := NewHandle(i) + _ = h.Value() + h.Delete() + } + }) + b.Run("concurrent", func(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + var v int + for pb.Next() { + h := NewHandle(v) + _ = h.Value() + h.Delete() + } + }) + }) +} diff --git a/driver/wrapper/field.go b/driver/wrapper/field.go new file mode 100644 index 00000000..feb3492e --- /dev/null +++ b/driver/wrapper/field.go @@ -0,0 +1,67 @@ +package wrapper + +/* +#include +*/ +import "C" +import ( + "bytes" + "reflect" + "unsafe" + + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/errors" +) + +type RowsHeader struct { + ColNames []string + ColTypes []uint8 + ColLength []int64 +} + +func ReadColumn(result unsafe.Pointer, count int) (*RowsHeader, error) { + if result == nil { + return nil, &errors.TaosError{Code: 0xffff, ErrStr: "invalid result"} + } + rowsHeader := &RowsHeader{ + ColNames: make([]string, count), + ColTypes: make([]uint8, count), + ColLength: make([]int64, count), + } + pFields := TaosFetchFields(result) + for i := 0; i < count; i++ { + field := *(*C.struct_taosField)(unsafe.Pointer(uintptr(pFields) + uintptr(C.sizeof_struct_taosField*C.int(i)))) + buf := bytes.NewBufferString("") + for _, c := range field.name { + if c == 0 { + break + } + buf.WriteByte(byte(c)) + } + rowsHeader.ColNames[i] = buf.String() + rowsHeader.ColTypes[i] = (uint8)(field._type) + rowsHeader.ColLength[i] = int64((uint32)(field.bytes)) + } + return rowsHeader, nil +} + +func (rh *RowsHeader) TypeDatabaseName(i int) string { + return common.TypeNameMap[int(rh.ColTypes[i])] +} + +func (rh *RowsHeader) ScanType(i int) reflect.Type { + t, exist := common.ColumnTypeMap[int(rh.ColTypes[i])] + if !exist { + return common.UnknownType + } + return t +} + +func FetchLengths(res unsafe.Pointer, count int) []int { + lengths := TaosFetchLengths(res) + result := make([]int, count) + for i := 0; i < count; i++ { + result[i] = int(*(*C.int)(unsafe.Pointer(uintptr(lengths) + uintptr(C.sizeof_int*C.int(i))))) + } + return result +} diff --git a/driver/wrapper/field_test.go b/driver/wrapper/field_test.go new file mode 100644 index 00000000..47210c13 --- /dev/null +++ b/driver/wrapper/field_test.go @@ -0,0 +1,483 @@ +package wrapper + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/errors" +) + +// @author: xftan +// @date: 2022/1/27 17:22 +// @description: test taos_fetch_lengths +func TestFetchLengths(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + res := TaosQuery(conn, "drop database if exists test_fetch_lengths") + code := TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + }() + res := TaosQuery(conn, "create database if not exists test_fetch_lengths") + code := TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + defer func() { + res := TaosQuery(conn, "drop database if exists test_fetch_lengths") + code := TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + }() + res = TaosQuery(conn, "drop table if exists test_fetch_lengths.test") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + res = TaosQuery(conn, "create table if not exists test_fetch_lengths.test (ts timestamp, c1 int,c2 binary(10),c3 nchar(10))") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + res = TaosQuery(conn, "insert into test_fetch_lengths.test values(now,1,'123','456')") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + + res = TaosQuery(conn, "select * from test_fetch_lengths.test") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + count := TaosNumFields(res) + assert.Equal(t, 4, count) + _, rows := TaosFetchBlock(res) + _ = rows + lengthList := FetchLengths(res, count) + TaosFreeResult(res) + assert.Equal(t, []int{8, 4, 12, 42}, lengthList) +} + +// @author: xftan +// @date: 2022/1/27 17:23 +// @description: test result column database name +func TestRowsHeader_TypeDatabaseName(t *testing.T) { + type fields struct { + ColNames []string + ColTypes []uint8 + ColLength []int64 + } + type args struct { + i int + } + tests := []struct { + name string + fields fields + args args + want string + }{ + { + name: "NULL", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 0, + }, + want: "NULL", + }, + { + name: "BOOL", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 1, + }, + want: "BOOL", + }, + { + name: "TINYINT", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 2, + }, + want: "TINYINT", + }, + { + name: "SMALLINT", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 3, + }, + want: "SMALLINT", + }, + { + name: "INT", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 4, + }, + want: "INT", + }, + { + name: "BIGINT", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 5, + }, + want: "BIGINT", + }, + { + name: "FLOAT", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 6, + }, + want: "FLOAT", + }, + { + name: "DOUBLE", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 7, + }, + want: "DOUBLE", + }, + { + name: "BINARY", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 8, + }, + want: "VARCHAR", + }, + { + name: "TIMESTAMP", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 9, + }, + want: "TIMESTAMP", + }, + { + name: "NCHAR", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 10, + }, + want: "NCHAR", + }, + { + name: "TINYINT UNSIGNED", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 11, + }, + want: "TINYINT UNSIGNED", + }, + { + name: "SMALLINT UNSIGNED", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 12, + }, + want: "SMALLINT UNSIGNED", + }, + { + name: "INT UNSIGNED", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 13, + }, + want: "INT UNSIGNED", + }, + { + name: "BIGINT UNSIGNED", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 14, + }, + want: "BIGINT UNSIGNED", + }, + { + name: "JSON", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 15, + }, + want: "JSON", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rh := &RowsHeader{ + ColNames: tt.fields.ColNames, + ColTypes: tt.fields.ColTypes, + ColLength: tt.fields.ColLength, + } + if got := rh.TypeDatabaseName(tt.args.i); got != tt.want { + t.Errorf("TypeDatabaseName() = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 17:23 +// @description: test scan result column type +func TestRowsHeader_ScanType(t *testing.T) { + type fields struct { + ColNames []string + ColTypes []uint8 + ColLength []int64 + } + type args struct { + i int + } + tests := []struct { + name string + fields fields + args args + want reflect.Type + }{ + { + name: "unknown", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 0, + }, + want: common.UnknownType, + }, + { + name: "BOOL", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 1, + }, + want: common.NullBool, + }, + { + name: "TINYINT", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 2, + }, + want: common.NullInt8, + }, + { + name: "SMALLINT", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 3, + }, + want: common.NullInt16, + }, { + name: "INT", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 4, + }, + want: common.NullInt32, + }, + { + name: "BIGINT", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 5, + }, + want: common.NullInt64, + }, + { + name: "FLOAT", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 6, + }, + want: common.NullFloat32, + }, + { + name: "DOUBLE", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 7, + }, + want: common.NullFloat64, + }, + { + name: "BINARY", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 8, + }, + want: common.NullString, + }, + { + name: "TIMESTAMP", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 9, + }, + want: common.NullTime, + }, + { + name: "NCHAR", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 10, + }, + want: common.NullString, + }, + { + name: "TINYINT UNSIGNED", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 11, + }, + want: common.NullUInt8, + }, + { + name: "SMALLINT UNSIGNED", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 12, + }, + want: common.NullUInt16, + }, + { + name: "INT UNSIGNED", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 13, + }, + want: common.NullUInt32, + }, + { + name: "BIGINT UNSIGNEDD", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 14, + }, + want: common.NullUInt64, + }, + { + name: "JSON", + fields: fields{ + ColTypes: []uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + args: args{ + i: 15, + }, + want: common.NullJson, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rh := &RowsHeader{ + ColNames: tt.fields.ColNames, + ColTypes: tt.fields.ColTypes, + ColLength: tt.fields.ColLength, + } + if got := rh.ScanType(tt.args.i); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ScanType() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/driver/wrapper/notify.go b/driver/wrapper/notify.go new file mode 100644 index 00000000..21aa9ef1 --- /dev/null +++ b/driver/wrapper/notify.go @@ -0,0 +1,22 @@ +package wrapper + +/* +#include +#include +#include +#include +extern void NotifyCallback(void *param, void *ext, int type); +int taos_set_notify_cb_wrapper(TAOS *taos, void *param, int type){ + return taos_set_notify_cb(taos,NotifyCallback,param,type); +}; +*/ +import "C" +import ( + "unsafe" + + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" +) + +func TaosSetNotifyCB(taosConnect unsafe.Pointer, caller cgo.Handle, notifyType int) int32 { + return int32(C.taos_set_notify_cb_wrapper(taosConnect, caller.Pointer(), (C.int)(notifyType))) +} diff --git a/driver/wrapper/notify_test.go b/driver/wrapper/notify_test.go new file mode 100644 index 00000000..8db0cb37 --- /dev/null +++ b/driver/wrapper/notify_test.go @@ -0,0 +1,100 @@ +package wrapper + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" +) + +// @author: xftan +// @date: 2023/10/13 11:28 +// @description: test notify callback +func TestNotify(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + + defer TaosClose(conn) + defer func() { + _ = exec(conn, "drop user t_notify") + }() + _ = exec(conn, "drop user t_notify") + err = exec(conn, "create user t_notify pass 'notify'") + assert.NoError(t, err) + + conn2, err := TaosConnect("", "t_notify", "notify", "", 0) + if err != nil { + t.Error(err) + return + } + + defer TaosClose(conn2) + notify := make(chan int32, 1) + handler := cgo.NewHandle(notify) + errCode := TaosSetNotifyCB(conn2, handler, common.TAOS_NOTIFY_PASSVER) + if errCode != 0 { + errStr := TaosErrorStr(nil) + t.Error(errCode, errStr) + } + notifyWhitelist := make(chan int64, 1) + handlerWhiteList := cgo.NewHandle(notifyWhitelist) + errCode = TaosSetNotifyCB(conn2, handlerWhiteList, common.TAOS_NOTIFY_WHITELIST_VER) + if errCode != 0 { + errStr := TaosErrorStr(nil) + t.Error(errCode, errStr) + } + + notifyDropUser := make(chan struct{}, 1) + handlerDropUser := cgo.NewHandle(notifyDropUser) + errCode = TaosSetNotifyCB(conn2, handlerDropUser, common.TAOS_NOTIFY_USER_DROPPED) + if errCode != 0 { + errStr := TaosErrorStr(nil) + t.Error(errCode, errStr) + } + + err = exec(conn, "alter user t_notify pass 'test'") + assert.NoError(t, err) + timeout, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + now := time.Now() + select { + case version := <-notify: + t.Log(time.Since(now)) + t.Log("password changed", version) + case <-timeout.Done(): + t.Error("wait for notify callback timeout") + } + + err = exec(conn, "ALTER USER t_notify ADD HOST '192.168.1.98/0','192.168.1.98/32'") + assert.NoError(t, err) + timeoutWhiteList, cancelWhitelist := context.WithTimeout(context.Background(), time.Second*5) + defer cancelWhitelist() + now = time.Now() + select { + case version := <-notifyWhitelist: + t.Log(time.Since(now)) + t.Log("whitelist changed", version) + case <-timeoutWhiteList.Done(): + t.Error("wait for notifyWhitelist callback timeout") + } + + err = exec(conn, "drop USER t_notify") + assert.NoError(t, err) + timeoutDropUser, cancelDropUser := context.WithTimeout(context.Background(), time.Second*5) + defer cancelDropUser() + now = time.Now() + select { + case <-notifyDropUser: + t.Log(time.Since(now)) + t.Log("user dropped") + case <-timeoutDropUser.Done(): + t.Error("wait for notifyDropUser callback timeoutDropUser") + } + +} diff --git a/driver/wrapper/notifycb.go b/driver/wrapper/notifycb.go new file mode 100644 index 00000000..8eb5c522 --- /dev/null +++ b/driver/wrapper/notifycb.go @@ -0,0 +1,36 @@ +package wrapper + +/* +#include +#include +#include +#include +*/ +import "C" +import ( + "unsafe" + + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" +) + +//export NotifyCallback +func NotifyCallback(p unsafe.Pointer, ext unsafe.Pointer, notifyType C.int) { + defer func() { + // channel may be closed + _ = recover() + }() + switch int(notifyType) { + case common.TAOS_NOTIFY_PASSVER: + version := int32(*(*C.int32_t)(ext)) + c := (*(*cgo.Handle)(p)).Value().(chan int32) + c <- version + case common.TAOS_NOTIFY_WHITELIST_VER: + version := int64(*(*C.int64_t)(ext)) + c := (*(*cgo.Handle)(p)).Value().(chan int64) + c <- version + case common.TAOS_NOTIFY_USER_DROPPED: + c := (*(*cgo.Handle)(p)).Value().(chan struct{}) + c <- struct{}{} + } +} diff --git a/driver/wrapper/row.go b/driver/wrapper/row.go new file mode 100644 index 00000000..ebe4d8cd --- /dev/null +++ b/driver/wrapper/row.go @@ -0,0 +1,77 @@ +package wrapper + +/* +#include +*/ +import "C" +import ( + "database/sql/driver" + "unsafe" + + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/tools" +) + +const ( + PointerSize = unsafe.Sizeof(uintptr(1)) +) + +type FormatTimeFunc func(ts int64, precision int) driver.Value + +func FetchRow(row unsafe.Pointer, offset int, colType uint8, length int, arg ...interface{}) driver.Value { + base := *(**C.void)(tools.AddPointer(row, uintptr(offset)*PointerSize)) + p := unsafe.Pointer(base) + if p == nil { + return nil + } + switch colType { + case C.TSDB_DATA_TYPE_BOOL: + if v := *((*byte)(p)); v != 0 { + return true + } else { + return false + } + case C.TSDB_DATA_TYPE_TINYINT: + return *((*int8)(p)) + case C.TSDB_DATA_TYPE_SMALLINT: + return *((*int16)(p)) + case C.TSDB_DATA_TYPE_INT: + return *((*int32)(p)) + case C.TSDB_DATA_TYPE_BIGINT: + return *((*int64)(p)) + case C.TSDB_DATA_TYPE_UTINYINT: + return *((*uint8)(p)) + case C.TSDB_DATA_TYPE_USMALLINT: + return *((*uint16)(p)) + case C.TSDB_DATA_TYPE_UINT: + return *((*uint32)(p)) + case C.TSDB_DATA_TYPE_UBIGINT: + return *((*uint64)(p)) + case C.TSDB_DATA_TYPE_FLOAT: + return *((*float32)(p)) + case C.TSDB_DATA_TYPE_DOUBLE: + return *((*float64)(p)) + case C.TSDB_DATA_TYPE_BINARY, C.TSDB_DATA_TYPE_NCHAR: + data := make([]byte, length) + for i := 0; i < length; i++ { + data[i] = *((*byte)(tools.AddPointer(p, uintptr(i)))) + } + return string(data) + case C.TSDB_DATA_TYPE_TIMESTAMP: + if len(arg) == 1 { + return common.TimestampConvertToTime(*((*int64)(p)), arg[0].(int)) + } else if len(arg) == 2 { + return arg[1].(FormatTimeFunc)(*((*int64)(p)), arg[0].(int)) + } else { + panic("convertTime error") + } + case C.TSDB_DATA_TYPE_JSON, C.TSDB_DATA_TYPE_VARBINARY, C.TSDB_DATA_TYPE_GEOMETRY: + data := make([]byte, length) + for i := 0; i < length; i++ { + data[i] = *((*byte)(tools.AddPointer(p, uintptr(i)))) + } + return data + default: + return nil + } +} diff --git a/driver/wrapper/row_test.go b/driver/wrapper/row_test.go new file mode 100644 index 00000000..d58e25a8 --- /dev/null +++ b/driver/wrapper/row_test.go @@ -0,0 +1,628 @@ +package wrapper + +import ( + "database/sql/driver" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/taosdata/taosadapter/v3/driver/errors" +) + +// @author: xftan +// @date: 2022/1/27 17:24 +// @description: test fetch json result +func TestFetchRowJSON(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + + defer TaosClose(conn) + defer func() { + res := TaosQuery(conn, "drop database if exists test_json_wrapper") + code := TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + }() + res := TaosQuery(conn, "create database if not exists test_json_wrapper") + code := TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(errors.NewError(code, errStr)) + return + } + TaosFreeResult(res) + defer func() { + res := TaosQuery(conn, "drop database if exists test_json_wrapper") + code := TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(&errors.TaosError{ + Code: int32(code) & 0xffff, + ErrStr: errStr, + }) + return + } + TaosFreeResult(res) + }() + res = TaosQuery(conn, "drop table if exists test_json_wrapper.tjsonr") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(&errors.TaosError{ + Code: int32(code) & 0xffff, + ErrStr: errStr, + }) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, "create stable if not exists test_json_wrapper.tjsonr(ts timestamp,v int )tags(t json)") + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(&errors.TaosError{ + Code: int32(code) & 0xffff, + ErrStr: errStr, + }) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, `insert into test_json_wrapper.tjr_1 using test_json_wrapper.tjsonr tags('{"a":1,"b":"b"}')values (now,1)`) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(&errors.TaosError{ + Code: int32(code) & 0xffff, + ErrStr: errStr, + }) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, `insert into test_json_wrapper.tjr_2 using test_json_wrapper.tjsonr tags('{"a":1,"c":"c"}')values (now+1s,1)`) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(&errors.TaosError{ + Code: int32(code) & 0xffff, + ErrStr: errStr, + }) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, `insert into test_json_wrapper.tjr_3 using test_json_wrapper.tjsonr tags('null')values (now+2s,1)`) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(&errors.TaosError{ + Code: int32(code) & 0xffff, + ErrStr: errStr, + }) + return + } + TaosFreeResult(res) + + res = TaosQuery(conn, `select * from test_json_wrapper.tjsonr order by ts`) + code = TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + TaosFreeResult(res) + t.Error(&errors.TaosError{ + Code: int32(code) & 0xffff, + ErrStr: errStr, + }) + return + } + numFields := TaosFieldCount(res) + precision := TaosResultPrecision(res) + assert.Equal(t, 3, numFields) + headers, err := ReadColumn(res, numFields) + assert.NoError(t, err) + var data [][]driver.Value + for i := 0; i < 3; i++ { + var d []driver.Value + row := TaosFetchRow(res) + lengths := FetchLengths(res, numFields) + for j := range headers.ColTypes { + d = append(d, FetchRow(row, j, headers.ColTypes[j], lengths[j], precision)) + } + data = append(data, d) + } + TaosFreeResult(res) + t.Log(data) + assert.Equal(t, `{"a":1,"b":"b"}`, string(data[0][2].([]byte))) + assert.Equal(t, `{"a":1,"c":"c"}`, string(data[1][2].([]byte))) + assert.Nil(t, data[2][2]) +} + +// @author: xftan +// @date: 2022/1/27 17:24 +// @description: test TS-781 error +func TestFetchRow(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + db := "test_ts_781" + //create stable stb1 (ts timestamp, name binary(10)) tags(n int); + //insert into tb1 using stb1 tags(1) values(now, 'log'); + //insert into tb2 using stb1 tags(2) values(now, 'test'); + //insert into tb3 using stb1 tags(3) values(now, 'db02'); + //insert into tb4 using stb1 tags(4) values(now, 'db3'); + defer func() { + res := TaosQuery(conn, "drop database if exists "+db) + code := TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + }() + res := TaosQuery(conn, "create database if not exists "+db) + code := TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, fmt.Sprintf("create stable if not exists %s.stb1 (ts timestamp, name binary(10)) tags(n int);", db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, fmt.Sprintf("create table if not exists %s.tb1 using %s.stb1 tags(1)", db, db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + + res = TaosQuery(conn, fmt.Sprintf("insert into %s.tb1 values(now, 'log');", db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, fmt.Sprintf("create table if not exists %s.tb2 using %s.stb1 tags(2)", db, db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, fmt.Sprintf("insert into %s.tb2 values(now, 'test');", db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, fmt.Sprintf("create table if not exists %s.tb3 using %s.stb1 tags(3)", db, db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, fmt.Sprintf("insert into %s.tb3 values(now, 'db02')", db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, fmt.Sprintf("create table if not exists %s.tb4 using %s.stb1 tags(4)", db, db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, fmt.Sprintf("insert into %s.tb4 values(now, 'db3');", db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, fmt.Sprintf("select distinct(name) from %s.stb1;", db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + numFields := TaosFieldCount(res) + header, err := ReadColumn(res, numFields) + if err != nil { + TaosFreeResult(res) + t.Error(err) + return + } + names := map[string]struct{}{ + "log": {}, + "test": {}, + "db02": {}, + "db3": {}, + } + for { + rr := TaosFetchRow(res) + lengths := FetchLengths(res, numFields) + if rr == nil { + break + } + d := FetchRow(rr, 0, header.ColTypes[0], lengths[0]) + delete(names, d.(string)) + } + TaosFreeResult(res) + + assert.Empty(t, names) +} + +// @author: xftan +// @date: 2022/1/27 17:24 +// @description: test TS-781 nchar type error +func TestFetchRowNchar(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + db := "test_ts_781_nchar" + //create stable stb1 (ts timestamp, name nchar(10)) tags(n int); + //insert into tb1 using stb1 tags(1) values(now, 'log'); + //insert into tb2 using stb1 tags(2) values(now, 'test'); + //insert into tb3 using stb1 tags(3) values(now, 'db02'); + //insert into tb4 using stb1 tags(4) values(now, 'db3'); + defer func() { + res := TaosQuery(conn, "drop database if exists "+db) + code := TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + }() + res := TaosQuery(conn, "create database if not exists "+db) + code := TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, fmt.Sprintf("create stable if not exists %s.stb1 (ts timestamp, name nchar(10)) tags(n int);", db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + + res = TaosQuery(conn, fmt.Sprintf("create table if not exists %s.tb1 using %s.stb1 tags(1)", db, db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + + res = TaosQuery(conn, fmt.Sprintf("create table if not exists %s.tb2 using %s.stb1 tags(2)", db, db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + + res = TaosQuery(conn, fmt.Sprintf("create table if not exists %s.tb3 using %s.stb1 tags(3)", db, db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + + res = TaosQuery(conn, fmt.Sprintf("create table if not exists %s.tb4 using %s.stb1 tags(4)", db, db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + + res = TaosQuery(conn, fmt.Sprintf("insert into %s.tb1 values(now, 'log');", db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, fmt.Sprintf("insert into %s.tb2 values(now, 'test');", db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, fmt.Sprintf("insert into %s.tb3 values(now, 'db02')", db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, fmt.Sprintf("insert into %s.tb4 values(now, 'db3');", db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, fmt.Sprintf("select distinct(name) from %s.stb1;", db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + numFields := TaosFieldCount(res) + header, err := ReadColumn(res, numFields) + if err != nil { + TaosFreeResult(res) + t.Error(err) + return + } + names := map[string]struct{}{ + "log": {}, + "test": {}, + "db02": {}, + "db3": {}, + } + for { + rr := TaosFetchRow(res) + lengths := FetchLengths(res, numFields) + if rr == nil { + break + } + d := FetchRow(rr, 0, header.ColTypes[0], lengths[0]) + delete(names, d.(string)) + } + TaosFreeResult(res) + assert.Empty(t, names) +} + +// @author: xftan +// @date: 2023/10/13 11:28 +// @description: test fetch row all type +func TestFetchRowAllType(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + db := "test_fetch_row_all" + + res := TaosQuery(conn, "drop database if exists "+db) + code := TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + defer func() { + res := TaosQuery(conn, "drop database if exists "+db) + code := TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + }() + res = TaosQuery(conn, "create database if not exists "+db) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, fmt.Sprintf( + "create stable if not exists %s.stb1 (ts timestamp,"+ + "c1 bool,"+ + "c2 tinyint,"+ + "c3 smallint,"+ + "c4 int,"+ + "c5 bigint,"+ + "c6 tinyint unsigned,"+ + "c7 smallint unsigned,"+ + "c8 int unsigned,"+ + "c9 bigint unsigned,"+ + "c10 float,"+ + "c11 double,"+ + "c12 binary(20),"+ + "c13 nchar(20),"+ + "c14 varbinary(20),"+ + "c15 geometry(100)"+ + ")"+ + "tags(t json)", db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + + res = TaosQuery(conn, fmt.Sprintf("create table if not exists %s.tb1 using %s.stb1 tags('{\"a\":1}')", db, db)) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + now := time.Now() + res = TaosQuery(conn, fmt.Sprintf("insert into %s.tb1 values('%s',true,2,3,4,5,6,7,8,9,10,11,'binary','nchar','varbinary','POINT(100 100)');", db, now.Format(time.RFC3339Nano))) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + TaosFreeResult(res) + + res = TaosQuery(conn, fmt.Sprintf("select * from %s.stb1 where ts = '%s';", db, now.Format(time.RFC3339Nano))) + code = TaosError(res) + if code != int(errors.SUCCESS) { + errStr := TaosErrorStr(res) + err := errors.NewError(code, errStr) + t.Error(err) + TaosFreeResult(res) + return + } + numFields := TaosFieldCount(res) + header, err := ReadColumn(res, numFields) + if err != nil { + TaosFreeResult(res) + t.Error(err) + return + } + precision := TaosResultPrecision(res) + count := 0 + result := make([]driver.Value, numFields) + for { + rr := TaosFetchRow(res) + if rr == nil { + break + } + count += 1 + lengths := FetchLengths(res, numFields) + + for i := range header.ColTypes { + result[i] = FetchRow(rr, i, header.ColTypes[i], lengths[i], precision) + } + } + assert.Equal(t, 1, count) + assert.Equal(t, now.UnixNano()/1e6, result[0].(time.Time).UnixNano()/1e6) + assert.Equal(t, true, result[1].(bool)) + assert.Equal(t, int8(2), result[2].(int8)) + assert.Equal(t, int16(3), result[3].(int16)) + assert.Equal(t, int32(4), result[4].(int32)) + assert.Equal(t, int64(5), result[5].(int64)) + assert.Equal(t, uint8(6), result[6].(uint8)) + assert.Equal(t, uint16(7), result[7].(uint16)) + assert.Equal(t, uint32(8), result[8].(uint32)) + assert.Equal(t, uint64(9), result[9].(uint64)) + assert.Equal(t, float32(10), result[10].(float32)) + assert.Equal(t, float64(11), result[11].(float64)) + assert.Equal(t, "binary", result[12].(string)) + assert.Equal(t, "nchar", result[13].(string)) + assert.Equal(t, []byte("varbinary"), result[14].([]byte)) + assert.Equal(t, []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, result[15].([]byte)) + assert.Equal(t, []byte(`{"a":1}`), result[16].([]byte)) +} diff --git a/driver/wrapper/schemaless.go b/driver/wrapper/schemaless.go new file mode 100644 index 00000000..4c81d618 --- /dev/null +++ b/driver/wrapper/schemaless.go @@ -0,0 +1,233 @@ +package wrapper + +/* +#include +#include +#include +#include +*/ +import "C" +import "unsafe" + +//revive:disable +const ( + InfluxDBLineProtocol = 1 + OpenTSDBTelnetLineProtocol = 2 + OpenTSDBJsonFormatProtocol = 3 +) +const ( + TSDB_SML_TIMESTAMP_NOT_CONFIGURED = iota + TSDB_SML_TIMESTAMP_HOURS + TSDB_SML_TIMESTAMP_MINUTES + TSDB_SML_TIMESTAMP_SECONDS + TSDB_SML_TIMESTAMP_MILLI_SECONDS + TSDB_SML_TIMESTAMP_MICRO_SECONDS + TSDB_SML_TIMESTAMP_NANO_SECONDS +) + +//revive:enable + +// TaosSchemalessInsert TAOS_RES *taos_schemaless_insert(TAOS* taos, char* lines[], int numLines, int protocol, int precision); +// Deprecated +func TaosSchemalessInsert(taosConnect unsafe.Pointer, lines []string, protocol int, precision string) unsafe.Pointer { + numLines, cLines, needFree := taosSchemalessInsertParams(lines) + defer func() { + for _, p := range needFree { + C.free(p) + } + }() + return unsafe.Pointer(C.taos_schemaless_insert( + taosConnect, + (**C.char)(&cLines[0]), + (C.int)(numLines), + (C.int)(protocol), + (C.int)(exchange(precision)), + )) +} + +// TaosSchemalessInsertTTL TAOS_RES *taos_schemaless_insert_ttl(TAOS *taos, char *lines[], int numLines, int protocol, int precision, int32_t ttl) +// Deprecated +func TaosSchemalessInsertTTL(taosConnect unsafe.Pointer, lines []string, protocol int, precision string, ttl int) unsafe.Pointer { + numLines, cLines, needFree := taosSchemalessInsertParams(lines) + defer func() { + for _, p := range needFree { + C.free(p) + } + }() + return unsafe.Pointer(C.taos_schemaless_insert_ttl( + taosConnect, + (**C.char)(&cLines[0]), + (C.int)(numLines), + (C.int)(protocol), + (C.int)(exchange(precision)), + (C.int32_t)(ttl), + )) +} + +// TaosSchemalessInsertWithReqID TAOS_RES *taos_schemaless_insert_with_reqid(TAOS *taos, char *lines[], int numLines, int protocol, int precision, int64_t reqid); +// Deprecated +func TaosSchemalessInsertWithReqID(taosConnect unsafe.Pointer, lines []string, protocol int, precision string, reqID int64) unsafe.Pointer { + numLines, cLines, needFree := taosSchemalessInsertParams(lines) + defer func() { + for _, p := range needFree { + C.free(p) + } + }() + return unsafe.Pointer(C.taos_schemaless_insert_with_reqid( + taosConnect, + (**C.char)(&cLines[0]), + (C.int)(numLines), + (C.int)(protocol), + (C.int)(exchange(precision)), + (C.int64_t)(reqID), + )) +} + +// TaosSchemalessInsertTTLWithReqID TAOS_RES *taos_schemaless_insert_ttl_with_reqid(TAOS *taos, char *lines[], int numLines, int protocol, int precision, int32_t ttl, int64_t reqid) +// Deprecated +func TaosSchemalessInsertTTLWithReqID(taosConnect unsafe.Pointer, lines []string, protocol int, precision string, ttl int, reqID int64) unsafe.Pointer { + numLines, cLines, needFree := taosSchemalessInsertParams(lines) + defer func() { + for _, p := range needFree { + C.free(p) + } + }() + return unsafe.Pointer(C.taos_schemaless_insert_ttl_with_reqid( + taosConnect, + (**C.char)(&cLines[0]), + (C.int)(numLines), + (C.int)(protocol), + (C.int)(exchange(precision)), + (C.int32_t)(ttl), + (C.int64_t)(reqID), + )) +} + +func taosSchemalessInsertParams(lines []string) (numLines int, cLines []*C.char, needFree []unsafe.Pointer) { + numLines = len(lines) + cLines = make([]*C.char, numLines) + needFree = make([]unsafe.Pointer, numLines) + for i, line := range lines { + cLine := C.CString(line) + needFree[i] = unsafe.Pointer(cLine) + cLines[i] = cLine + } + return +} + +// TaosSchemalessInsertRaw TAOS_RES *taos_schemaless_insert_raw(TAOS* taos, char* lines, int len, int32_t *totalRows, int protocol, int precision); +func TaosSchemalessInsertRaw(taosConnect unsafe.Pointer, lines string, protocol int, precision string) (int32, unsafe.Pointer) { + cLine := C.CString(lines) + defer C.free(unsafe.Pointer(cLine)) + var rows int32 + pTotalRows := unsafe.Pointer(&rows) + result := unsafe.Pointer(C.taos_schemaless_insert_raw( + taosConnect, + cLine, + (C.int)(len(lines)), + (*C.int32_t)(pTotalRows), + (C.int)(protocol), + (C.int)(exchange(precision)), + )) + return rows, result +} + +// TaosSchemalessInsertRawTTL TAOS_RES *taos_schemaless_insert_raw_ttl(TAOS *taos, char *lines, int len, int32_t *totalRows, int protocol, int precision, int32_t ttl); +func TaosSchemalessInsertRawTTL(taosConnect unsafe.Pointer, lines string, protocol int, precision string, ttl int) (int32, unsafe.Pointer) { + cLine := C.CString(lines) + defer C.free(unsafe.Pointer(cLine)) + var rows int32 + pTotalRows := unsafe.Pointer(&rows) + result := unsafe.Pointer(C.taos_schemaless_insert_raw_ttl( + taosConnect, + cLine, + (C.int)(len(lines)), + (*C.int32_t)(pTotalRows), + (C.int)(protocol), + (C.int)(exchange(precision)), + (C.int32_t)(ttl), + )) + return rows, result +} + +// TaosSchemalessInsertRawWithReqID TAOS_RES *taos_schemaless_insert_raw_with_reqid(TAOS *taos, char *lines, int len, int32_t *totalRows, int protocol, int precision, int64_t reqid); +func TaosSchemalessInsertRawWithReqID(taosConnect unsafe.Pointer, lines string, protocol int, precision string, reqID int64) (int32, unsafe.Pointer) { + cLine := C.CString(lines) + defer C.free(unsafe.Pointer(cLine)) + var rows int32 + pTotalRows := unsafe.Pointer(&rows) + result := unsafe.Pointer(C.taos_schemaless_insert_raw_with_reqid( + taosConnect, + cLine, + (C.int)(len(lines)), + (*C.int32_t)(pTotalRows), + (C.int)(protocol), + (C.int)(exchange(precision)), + (C.int64_t)(reqID), + )) + return rows, result +} + +// TaosSchemalessInsertRawTTLWithReqID TAOS_RES *taos_schemaless_insert_raw_ttl_with_reqid(TAOS *taos, char *lines, int len, int32_t *totalRows, int protocol, int precision, int32_t ttl, int64_t reqid) +func TaosSchemalessInsertRawTTLWithReqID(taosConnect unsafe.Pointer, lines string, protocol int, precision string, ttl int, reqID int64) (int32, unsafe.Pointer) { + cLine := C.CString(lines) + defer C.free(unsafe.Pointer(cLine)) + var rows int32 + pTotalRows := unsafe.Pointer(&rows) + result := C.taos_schemaless_insert_raw_ttl_with_reqid( + taosConnect, + cLine, + (C.int)(len(lines)), + (*C.int32_t)(pTotalRows), + (C.int)(protocol), + (C.int)(exchange(precision)), + (C.int32_t)(ttl), + (C.int64_t)(reqID), + ) + return rows, result +} + +// TaosSchemalessInsertRawTTLWithReqIDTBNameKey TAOS_RES *taos_schemaless_insert_raw_ttl_with_reqid_tbname_key(TAOS *taos, char *lines, int len, int32_t *totalRows, int protocol, int precision, int32_t ttl, int64_t reqid, char *tbnameKey); +func TaosSchemalessInsertRawTTLWithReqIDTBNameKey(taosConnect unsafe.Pointer, lines string, protocol int, precision string, ttl int, reqID int64, tbNameKey string) (int32, unsafe.Pointer) { + cLine := C.CString(lines) + defer C.free(unsafe.Pointer(cLine)) + cTBNameKey := (*C.char)(nil) + if tbNameKey != "" { + cTBNameKey = C.CString(tbNameKey) + defer C.free(unsafe.Pointer(cTBNameKey)) + } + var rows int32 + pTotalRows := unsafe.Pointer(&rows) + result := C.taos_schemaless_insert_raw_ttl_with_reqid_tbname_key( + taosConnect, + cLine, + (C.int)(len(lines)), + (*C.int32_t)(pTotalRows), + (C.int)(protocol), + (C.int)(exchange(precision)), + (C.int32_t)(ttl), + (C.int64_t)(reqID), + cTBNameKey, + ) + return rows, result +} + +func exchange(ts string) int { + switch ts { + case "": + return TSDB_SML_TIMESTAMP_NOT_CONFIGURED + case "h": + return TSDB_SML_TIMESTAMP_HOURS + case "m": + return TSDB_SML_TIMESTAMP_MINUTES + case "s": + return TSDB_SML_TIMESTAMP_SECONDS + case "ms": + return TSDB_SML_TIMESTAMP_MILLI_SECONDS + case "u", "μ": + return TSDB_SML_TIMESTAMP_MICRO_SECONDS + case "ns": + return TSDB_SML_TIMESTAMP_NANO_SECONDS + } + return TSDB_SML_TIMESTAMP_NOT_CONFIGURED +} diff --git a/driver/wrapper/schemaless_test.go b/driver/wrapper/schemaless_test.go new file mode 100644 index 00000000..b47ba8f4 --- /dev/null +++ b/driver/wrapper/schemaless_test.go @@ -0,0 +1,744 @@ +package wrapper_test + +import ( + "strings" + "testing" + "time" + "unsafe" + + "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" +) + +func prepareEnv() unsafe.Pointer { + conn, err := wrapper.TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + panic(err) + } + res := wrapper.TaosQuery(conn, "create database if not exists test_schemaless_common") + if wrapper.TaosError(res) != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + panic(errStr) + } + wrapper.TaosFreeResult(res) + code := wrapper.TaosSelectDB(conn, "test_schemaless_common") + if code != 0 { + panic("use db test_schemaless_common fail") + } + return conn +} + +func cleanEnv(conn unsafe.Pointer) { + res := wrapper.TaosQuery(conn, "drop database if exists test_schemaless_common") + if wrapper.TaosError(res) != 0 { + errStr := wrapper.TaosErrorStr(res) + wrapper.TaosFreeResult(res) + panic(errStr) + } + wrapper.TaosFreeResult(res) +} + +func BenchmarkTelnetSchemaless(b *testing.B) { + conn := prepareEnv() + defer wrapper.TaosClose(conn) + defer cleanEnv(conn) + for i := 0; i < b.N; i++ { + result := wrapper.TaosSchemalessInsert(conn, []string{ + "sys_if_bytes_out 1636626444 1.3E3 host=web01 interface=eth0", + }, wrapper.OpenTSDBTelnetLineProtocol, "") + code := wrapper.TaosError(result) + if code != 0 { + errStr := wrapper.TaosErrorStr(result) + wrapper.TaosFreeResult(result) + b.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(result) + } +} + +// @author: xftan +// @date: 2022/1/27 17:26 +// @description: test schemaless opentsdb telnet +func TestSchemalessTelnet(t *testing.T) { + conn := prepareEnv() + defer wrapper.TaosClose(conn) + defer cleanEnv(conn) + result := wrapper.TaosSchemalessInsert(conn, []string{ + "sys_if_bytes_out 1636626444 1.3E3 host=web01 interface=eth0", + }, wrapper.OpenTSDBTelnetLineProtocol, "") + code := wrapper.TaosError(result) + if code != 0 { + errStr := wrapper.TaosErrorStr(result) + wrapper.TaosFreeResult(result) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(result) + s := time.Now() + result = wrapper.TaosSchemalessInsert(conn, []string{ + "sys_if_bytes_out 1636626444 1.3E3 host=web01 interface=eth0", + }, wrapper.OpenTSDBTelnetLineProtocol, "") + code = wrapper.TaosError(result) + if code != 0 { + errStr := wrapper.TaosErrorStr(result) + wrapper.TaosFreeResult(result) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(result) + t.Log("finish ", time.Since(s)) +} + +// @author: xftan +// @date: 2022/1/27 17:26 +// @description: test schemaless influxDB +func TestSchemalessInfluxDB(t *testing.T) { + conn := prepareEnv() + defer wrapper.TaosClose(conn) + defer cleanEnv(conn) + { + result := wrapper.TaosSchemalessInsert(conn, []string{ + "measurement,host=host1 field1=2i,field2=2.0 1577836800000000000", + }, wrapper.InfluxDBLineProtocol, "") + code := wrapper.TaosError(result) + if code != 0 { + errStr := wrapper.TaosErrorStr(result) + wrapper.TaosFreeResult(result) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(result) + } + { + result := wrapper.TaosSchemalessInsert(conn, []string{ + "measurement,host=host1 field1=2i,field2=2.0 1577836800000000000", + }, wrapper.InfluxDBLineProtocol, "ns") + code := wrapper.TaosError(result) + if code != 0 { + errStr := wrapper.TaosErrorStr(result) + wrapper.TaosFreeResult(result) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(result) + } + { + result := wrapper.TaosSchemalessInsert(conn, []string{ + "measurement,host=host1 field1=2i,field2=2.0 1577836800000000", + }, wrapper.InfluxDBLineProtocol, "u") + code := wrapper.TaosError(result) + if code != 0 { + errStr := wrapper.TaosErrorStr(result) + wrapper.TaosFreeResult(result) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(result) + } + { + result := wrapper.TaosSchemalessInsert(conn, []string{ + "measurement,host=host1 field1=2i,field2=2.0 1577836800000000", + }, wrapper.InfluxDBLineProtocol, "μ") + code := wrapper.TaosError(result) + if code != 0 { + errStr := wrapper.TaosErrorStr(result) + wrapper.TaosFreeResult(result) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(result) + } + { + result := wrapper.TaosSchemalessInsert(conn, []string{ + "measurement,host=host1 field1=2i,field2=2.0 1577836800000", + }, wrapper.InfluxDBLineProtocol, "ms") + code := wrapper.TaosError(result) + if code != 0 { + errStr := wrapper.TaosErrorStr(result) + wrapper.TaosFreeResult(result) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(result) + } + { + result := wrapper.TaosSchemalessInsert(conn, []string{ + "measurement,host=host1 field1=2i,field2=2.0 1577836800", + }, wrapper.InfluxDBLineProtocol, "s") + code := wrapper.TaosError(result) + if code != 0 { + errStr := wrapper.TaosErrorStr(result) + wrapper.TaosFreeResult(result) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(result) + } + { + result := wrapper.TaosSchemalessInsert(conn, []string{ + "measurement,host=host1 field1=2i,field2=2.0 26297280", + }, wrapper.InfluxDBLineProtocol, "m") + code := wrapper.TaosError(result) + if code != 0 { + errStr := wrapper.TaosErrorStr(result) + wrapper.TaosFreeResult(result) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(result) + } + { + result := wrapper.TaosSchemalessInsert(conn, []string{ + "measurement,host=host1 field1=2i,field2=2.0 438288", + }, wrapper.InfluxDBLineProtocol, "h") + code := wrapper.TaosError(result) + if code != 0 { + errStr := wrapper.TaosErrorStr(result) + wrapper.TaosFreeResult(result) + t.Error(errors.NewError(code, errStr)) + return + } + wrapper.TaosFreeResult(result) + } +} + +// @author: xftan +// @date: 2023/10/13 11:28 +// @description: test schemaless insert with opentsdb telnet line protocol +func TestSchemalessRawTelnet(t *testing.T) { + conn := prepareEnv() + defer wrapper.TaosClose(conn) + defer cleanEnv(conn) + type in struct { + rows []string + } + data := []in{ + { + rows: []string{"sys_if_bytes_out 1636626444 1.3E3 host=web01 interface=eth0"}, + }, + { + rows: []string{"sys_if_bytes_out 1636626444 1.3E3 host=web01 interface=eth0"}, + }, + } + for _, d := range data { + row := strings.Join(d.rows, "\n") + totalRows, result := wrapper.TaosSchemalessInsertRaw(conn, row, wrapper.OpenTSDBTelnetLineProtocol, "") + code := wrapper.TaosError(result) + if code != 0 { + errStr := wrapper.TaosErrorStr(result) + wrapper.TaosFreeResult(result) + t.Log(row) + t.Error(errors.NewError(code, errStr)) + return + } + if int(totalRows) != len(d.rows) { + t.Log(row) + t.Errorf("expect rows %d got %d", len(d.rows), totalRows) + } + affected := wrapper.TaosAffectedRows(result) + if affected != len(d.rows) { + t.Log(row) + t.Errorf("expect affected %d got %d", len(d.rows), affected) + } + wrapper.TaosFreeResult(result) + } +} + +// @author: xftan +// @date: 2023/10/13 11:29 +// @description: test schemaless insert with opentsdb telnet line protocol +func TestSchemalessRawInfluxDB(t *testing.T) { + conn := prepareEnv() + defer wrapper.TaosClose(conn) + defer cleanEnv(conn) + type in struct { + rows []string + precision string + } + data := []in{ + { + rows: []string{"measurement,host=host1 field1=2i,field2=2.0 1577836800000000000"}, + precision: "", + }, + { + rows: []string{"measurement,host=host1 field1=2i,field2=2.0 1577836800000000000"}, + precision: "ns", + }, + { + rows: []string{"measurement,host=host1 field1=2i,field2=2.0 1577836800000000"}, + precision: "u", + }, + { + rows: []string{"measurement,host=host1 field1=2i,field2=2.0 1577836800000000"}, + precision: "μ", + }, + { + rows: []string{"measurement,host=host1 field1=2i,field2=2.0 1577836800000"}, + precision: "ms", + }, + { + rows: []string{"measurement,host=host1 field1=2i,field2=2.0 1577836800"}, + precision: "s", + }, + { + rows: []string{"measurement,host=host1 field1=2i,field2=2.0 26297280"}, + precision: "m", + }, + { + rows: []string{"measurement,host=host1 field1=2i,field2=2.0 438288"}, + precision: "h", + }, + { + rows: []string{"cpu_value,host=xyzzy,instance=0,type=cpu,type_instance=user value=63843347 1665212955372077566\n"}, + precision: "ns", + }, + } + for _, d := range data { + row := strings.Join(d.rows, "\n") + totalRows, result := wrapper.TaosSchemalessInsertRaw(conn, row, wrapper.InfluxDBLineProtocol, d.precision) + code := wrapper.TaosError(result) + if code != 0 { + errStr := wrapper.TaosErrorStr(result) + wrapper.TaosFreeResult(result) + t.Log(row) + t.Error(errors.NewError(code, errStr)) + return + } + if int(totalRows) != len(d.rows) { + t.Log(row) + t.Errorf("expect rows %d got %d", len(d.rows), totalRows) + } + affected := wrapper.TaosAffectedRows(result) + if affected != len(d.rows) { + t.Log(row) + t.Errorf("expect affected %d got %d", len(d.rows), affected) + } + wrapper.TaosFreeResult(result) + } +} + +// @author: xftan +// @date: 2023/10/13 11:29 +// @description: test schemaless insert raw with reqid +func TestTaosSchemalessInsertRawWithReqID(t *testing.T) { + conn := prepareEnv() + defer wrapper.TaosClose(conn) + defer cleanEnv(conn) + cases := []struct { + name string + row string + rows int32 + precision string + reqID int64 + }{ + { + name: "1", + row: "measurement,host=host1 field1=2i,field2=2.0 1577836800000000000", + rows: 1, + precision: "", + reqID: 1, + }, + { + name: "2", + row: "measurement,host=host1 field1=2i,field2=2.0 1577836900000000000", + rows: 1, + precision: "ns", + reqID: 2, + }, + { + name: "3", + row: "measurement,host=host1 field1=2i,field2=2.0 1577837000000000", + rows: 1, + precision: "u", + reqID: 3, + }, + { + name: "4", + row: "measurement,host=host1 field1=2i,field2=2.0 1577837100000000", + rows: 1, + precision: "μ", + reqID: 4, + }, + { + name: "5", + row: "measurement,host=host1 field1=2i,field2=2.0 1577837200000\n" + + "measurement,host=host1 field1=2i,field2=2.0 1577837300000", + rows: 2, + precision: "ms", + reqID: 5, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + rows, result := wrapper.TaosSchemalessInsertRawWithReqID(conn, c.row, wrapper.InfluxDBLineProtocol, c.precision, c.reqID) + if rows != c.rows { + t.Fatal("rows miss") + } + code := wrapper.TaosError(result) + if code != 0 { + errStr := wrapper.TaosErrorStr(result) + t.Fatal(errors.NewError(code, errStr)) + } + wrapper.TaosFreeResult(result) + }) + } +} + +// @author: xftan +// @date: 2023/10/13 11:29 +// @description: test schemaless insert with reqid +func TestTaosSchemalessInsertWithReqID(t *testing.T) { + conn := prepareEnv() + defer wrapper.TaosClose(conn) + defer cleanEnv(conn) + cases := []struct { + name string + rows []string + precision string + reqID int64 + }{ + { + name: "1", + rows: []string{"measurement,host=host1 field1=2i,field2=2.0 1577836800000000000"}, + precision: "", + reqID: 1, + }, + { + name: "2", + rows: []string{"measurement,host=host1 field1=2i,field2=2.0 1577836900000000000"}, + precision: "ns", + reqID: 2, + }, + { + name: "3", + rows: []string{"measurement,host=host1 field1=2i,field2=2.0 1577837000000000"}, + precision: "u", + reqID: 3, + }, + { + name: "4", + rows: []string{"measurement,host=host1 field1=2i,field2=2.0 1577837100000000"}, + precision: "μ", + reqID: 4, + }, + { + name: "5", + rows: []string{ + "measurement,host=host1 field1=2i,field2=2.0 1577837200000", + "measurement,host=host1 field1=2i,field2=2.0 1577837300000", + }, + precision: "ms", + reqID: 5, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := wrapper.TaosSchemalessInsertWithReqID(conn, c.rows, wrapper.InfluxDBLineProtocol, c.precision, c.reqID) + code := wrapper.TaosError(result) + if code != 0 { + errStr := wrapper.TaosErrorStr(result) + t.Fatal(errors.NewError(code, errStr)) + } + wrapper.TaosFreeResult(result) + }) + } +} + +// @author: xftan +// @date: 2023/10/13 11:29 +// @description: test schemaless insert with ttl +func TestTaosSchemalessInsertTTL(t *testing.T) { + conn := prepareEnv() + defer wrapper.TaosClose(conn) + defer cleanEnv(conn) + cases := []struct { + name string + rows []string + precision string + ttl int + }{ + { + name: "1", + rows: []string{"measurement,host=host1 field1=2i,field2=2.0 1577836800000000000"}, + precision: "", + ttl: 1000, + }, + { + name: "2", + rows: []string{"measurement,host=host1 field1=2i,field2=2.0 1577836900000000000"}, + precision: "ns", + ttl: 1200, + }, + { + name: "3", + rows: []string{"measurement,host=host1 field1=2i,field2=2.0 1577837100000000"}, + precision: "μ", + ttl: 1400, + }, + { + name: "4", + rows: []string{ + "measurement,host=host1 field1=2i,field2=2.0 1577837200000", + "measurement,host=host1 field1=2i,field2=2.0 1577837300000", + }, + precision: "ms", + ttl: 1600, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := wrapper.TaosSchemalessInsertTTL(conn, c.rows, wrapper.InfluxDBLineProtocol, c.precision, c.ttl) + code := wrapper.TaosError(result) + if code != 0 { + errStr := wrapper.TaosErrorStr(result) + t.Fatal(errors.NewError(code, errStr)) + } + wrapper.TaosFreeResult(result) + }) + } +} + +// @author: xftan +// @date: 2023/10/13 11:30 +// @description: test schemaless insert with ttl and reqid +func TestTaosSchemalessInsertTTLWithReqID(t *testing.T) { + conn := prepareEnv() + defer wrapper.TaosClose(conn) + defer cleanEnv(conn) + cases := []struct { + name string + rows []string + precision string + ttl int + reqId int64 + }{ + { + name: "1", + rows: []string{"measurement,host=host1 field1=2i,field2=2.0 1577836800000000000"}, + precision: "", + ttl: 1000, + reqId: 1, + }, + { + name: "2", + rows: []string{"measurement,host=host1 field1=2i,field2=2.0 1577836900000000000"}, + precision: "ns", + ttl: 1200, + reqId: 2, + }, + { + name: "3", + rows: []string{"measurement,host=host1 field1=2i,field2=2.0 1577837100000000"}, + precision: "μ", + ttl: 1400, + reqId: 3, + }, + { + name: "4", + rows: []string{ + "measurement,host=host1 field1=2i,field2=2.0 1577837200000", + "measurement,host=host1 field1=2i,field2=2.0 1577837300000", + }, + precision: "ms", + ttl: 1600, + reqId: 4, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := wrapper.TaosSchemalessInsertTTLWithReqID(conn, c.rows, wrapper.InfluxDBLineProtocol, c.precision, c.ttl, c.reqId) + code := wrapper.TaosError(result) + if code != 0 { + errStr := wrapper.TaosErrorStr(result) + t.Fatal(errors.NewError(code, errStr)) + } + wrapper.TaosFreeResult(result) + }) + } +} + +// @author: xftan +// @date: 2023/10/13 11:30 +// @description: test schemaless insert raw with ttl +func TestTaosSchemalessInsertRawTTL(t *testing.T) { + conn := prepareEnv() + defer wrapper.TaosClose(conn) + defer cleanEnv(conn) + cases := []struct { + name string + row string + rows int32 + precision string + ttl int + }{ + { + name: "1", + row: "measurement,host=host1 field1=2i,field2=2.0 1577836800000000000", + rows: 1, + precision: "", + ttl: 1000, + }, + { + name: "2", + row: "measurement,host=host1 field1=2i,field2=2.0 1577836900000000000", + rows: 1, + precision: "ns", + ttl: 1200, + }, + { + name: "3", + row: "measurement,host=host1 field1=2i,field2=2.0 1577837200000\n" + + "measurement,host=host1 field1=2i,field2=2.0 1577837300000", + rows: 2, + precision: "ms", + ttl: 1400, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + rows, result := wrapper.TaosSchemalessInsertRawTTL(conn, c.row, wrapper.InfluxDBLineProtocol, c.precision, c.ttl) + if rows != c.rows { + t.Fatal("rows miss") + } + code := wrapper.TaosError(result) + if code != 0 { + errStr := wrapper.TaosErrorStr(result) + t.Fatal(errors.NewError(code, errStr)) + } + wrapper.TaosFreeResult(result) + }) + } +} + +// @author: xftan +// @date: 2023/10/13 11:30 +// @description: test schemaless insert raw with ttl and reqid +func TestTaosSchemalessInsertRawTTLWithReqID(t *testing.T) { + conn := prepareEnv() + defer wrapper.TaosClose(conn) + defer cleanEnv(conn) + cases := []struct { + name string + row string + rows int32 + precision string + ttl int + reqID int64 + }{ + { + name: "1", + row: "measurement,host=host1 field1=2i,field2=2.0 1577836800000000000", + rows: 1, + precision: "", + ttl: 1000, + reqID: 1, + }, + { + name: "2", + row: "measurement,host=host1 field1=2i,field2=2.0 1577836900000000000", + rows: 1, + precision: "ns", + ttl: 1200, + reqID: 2, + }, + { + name: "3", + row: "measurement,host=host1 field1=2i,field2=2.0 1577837200000\n" + + "measurement,host=host1 field1=2i,field2=2.0 1577837300000", + rows: 2, + precision: "ms", + ttl: 1400, + reqID: 3, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + rows, result := wrapper.TaosSchemalessInsertRawTTLWithReqID(conn, c.row, wrapper.InfluxDBLineProtocol, c.precision, c.ttl, c.reqID) + if rows != c.rows { + t.Fatal("rows miss") + } + code := wrapper.TaosError(result) + if code != 0 { + errStr := wrapper.TaosErrorStr(result) + t.Fatal(errors.NewError(code, errStr)) + } + wrapper.TaosFreeResult(result) + }) + } +} + +func TestTaosSchemalessInsertRawTTLWithReqIDTBNameKey(t *testing.T) { + conn := prepareEnv() + defer wrapper.TaosClose(conn) + //defer cleanEnv(conn) + cases := []struct { + name string + row string + rows int32 + precision string + ttl int + reqID int64 + tbNameKey string + }{ + { + name: "1", + row: "measurement,host=host1 field1=2i,field2=1.0 1577836800000000000", + rows: 1, + precision: "", + ttl: 1000, + reqID: 1, + tbNameKey: "host", + }, + { + name: "2", + row: "measurement,host=host1 field1=2i,field2=2.0 1577836900000000000", + rows: 1, + precision: "ns", + ttl: 1200, + reqID: 2, + tbNameKey: "host", + }, + { + name: "3", + row: "measurement,host=host1 field1=2i,field2=3.0 1577837200000\n" + + "measurement,host=host1 field1=2i,field2=4.0 1577837300000", + rows: 2, + precision: "ms", + ttl: 1400, + reqID: 3, + tbNameKey: "host", + }, + { + name: "no table name key", + row: "measurement,host=host1 field1=2i,field2=2.0 1577836900000000000", + rows: 1, + precision: "ns", + ttl: 1200, + reqID: 2, + tbNameKey: "", + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + rows, result := wrapper.TaosSchemalessInsertRawTTLWithReqIDTBNameKey(conn, c.row, wrapper.InfluxDBLineProtocol, c.precision, c.ttl, c.reqID, c.tbNameKey) + if rows != c.rows { + t.Fatal("rows miss") + } + code := wrapper.TaosError(result) + if code != 0 { + errStr := wrapper.TaosErrorStr(result) + t.Fatal(errors.NewError(code, errStr)) + } + wrapper.TaosFreeResult(result) + }) + } +} diff --git a/driver/wrapper/setconfig.go b/driver/wrapper/setconfig.go new file mode 100644 index 00000000..93db562a --- /dev/null +++ b/driver/wrapper/setconfig.go @@ -0,0 +1,42 @@ +package wrapper + +/* +#include +#include +#include +#include +*/ +import "C" +import ( + "strings" + "unsafe" + + "github.com/taosdata/taosadapter/v3/driver/errors" +) + +// TaosSetConfig int taos_set_config(const char *config); +func TaosSetConfig(params map[string]string) error { + if len(params) == 0 { + return nil + } + buf := &strings.Builder{} + for k, v := range params { + buf.WriteString(k) + buf.WriteString(" ") + buf.WriteString(v) + } + cConfig := C.CString(buf.String()) + defer C.free(unsafe.Pointer(cConfig)) + result := (C.struct_setConfRet)(C.taos_set_config(cConfig)) + if int(result.retCode) == -5 || int(result.retCode) == 0 { + return nil + } + buf.Reset() + for _, c := range result.retMsg { + if c == 0 { + break + } + buf.WriteByte(byte(c)) + } + return &errors.TaosError{Code: int32(result.retCode) & 0xffff, ErrStr: buf.String()} +} diff --git a/driver/wrapper/setconfig_test.go b/driver/wrapper/setconfig_test.go new file mode 100644 index 00000000..5589e0dc --- /dev/null +++ b/driver/wrapper/setconfig_test.go @@ -0,0 +1,41 @@ +package wrapper + +import ( + "testing" +) + +// @author: xftan +// @date: 2022/1/27 17:27 +// @description: test taos_set_config +func TestSetConfig(t *testing.T) { + source := map[string]string{ + "numOfThreadsPerCore": "1.000000", + "rpcTimer": "300", + "rpcForceTcp": "0", + "rpcMaxTime": "600", + "compressMsgSize": "-1", + "maxSQLLength": "1048576", + "maxWildCardsLength": "100", + "maxNumOfOrderedRes": "100000", + "keepColumnName": "0", + "timezone": "Asia/Shanghai (CST, +0800)", + "locale": "C.UTF-8", + "charset": "UTF-8", + "numOfLogLines": "10000000", + "asyncLog": "1", + "debugFlag": "135", + "rpcDebugFlag": "131", + "tmrDebugFlag": "131", + "cDebugFlag": "131", + "jniDebugFlag": "131", + "odbcDebugFlag": "131", + "uDebugFlag": "131", + "qDebugFlag": "131", + "maxBinaryDisplayWidth": "30", + "tempDir": "/tmp/", + } + err := TaosSetConfig(source) + if err != nil { + t.Error(err) + } +} diff --git a/driver/wrapper/stmt.go b/driver/wrapper/stmt.go new file mode 100644 index 00000000..734cc33f --- /dev/null +++ b/driver/wrapper/stmt.go @@ -0,0 +1,756 @@ +package wrapper + +/* +#include +#include +#include +#include +*/ +import "C" +import ( + "bytes" + "database/sql/driver" + "errors" + "unsafe" + + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/common/stmt" + taosError "github.com/taosdata/taosadapter/v3/driver/errors" + taosTypes "github.com/taosdata/taosadapter/v3/driver/types" +) + +// TaosStmtInit TAOS_STMT *taos_stmt_init(TAOS *taos); +func TaosStmtInit(taosConnect unsafe.Pointer) unsafe.Pointer { + return C.taos_stmt_init(taosConnect) +} + +// TaosStmtInitWithReqID TAOS_STMT *taos_stmt_init_with_reqid(TAOS *taos, int64_t reqid); +func TaosStmtInitWithReqID(taosConn unsafe.Pointer, reqID int64) unsafe.Pointer { + return C.taos_stmt_init_with_reqid(taosConn, (C.int64_t)(reqID)) +} + +// TaosStmtPrepare int taos_stmt_prepare(TAOS_STMT *stmt, const char *sql, unsigned long length); +func TaosStmtPrepare(stmt unsafe.Pointer, sql string) int { + cSql := C.CString(sql) + cLen := C.ulong(len(sql)) + defer C.free(unsafe.Pointer(cSql)) + return int(C.taos_stmt_prepare(stmt, cSql, cLen)) +} + +//typedef struct TAOS_MULTI_BIND { +//int buffer_type; +//void *buffer; +//int32_t buffer_length; +//int32_t *length; +//char *is_null; +//int num; +//} TAOS_MULTI_BIND; + +// TaosStmtSetTags int taos_stmt_set_tags(TAOS_STMT *stmt, TAOS_MULTI_BIND *tags); +func TaosStmtSetTags(stmt unsafe.Pointer, tags []driver.Value) int { + if len(tags) == 0 { + return int(C.taos_stmt_set_tags(stmt, nil)) + } + binds, needFreePointer, err := generateTaosBindList(tags) + defer func() { + for _, pointer := range needFreePointer { + C.free(pointer) + } + }() + if err != nil { + return -1 + } + result := int(C.taos_stmt_set_tags(stmt, (*C.TAOS_MULTI_BIND)(&binds[0]))) + return result +} + +// TaosStmtSetTBNameTags int taos_stmt_set_tbname_tags(TAOS_STMT* stmt, const char* name, TAOS_MULTI_BIND* tags); +func TaosStmtSetTBNameTags(stmt unsafe.Pointer, name string, tags []driver.Value) int { + cStr := C.CString(name) + defer C.free(unsafe.Pointer(cStr)) + if len(tags) == 0 { + return int(C.taos_stmt_set_tbname_tags(stmt, cStr, nil)) + } + binds, needFreePointer, err := generateTaosBindList(tags) + defer func() { + for _, pointer := range needFreePointer { + C.free(pointer) + } + }() + if err != nil { + return -1 + } + result := int(C.taos_stmt_set_tbname_tags(stmt, cStr, (*C.TAOS_MULTI_BIND)(&binds[0]))) + return result +} + +// TaosStmtSetTBName int taos_stmt_set_tbname(TAOS_STMT* stmt, const char* name); +func TaosStmtSetTBName(stmt unsafe.Pointer, name string) int { + cStr := C.CString(name) + defer C.free(unsafe.Pointer(cStr)) + return int(C.taos_stmt_set_tbname(stmt, cStr)) +} + +// TaosStmtIsInsert int taos_stmt_is_insert(TAOS_STMT *stmt, int *insert); +func TaosStmtIsInsert(stmt unsafe.Pointer) (is bool, errorCode int) { + p := C.malloc(C.size_t(4)) + isInsert := (*C.int)(p) + defer C.free(p) + errorCode = int(C.taos_stmt_is_insert(stmt, isInsert)) + return int(*isInsert) == 1, errorCode +} + +// TaosStmtNumParams int taos_stmt_num_params(TAOS_STMT *stmt, int *nums); +func TaosStmtNumParams(stmt unsafe.Pointer) (count int, errorCode int) { + p := C.malloc(C.size_t(4)) + num := (*C.int)(p) + defer C.free(p) + errorCode = int(C.taos_stmt_num_params(stmt, num)) + return int(*num), errorCode +} + +// TaosStmtBindParam int taos_stmt_bind_param(TAOS_STMT *stmt, TAOS_MULTI_BIND *bind); +func TaosStmtBindParam(stmt unsafe.Pointer, params []driver.Value) int { + if len(params) == 0 { + return int(C.taos_stmt_bind_param(stmt, nil)) + } + binds, needFreePointer, err := generateTaosBindList(params) + defer func() { + for _, pointer := range needFreePointer { + if pointer != nil { + C.free(pointer) + } + } + }() + if err != nil { + return -1 + } + result := int(C.taos_stmt_bind_param(stmt, (*C.TAOS_MULTI_BIND)(unsafe.Pointer(&binds[0])))) + return result +} + +func generateTaosBindList(params []driver.Value) ([]C.TAOS_MULTI_BIND, []unsafe.Pointer, error) { + binds := make([]C.TAOS_MULTI_BIND, len(params)) + var needFreePointer []unsafe.Pointer + for i, param := range params { + bind := C.TAOS_MULTI_BIND{} + bind.num = C.int(1) + if param == nil { + bind.buffer_type = C.TSDB_DATA_TYPE_BOOL + p := C.malloc(1) + *(*C.char)(p) = C.char(1) + needFreePointer = append(needFreePointer, p) + bind.is_null = (*C.char)(p) + } else { + switch value := param.(type) { + case taosTypes.TaosBool: + bind.buffer_type = C.TSDB_DATA_TYPE_BOOL + p := C.malloc(1) + if value { + *(*C.int8_t)(p) = C.int8_t(1) + } else { + *(*C.int8_t)(p) = C.int8_t(0) + } + needFreePointer = append(needFreePointer, p) + bind.buffer = p + bind.buffer_length = C.uintptr_t(1) + case taosTypes.TaosTinyint: + bind.buffer_type = C.TSDB_DATA_TYPE_TINYINT + p := C.malloc(1) + *(*C.int8_t)(p) = C.int8_t(value) + needFreePointer = append(needFreePointer, p) + bind.buffer = p + bind.buffer_length = C.uintptr_t(1) + case taosTypes.TaosSmallint: + bind.buffer_type = C.TSDB_DATA_TYPE_SMALLINT + p := C.malloc(2) + *(*C.int16_t)(p) = C.int16_t(value) + needFreePointer = append(needFreePointer, p) + bind.buffer = p + bind.buffer_length = C.uintptr_t(2) + case taosTypes.TaosInt: + bind.buffer_type = C.TSDB_DATA_TYPE_INT + p := C.malloc(4) + *(*C.int32_t)(p) = C.int32_t(value) + needFreePointer = append(needFreePointer, p) + bind.buffer = p + bind.buffer_length = C.uintptr_t(4) + case taosTypes.TaosBigint: + bind.buffer_type = C.TSDB_DATA_TYPE_BIGINT + p := C.malloc(8) + *(*C.int64_t)(p) = C.int64_t(value) + needFreePointer = append(needFreePointer, p) + bind.buffer = p + bind.buffer_length = C.uintptr_t(8) + case taosTypes.TaosUTinyint: + bind.buffer_type = C.TSDB_DATA_TYPE_UTINYINT + cbuf := C.malloc(1) + *(*C.uint8_t)(cbuf) = C.uint8_t(value) + needFreePointer = append(needFreePointer, cbuf) + bind.buffer = cbuf + bind.buffer_length = C.uintptr_t(1) + case taosTypes.TaosUSmallint: + bind.buffer_type = C.TSDB_DATA_TYPE_USMALLINT + p := C.malloc(2) + *(*C.uint16_t)(p) = C.uint16_t(value) + needFreePointer = append(needFreePointer, p) + bind.buffer = p + bind.buffer_length = C.uintptr_t(2) + case taosTypes.TaosUInt: + bind.buffer_type = C.TSDB_DATA_TYPE_UINT + p := C.malloc(4) + *(*C.uint32_t)(p) = C.uint32_t(value) + needFreePointer = append(needFreePointer, p) + bind.buffer = p + bind.buffer_length = C.uintptr_t(4) + case taosTypes.TaosUBigint: + bind.buffer_type = C.TSDB_DATA_TYPE_UBIGINT + p := C.malloc(8) + *(*C.uint64_t)(p) = C.uint64_t(value) + needFreePointer = append(needFreePointer, p) + bind.buffer = p + bind.buffer_length = C.uintptr_t(8) + case taosTypes.TaosFloat: + bind.buffer_type = C.TSDB_DATA_TYPE_FLOAT + p := C.malloc(4) + *(*C.float)(p) = C.float(value) + needFreePointer = append(needFreePointer, p) + bind.buffer = p + bind.buffer_length = C.uintptr_t(4) + case taosTypes.TaosDouble: + bind.buffer_type = C.TSDB_DATA_TYPE_DOUBLE + p := C.malloc(8) + *(*C.double)(p) = C.double(value) + needFreePointer = append(needFreePointer, p) + bind.buffer = p + bind.buffer_length = C.uintptr_t(8) + case taosTypes.TaosBinary: + bind.buffer_type = C.TSDB_DATA_TYPE_BINARY + cbuf := C.CString(string(value)) + needFreePointer = append(needFreePointer, unsafe.Pointer(cbuf)) + bind.buffer = unsafe.Pointer(cbuf) + clen := int32(len(value)) + p := C.malloc(C.size_t(unsafe.Sizeof(clen))) + bind.length = (*C.int32_t)(p) + *(bind.length) = C.int32_t(clen) + needFreePointer = append(needFreePointer, p) + bind.buffer_length = C.uintptr_t(clen) + case taosTypes.TaosVarBinary: + bind.buffer_type = C.TSDB_DATA_TYPE_VARBINARY + cbuf := C.CString(string(value)) + needFreePointer = append(needFreePointer, unsafe.Pointer(cbuf)) + bind.buffer = unsafe.Pointer(cbuf) + clen := int32(len(value)) + p := C.malloc(C.size_t(unsafe.Sizeof(clen))) + bind.length = (*C.int32_t)(p) + *(bind.length) = C.int32_t(clen) + needFreePointer = append(needFreePointer, p) + bind.buffer_length = C.uintptr_t(clen) + case taosTypes.TaosGeometry: + bind.buffer_type = C.TSDB_DATA_TYPE_GEOMETRY + cbuf := C.CString(string(value)) + needFreePointer = append(needFreePointer, unsafe.Pointer(cbuf)) + bind.buffer = unsafe.Pointer(cbuf) + clen := int32(len(value)) + p := C.malloc(C.size_t(unsafe.Sizeof(clen))) + bind.length = (*C.int32_t)(p) + *(bind.length) = C.int32_t(clen) + needFreePointer = append(needFreePointer, p) + bind.buffer_length = C.uintptr_t(clen) + case taosTypes.TaosNchar: + bind.buffer_type = C.TSDB_DATA_TYPE_NCHAR + p := unsafe.Pointer(C.CString(string(value))) + needFreePointer = append(needFreePointer, p) + bind.buffer = unsafe.Pointer(p) + clen := int32(len(value)) + bind.length = (*C.int32_t)(C.malloc(C.size_t(unsafe.Sizeof(clen)))) + *(bind.length) = C.int32_t(clen) + needFreePointer = append(needFreePointer, unsafe.Pointer(bind.length)) + bind.buffer_length = C.uintptr_t(clen) + case taosTypes.TaosTimestamp: + bind.buffer_type = C.TSDB_DATA_TYPE_TIMESTAMP + ts := common.TimeToTimestamp(value.T, value.Precision) + p := C.malloc(8) + needFreePointer = append(needFreePointer, p) + *(*C.int64_t)(p) = C.int64_t(ts) + bind.buffer = p + bind.buffer_length = C.uintptr_t(8) + case taosTypes.TaosJson: + bind.buffer_type = C.TSDB_DATA_TYPE_JSON + cbuf := C.CString(string(value)) + needFreePointer = append(needFreePointer, unsafe.Pointer(cbuf)) + bind.buffer = unsafe.Pointer(cbuf) + clen := int32(len(value)) + p := C.malloc(C.size_t(unsafe.Sizeof(clen))) + bind.length = (*C.int32_t)(p) + *(bind.length) = C.int32_t(clen) + needFreePointer = append(needFreePointer, p) + bind.buffer_length = C.uintptr_t(clen) + default: + return nil, nil, errors.New("unsupported type") + } + } + binds[i] = bind + } + return binds, needFreePointer, nil +} + +// TaosStmtAddBatch int taos_stmt_add_batch(TAOS_STMT *stmt); +func TaosStmtAddBatch(stmt unsafe.Pointer) int { + return int(C.taos_stmt_add_batch(stmt)) +} + +// TaosStmtExecute int taos_stmt_execute(TAOS_STMT *stmt); +func TaosStmtExecute(stmt unsafe.Pointer) int { + return int(C.taos_stmt_execute(stmt)) +} + +// TaosStmtUseResult TAOS_RES * taos_stmt_use_result(TAOS_STMT *stmt); +func TaosStmtUseResult(stmt unsafe.Pointer) unsafe.Pointer { + return C.taos_stmt_use_result(stmt) +} + +// TaosStmtClose int taos_stmt_close(TAOS_STMT *stmt); +func TaosStmtClose(stmt unsafe.Pointer) int { + return int(C.taos_stmt_close(stmt)) +} + +// TaosStmtSetSubTBName int taos_stmt_set_sub_tbname(TAOS_STMT* stmt, const char* name); +func TaosStmtSetSubTBName(stmt unsafe.Pointer, name string) int { + cStr := C.CString(name) + defer C.free(unsafe.Pointer(cStr)) + return int(C.taos_stmt_set_tbname(stmt, cStr)) +} + +// TaosStmtBindParamBatch int taos_stmt_bind_param_batch(TAOS_STMT* stmt, TAOS_MULTI_BIND* bind); +func TaosStmtBindParamBatch(stmt unsafe.Pointer, multiBind [][]driver.Value, bindType []*taosTypes.ColumnType) int { + var binds = make([]C.TAOS_MULTI_BIND, len(multiBind)) + var needFreePointer []unsafe.Pointer + defer func() { + for _, pointer := range needFreePointer { + C.free(pointer) + } + }() + for columnIndex, columnData := range multiBind { + bind := C.TAOS_MULTI_BIND{} + //malloc + rowLen := len(multiBind[0]) + bind.num = C.int(rowLen) + nullList := unsafe.Pointer(C.malloc(C.size_t(C.uint(rowLen)))) + needFreePointer = append(needFreePointer, nullList) + lengthList := unsafe.Pointer(C.malloc(C.size_t(C.uint(rowLen * 4)))) + needFreePointer = append(needFreePointer, lengthList) + var p unsafe.Pointer + columnType := bindType[columnIndex] + switch columnType.Type { + case taosTypes.TaosBoolType: + //1 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(rowLen)))) + bind.buffer_type = C.TSDB_DATA_TYPE_BOOL + bind.buffer_length = C.uintptr_t(1) + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value := rowData.(taosTypes.TaosBool) + current := unsafe.Pointer(uintptr(p) + uintptr(i)) + if value { + *(*C.int8_t)(current) = C.int8_t(1) + } else { + *(*C.int8_t)(current) = C.int8_t(0) + } + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(1) + } + } + case taosTypes.TaosTinyintType: + //1 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(rowLen)))) + bind.buffer_type = C.TSDB_DATA_TYPE_TINYINT + bind.buffer_length = C.uintptr_t(1) + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value := rowData.(taosTypes.TaosTinyint) + current := unsafe.Pointer(uintptr(p) + uintptr(i)) + *(*C.int8_t)(current) = C.int8_t(value) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(1) + } + } + case taosTypes.TaosSmallintType: + //2 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(2 * rowLen)))) + bind.buffer_type = C.TSDB_DATA_TYPE_SMALLINT + bind.buffer_length = C.uintptr_t(2) + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value := rowData.(taosTypes.TaosSmallint) + current := unsafe.Pointer(uintptr(p) + uintptr(2*i)) + *(*C.int16_t)(current) = C.int16_t(value) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(2) + } + } + case taosTypes.TaosIntType: + //4 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(4 * rowLen)))) + bind.buffer_type = C.TSDB_DATA_TYPE_INT + bind.buffer_length = C.uintptr_t(4) + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value := rowData.(taosTypes.TaosInt) + current := unsafe.Pointer(uintptr(p) + uintptr(4*i)) + *(*C.int32_t)(current) = C.int32_t(value) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(4) + } + } + case taosTypes.TaosBigintType: + //8 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(8 * rowLen)))) + bind.buffer_type = C.TSDB_DATA_TYPE_BIGINT + bind.buffer_length = C.uintptr_t(8) + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value := rowData.(taosTypes.TaosBigint) + current := unsafe.Pointer(uintptr(p) + uintptr(8*i)) + *(*C.int64_t)(current) = C.int64_t(value) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(8) + } + } + case taosTypes.TaosUTinyintType: + //1 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(rowLen)))) + bind.buffer_type = C.TSDB_DATA_TYPE_UTINYINT + bind.buffer_length = C.uintptr_t(1) + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value := rowData.(taosTypes.TaosUTinyint) + current := unsafe.Pointer(uintptr(p) + uintptr(i)) + *(*C.uint8_t)(current) = C.uint8_t(value) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(1) + } + } + case taosTypes.TaosUSmallintType: + //2 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(2 * rowLen)))) + bind.buffer_type = C.TSDB_DATA_TYPE_USMALLINT + bind.buffer_length = C.uintptr_t(2) + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value := rowData.(taosTypes.TaosUSmallint) + current := unsafe.Pointer(uintptr(p) + uintptr(2*i)) + *(*C.uint16_t)(current) = C.uint16_t(value) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(2) + } + } + case taosTypes.TaosUIntType: + //4 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(4 * rowLen)))) + bind.buffer_type = C.TSDB_DATA_TYPE_UINT + bind.buffer_length = C.uintptr_t(4) + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value := rowData.(taosTypes.TaosUInt) + current := unsafe.Pointer(uintptr(p) + uintptr(4*i)) + *(*C.uint32_t)(current) = C.uint32_t(value) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(4) + } + } + case taosTypes.TaosUBigintType: + //8 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(8 * rowLen)))) + bind.buffer_type = C.TSDB_DATA_TYPE_UBIGINT + bind.buffer_length = C.uintptr_t(8) + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value := rowData.(taosTypes.TaosUBigint) + current := unsafe.Pointer(uintptr(p) + uintptr(8*i)) + *(*C.uint64_t)(current) = C.uint64_t(value) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(8) + } + } + case taosTypes.TaosFloatType: + //4 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(4 * rowLen)))) + bind.buffer_type = C.TSDB_DATA_TYPE_FLOAT + bind.buffer_length = C.uintptr_t(4) + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value := rowData.(taosTypes.TaosFloat) + current := unsafe.Pointer(uintptr(p) + uintptr(4*i)) + *(*C.float)(current) = C.float(value) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(4) + } + } + case taosTypes.TaosDoubleType: + //8 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(8 * rowLen)))) + bind.buffer_type = C.TSDB_DATA_TYPE_DOUBLE + bind.buffer_length = C.uintptr_t(8) + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value := rowData.(taosTypes.TaosDouble) + current := unsafe.Pointer(uintptr(p) + uintptr(8*i)) + *(*C.double)(current) = C.double(value) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(8) + } + } + case taosTypes.TaosBinaryType: + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(columnType.MaxLen * rowLen)))) + bind.buffer_type = C.TSDB_DATA_TYPE_BINARY + bind.buffer_length = C.uintptr_t(columnType.MaxLen) + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value := rowData.(taosTypes.TaosBinary) + for j := 0; j < len(value); j++ { + *(*C.char)(unsafe.Pointer(uintptr(p) + uintptr(columnType.MaxLen*i+j))) = (C.char)(value[j]) + } + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(len(value)) + } + } + case taosTypes.TaosVarBinaryType: + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(columnType.MaxLen * rowLen)))) + bind.buffer_type = C.TSDB_DATA_TYPE_VARBINARY + bind.buffer_length = C.uintptr_t(columnType.MaxLen) + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value := rowData.(taosTypes.TaosVarBinary) + for j := 0; j < len(value); j++ { + *(*C.char)(unsafe.Pointer(uintptr(p) + uintptr(columnType.MaxLen*i+j))) = (C.char)(value[j]) + } + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(len(value)) + } + } + case taosTypes.TaosGeometryType: + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(columnType.MaxLen * rowLen)))) + bind.buffer_type = C.TSDB_DATA_TYPE_GEOMETRY + bind.buffer_length = C.uintptr_t(columnType.MaxLen) + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value := rowData.(taosTypes.TaosGeometry) + for j := 0; j < len(value); j++ { + *(*C.char)(unsafe.Pointer(uintptr(p) + uintptr(columnType.MaxLen*i+j))) = (C.char)(value[j]) + } + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(len(value)) + } + } + case taosTypes.TaosNcharType: + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(columnType.MaxLen * rowLen)))) + bind.buffer_type = C.TSDB_DATA_TYPE_NCHAR + bind.buffer_length = C.uintptr_t(columnType.MaxLen) + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value := rowData.(taosTypes.TaosNchar) + for j := 0; j < len(value); j++ { + *(*C.char)(unsafe.Pointer(uintptr(p) + uintptr(columnType.MaxLen*i+j))) = (C.char)(value[j]) + } + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(len(value)) + } + } + case taosTypes.TaosTimestampType: + //8 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(8 * rowLen)))) + bind.buffer_type = C.TSDB_DATA_TYPE_TIMESTAMP + bind.buffer_length = C.uintptr_t(8) + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value := rowData.(taosTypes.TaosTimestamp) + ts := common.TimeToTimestamp(value.T, value.Precision) + current := unsafe.Pointer(uintptr(p) + uintptr(8*i)) + *(*C.int64_t)(current) = C.int64_t(ts) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(8) + } + } + } + needFreePointer = append(needFreePointer, p) + bind.buffer = p + bind.length = (*C.int32_t)(lengthList) + bind.is_null = (*C.char)(nullList) + binds[columnIndex] = bind + } + return int(C.taos_stmt_bind_param_batch(stmt, (*C.TAOS_MULTI_BIND)(&binds[0]))) +} + +// TaosStmtErrStr char *taos_stmt_errstr(TAOS_STMT *stmt); +func TaosStmtErrStr(stmt unsafe.Pointer) string { + return C.GoString(C.taos_stmt_errstr(stmt)) +} + +// TaosStmtAffectedRows int taos_stmt_affected_rows(TAOS_STMT *stmt); +func TaosStmtAffectedRows(stmt unsafe.Pointer) int { + return int(C.taos_stmt_affected_rows(stmt)) +} + +// TaosStmtAffectedRowsOnce int taos_stmt_affected_rows_once(TAOS_STMT *stmt); +func TaosStmtAffectedRowsOnce(stmt unsafe.Pointer) int { + return int(C.taos_stmt_affected_rows_once(stmt)) +} + +//typedef struct TAOS_FIELD_E { +//char name[65]; +//int8_t type; +//uint8_t precision; +//uint8_t scale; +//int32_t bytes; +//} TAOS_FIELD_E; + +// TaosStmtGetTagFields DLL_EXPORT int taos_stmt_get_tag_fields(TAOS_STMT *stmt, int* fieldNum, TAOS_FIELD_E** fields); +func TaosStmtGetTagFields(stmt unsafe.Pointer) (code, num int, fields unsafe.Pointer) { + cNum := unsafe.Pointer(&num) + var cField *C.TAOS_FIELD_E + code = int(C.taos_stmt_get_tag_fields(stmt, (*C.int)(cNum), (**C.TAOS_FIELD_E)(unsafe.Pointer(&cField)))) + if code != 0 { + return code, num, nil + } + if num == 0 { + return code, num, nil + } + return code, num, unsafe.Pointer(cField) +} + +// TaosStmtGetColFields DLL_EXPORT int taos_stmt_get_col_fields(TAOS_STMT *stmt, int* fieldNum, TAOS_FIELD_E** fields); +func TaosStmtGetColFields(stmt unsafe.Pointer) (code, num int, fields unsafe.Pointer) { + cNum := unsafe.Pointer(&num) + var cField *C.TAOS_FIELD_E + code = int(C.taos_stmt_get_col_fields(stmt, (*C.int)(cNum), (**C.TAOS_FIELD_E)(unsafe.Pointer(&cField)))) + if code != 0 { + return code, num, nil + } + if num == 0 { + return code, num, nil + } + return code, num, unsafe.Pointer(cField) +} + +func StmtParseFields(num int, fields unsafe.Pointer) []*stmt.StmtField { + if num == 0 { + return nil + } + if fields == nil { + return nil + } + result := make([]*stmt.StmtField, num) + buf := bytes.NewBufferString("") + for i := 0; i < num; i++ { + r := &stmt.StmtField{} + field := *(*C.TAOS_FIELD_E)(unsafe.Pointer(uintptr(fields) + uintptr(C.sizeof_struct_TAOS_FIELD_E*C.int(i)))) + for _, c := range field.name { + if c == 0 { + break + } + buf.WriteByte(byte(c)) + } + r.Name = buf.String() + buf.Reset() + r.FieldType = int8(field._type) + r.Precision = uint8(field.precision) + r.Scale = uint8(field.scale) + r.Bytes = int32(field.bytes) + result[i] = r + } + return result +} + +// TaosStmtReclaimFields DLL_EXPORT void taos_stmt_reclaim_fields(TAOS_STMT *stmt, TAOS_FIELD_E *fields); +func TaosStmtReclaimFields(stmt unsafe.Pointer, fields unsafe.Pointer) { + C.taos_stmt_reclaim_fields(stmt, (*C.TAOS_FIELD_E)(fields)) +} + +// TaosStmtGetParam DLL_EXPORT int taos_stmt_get_param(TAOS_STMT *stmt, int idx, int *type, int *bytes) +func TaosStmtGetParam(stmt unsafe.Pointer, idx int) (dataType int, dataLength int, err error) { + code := C.taos_stmt_get_param(stmt, C.int(idx), (*C.int)(unsafe.Pointer(&dataType)), (*C.int)(unsafe.Pointer(&dataLength))) + if code != 0 { + err = &taosError.TaosError{ + Code: int32(code), + ErrStr: TaosStmtErrStr(stmt), + } + } + return +} diff --git a/driver/wrapper/stmt2.go b/driver/wrapper/stmt2.go new file mode 100644 index 00000000..09eb1cbb --- /dev/null +++ b/driver/wrapper/stmt2.go @@ -0,0 +1,857 @@ +package wrapper + +/* +#include +#include +#include +#include + +extern void Stmt2ExecCallback(void *param,TAOS_RES *,int code); +//TAOS_STMT2 *taos_stmt2_init(TAOS *taos, TAOS_STMT2_OPTION *option); +TAOS_STMT2 * taos_stmt2_init_wrapper(TAOS *taos, int64_t reqid, bool singleStbInsert,bool singleTableBindOnce, void *param){ + TAOS_STMT2_OPTION option = {reqid, singleStbInsert, singleTableBindOnce, Stmt2ExecCallback , param}; + return taos_stmt2_init(taos,&option); +}; +*/ +import "C" +import ( + "database/sql/driver" + "encoding/binary" + "fmt" + "time" + "unsafe" + + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/common/stmt" + taosError "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" + "github.com/taosdata/taosadapter/v3/tools" +) + +// TaosStmt2Init TAOS_STMT2 *taos_stmt2_init(TAOS *taos, TAOS_STMT2_OPTION *option); +func TaosStmt2Init(taosConnect unsafe.Pointer, reqID int64, singleStbInsert bool, singleTableBindOnce bool, handler cgo.Handle) unsafe.Pointer { + return C.taos_stmt2_init_wrapper(taosConnect, C.int64_t(reqID), C.bool(singleStbInsert), C.bool(singleTableBindOnce), handler.Pointer()) +} + +// TaosStmt2Prepare int taos_stmt2_prepare(TAOS_STMT2 *stmt, const char *sql, unsigned long length); +func TaosStmt2Prepare(stmt unsafe.Pointer, sql string) int { + cSql := C.CString(sql) + cLen := C.ulong(len(sql)) + defer C.free(unsafe.Pointer(cSql)) + return int(C.taos_stmt2_prepare(stmt, cSql, cLen)) +} + +// TaosStmt2BindParam int taos_stmt2_bind_param(TAOS_STMT2 *stmt, TAOS_STMT2_BINDV *bindv, int32_t col_idx); +func TaosStmt2BindParam(stmt unsafe.Pointer, isInsert bool, params []*stmt.TaosStmt2BindData, colTypes, tagTypes []*stmt.StmtField, colIdx int32) error { + count := len(params) + if count == 0 { + return taosError.NewError(0xffff, "params is empty") + } + cBindv := C.TAOS_STMT2_BINDV{} + cBindv.count = C.int(count) + tbNames := unsafe.Pointer(C.malloc(C.size_t(count) * C.size_t(PointerSize))) + needFreePointer := []unsafe.Pointer{tbNames} + defer func() { + for i := len(needFreePointer) - 1; i >= 0; i-- { + if needFreePointer[i] != nil { + C.free(needFreePointer[i]) + } + } + }() + tagList := C.malloc(C.size_t(count) * C.size_t(PointerSize)) + needFreePointer = append(needFreePointer, unsafe.Pointer(tagList)) + colList := C.malloc(C.size_t(count) * C.size_t(PointerSize)) + needFreePointer = append(needFreePointer, unsafe.Pointer(colList)) + var currentTbNameP unsafe.Pointer + var currentTagP unsafe.Pointer + var currentColP unsafe.Pointer + for i, param := range params { + //parse table name + currentTbNameP = tools.AddPointer(tbNames, uintptr(i)*PointerSize) + if param.TableName != "" { + if !isInsert { + return taosError.NewError(0xffff, "table name is not allowed in query statement") + } + tbName := C.CString(param.TableName) + needFreePointer = append(needFreePointer, unsafe.Pointer(tbName)) + *(**C.char)(currentTbNameP) = tbName + } else { + *(**C.char)(currentTbNameP) = nil + } + //parse tags + currentTagP = tools.AddPointer(tagList, uintptr(i)*PointerSize) + if len(param.Tags) > 0 { + if !isInsert { + return taosError.NewError(0xffff, "tag is not allowed in query statement") + } + //transpose + columnFormatTags := make([][]driver.Value, len(param.Tags)) + for j := 0; j < len(param.Tags); j++ { + columnFormatTags[j] = []driver.Value{param.Tags[j]} + } + tags, freePointer, err := generateTaosStmt2BindsInsert(columnFormatTags, tagTypes) + needFreePointer = append(needFreePointer, freePointer...) + if err != nil { + return taosError.NewError(0xffff, fmt.Sprintf("generate tags Bindv struct error: %s", err.Error())) + } + *(**C.TAOS_STMT2_BIND)(currentTagP) = (*C.TAOS_STMT2_BIND)(tags) + } else { + *(**C.TAOS_STMT2_BIND)(currentTagP) = nil + } + // parse cols + currentColP = tools.AddPointer(colList, uintptr(i)*PointerSize) + if len(param.Cols) > 0 { + var err error + var cols unsafe.Pointer + var freePointer []unsafe.Pointer + if isInsert { + cols, freePointer, err = generateTaosStmt2BindsInsert(param.Cols, colTypes) + } else { + cols, freePointer, err = generateTaosStmt2BindsQuery(param.Cols) + } + needFreePointer = append(needFreePointer, freePointer...) + if err != nil { + return taosError.NewError(0xffff, fmt.Sprintf("generate cols Bindv struct error: %s", err.Error())) + } + *(**C.TAOS_STMT2_BIND)(currentColP) = (*C.TAOS_STMT2_BIND)(cols) + } else { + *(**C.TAOS_STMT2_BIND)(currentColP) = nil + } + } + cBindv.bind_cols = (**C.TAOS_STMT2_BIND)(unsafe.Pointer(colList)) + cBindv.tags = (**C.TAOS_STMT2_BIND)(unsafe.Pointer(tagList)) + cBindv.tbnames = (**C.char)(tbNames) + code := int(C.taos_stmt2_bind_param(stmt, &cBindv, C.int32_t(colIdx))) + if code != 0 { + errStr := TaosStmt2Error(stmt) + return taosError.NewError(code, errStr) + } + return nil +} + +func generateTaosStmt2BindsInsert(multiBind [][]driver.Value, fieldTypes []*stmt.StmtField) (unsafe.Pointer, []unsafe.Pointer, error) { + var needFreePointer []unsafe.Pointer + if len(multiBind) != len(fieldTypes) { + return nil, needFreePointer, fmt.Errorf("data and type length not match, data length: %d, type length: %d", len(multiBind), len(fieldTypes)) + } + binds := unsafe.Pointer(C.malloc(C.size_t(C.size_t(len(multiBind)) * C.size_t(unsafe.Sizeof(C.TAOS_STMT2_BIND{}))))) + needFreePointer = append(needFreePointer, binds) + rowLen := len(multiBind[0]) + for columnIndex, columnData := range multiBind { + if len(multiBind[columnIndex]) != rowLen { + return nil, needFreePointer, fmt.Errorf("data length not match, column %d data length: %d, expect: %d", columnIndex, len(multiBind[columnIndex]), rowLen) + } + bind := (*C.TAOS_STMT2_BIND)(unsafe.Pointer(uintptr(binds) + uintptr(columnIndex)*unsafe.Sizeof(C.TAOS_STMT2_BIND{}))) + bind.num = C.int(rowLen) + nullList := unsafe.Pointer(C.malloc(C.size_t(C.uint(rowLen)))) + needFreePointer = append(needFreePointer, nullList) + lengthList := unsafe.Pointer(C.calloc(C.size_t(C.uint(rowLen)), C.size_t(C.uint(4)))) + needFreePointer = append(needFreePointer, lengthList) + var p unsafe.Pointer + columnType := fieldTypes[columnIndex].FieldType + precision := int(fieldTypes[columnIndex].Precision) + switch columnType { + case common.TSDB_DATA_TYPE_BOOL: + //1 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(rowLen)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_BOOL + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value, ok := rowData.(bool) + if !ok { + return nil, needFreePointer, fmt.Errorf("data type error, expect bool, but got %T, value: %v", rowData, value) + } + current := unsafe.Pointer(uintptr(p) + uintptr(i)) + if value { + *(*C.int8_t)(current) = C.int8_t(1) + } else { + *(*C.int8_t)(current) = C.int8_t(0) + } + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(1) + } + } + case common.TSDB_DATA_TYPE_TINYINT: + //1 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(rowLen)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_TINYINT + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value, ok := rowData.(int8) + if !ok { + return nil, needFreePointer, fmt.Errorf("data type error, expect int8, but got %T, value: %v", rowData, value) + } + current := unsafe.Pointer(uintptr(p) + uintptr(i)) + *(*C.int8_t)(current) = C.int8_t(value) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(1) + } + } + case common.TSDB_DATA_TYPE_SMALLINT: + //2 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(2 * rowLen)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_SMALLINT + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value, ok := rowData.(int16) + if !ok { + return nil, needFreePointer, fmt.Errorf("data type error, expect int16, but got %T, value: %v", rowData, value) + } + current := unsafe.Pointer(uintptr(p) + uintptr(2*i)) + *(*C.int16_t)(current) = C.int16_t(value) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(2) + } + } + case common.TSDB_DATA_TYPE_INT: + //4 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(4 * rowLen)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_INT + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value, ok := rowData.(int32) + if !ok { + return nil, needFreePointer, fmt.Errorf("data type error, expect int32, but got %T, value: %v", rowData, value) + } + current := unsafe.Pointer(uintptr(p) + uintptr(4*i)) + *(*C.int32_t)(current) = C.int32_t(value) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(4) + } + } + case common.TSDB_DATA_TYPE_BIGINT: + //8 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(8 * rowLen)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_BIGINT + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value, ok := rowData.(int64) + if !ok { + return nil, needFreePointer, fmt.Errorf("data type error, expect int64, but got %T, value: %v", rowData, value) + } + current := unsafe.Pointer(uintptr(p) + uintptr(8*i)) + *(*C.int64_t)(current) = C.int64_t(value) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(8) + } + } + case common.TSDB_DATA_TYPE_UTINYINT: + //1 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(rowLen)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_UTINYINT + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value, ok := rowData.(uint8) + if !ok { + return nil, needFreePointer, fmt.Errorf("data type error, expect uint8, but got %T, value: %v", rowData, value) + } + current := unsafe.Pointer(uintptr(p) + uintptr(i)) + *(*C.uint8_t)(current) = C.uint8_t(value) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(1) + } + } + case common.TSDB_DATA_TYPE_USMALLINT: + //2 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(2 * rowLen)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_USMALLINT + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value, ok := rowData.(uint16) + if !ok { + return nil, needFreePointer, fmt.Errorf("data type error, expect uint16, but got %T, value: %v", rowData, value) + } + current := unsafe.Pointer(uintptr(p) + uintptr(2*i)) + *(*C.uint16_t)(current) = C.uint16_t(value) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(2) + } + } + case common.TSDB_DATA_TYPE_UINT: + //4 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(4 * rowLen)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_UINT + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value, ok := rowData.(uint32) + if !ok { + return nil, needFreePointer, fmt.Errorf("data type error, expect uint32, but got %T, value: %v", rowData, value) + } + current := unsafe.Pointer(uintptr(p) + uintptr(4*i)) + *(*C.uint32_t)(current) = C.uint32_t(value) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(4) + } + } + case common.TSDB_DATA_TYPE_UBIGINT: + //8 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(8 * rowLen)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_UBIGINT + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value, ok := rowData.(uint64) + if !ok { + return nil, needFreePointer, fmt.Errorf("data type error, expect uint64, but got %T, value: %v", rowData, value) + } + current := unsafe.Pointer(uintptr(p) + uintptr(8*i)) + *(*C.uint64_t)(current) = C.uint64_t(value) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(8) + } + } + case common.TSDB_DATA_TYPE_FLOAT: + //4 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(4 * rowLen)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_FLOAT + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + } else { + *(*C.char)(currentNull) = C.char(0) + value, ok := rowData.(float32) + if !ok { + return nil, needFreePointer, fmt.Errorf("data type error, expect float32, but got %T, value: %v", rowData, value) + } + current := unsafe.Pointer(uintptr(p) + uintptr(4*i)) + *(*C.float)(current) = C.float(value) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(4) + } + } + case common.TSDB_DATA_TYPE_DOUBLE: + //8 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(8 * rowLen)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_DOUBLE + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(0) + } else { + *(*C.char)(currentNull) = C.char(0) + value, ok := rowData.(float64) + if !ok { + return nil, needFreePointer, fmt.Errorf("data type error, expect float64, but got %T, value: %v", rowData, value) + } + current := unsafe.Pointer(uintptr(p) + uintptr(8*i)) + *(*C.double)(current) = C.double(value) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(8) + } + } + case common.TSDB_DATA_TYPE_BINARY, common.TSDB_DATA_TYPE_VARBINARY, common.TSDB_DATA_TYPE_JSON, common.TSDB_DATA_TYPE_GEOMETRY, common.TSDB_DATA_TYPE_NCHAR: + bind.buffer_type = C.int(columnType) + colOffset := make([]int, rowLen) + totalLen := 0 + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + + *(*C.int32_t)(l) = C.int32_t(0) + } else { + colOffset[i] = totalLen + switch value := rowData.(type) { + case string: + totalLen += len(value) + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(len(value)) + case []byte: + totalLen += len(value) + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(len(value)) + default: + return nil, needFreePointer, fmt.Errorf("data type error, expect string or []byte, but got %T, value: %v", rowData, value) + } + *(*C.char)(currentNull) = C.char(0) + } + } + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(totalLen)))) + needFreePointer = append(needFreePointer, p) + for i, rowData := range columnData { + if rowData != nil { + switch value := rowData.(type) { + case string: + x := *(*[]byte)(unsafe.Pointer(&value)) + C.memcpy(unsafe.Pointer(uintptr(p)+uintptr(colOffset[i])), unsafe.Pointer(&x[0]), C.size_t(len(value))) + case []byte: + C.memcpy(unsafe.Pointer(uintptr(p)+uintptr(colOffset[i])), unsafe.Pointer(&value[0]), C.size_t(len(value))) + default: + return nil, needFreePointer, fmt.Errorf("data type error, expect string or []byte, but got %T, value: %v", rowData, value) + } + } + } + case common.TSDB_DATA_TYPE_TIMESTAMP: + //8 + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(8 * rowLen)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_TIMESTAMP + for i, rowData := range columnData { + currentNull := unsafe.Pointer(uintptr(nullList) + uintptr(i)) + if rowData == nil { + *(*C.char)(currentNull) = C.char(1) + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(0) + } else { + *(*C.char)(currentNull) = C.char(0) + var ts int64 + switch value := rowData.(type) { + case time.Time: + ts = common.TimeToTimestamp(value, precision) + case int64: + ts = value + default: + return nil, needFreePointer, fmt.Errorf("data type error, expect time.Time or int64, but got %T, value: %v", rowData, rowData) + } + current := unsafe.Pointer(uintptr(p) + uintptr(8*i)) + *(*C.int64_t)(current) = C.int64_t(ts) + + l := unsafe.Pointer(uintptr(lengthList) + uintptr(4*i)) + *(*C.int32_t)(l) = C.int32_t(8) + } + } + } + bind.buffer = p + bind.length = (*C.int32_t)(lengthList) + bind.is_null = (*C.char)(nullList) + } + + return binds, needFreePointer, nil + +} + +func generateTaosStmt2BindsQuery(multiBind [][]driver.Value) (unsafe.Pointer, []unsafe.Pointer, error) { + var needFreePointer []unsafe.Pointer + binds := unsafe.Pointer(C.malloc(C.size_t(C.size_t(len(multiBind)) * C.size_t(unsafe.Sizeof(C.TAOS_STMT2_BIND{}))))) + needFreePointer = append(needFreePointer, binds) + for columnIndex, columnData := range multiBind { + if len(columnData) != 1 { + return nil, needFreePointer, fmt.Errorf("bind query data length must be 1, but column %d got %d", columnIndex, len(columnData)) + } + bind := (*C.TAOS_STMT2_BIND)(unsafe.Pointer(uintptr(binds) + uintptr(columnIndex)*unsafe.Sizeof(C.TAOS_STMT2_BIND{}))) + data := columnData[0] + bind.num = C.int(1) + nullList := unsafe.Pointer(C.malloc(C.size_t(C.uint(1)))) + needFreePointer = append(needFreePointer, nullList) + var lengthList unsafe.Pointer + var p unsafe.Pointer + if data == nil { + return nil, needFreePointer, fmt.Errorf("bind query data can not be nil") + } + *(*C.char)(nullList) = C.char(0) + + switch rowData := data.(type) { + case bool: + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(1)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_BOOL + if rowData { + *(*C.int8_t)(p) = C.int8_t(1) + } else { + *(*C.int8_t)(p) = C.int8_t(0) + } + + case int8: + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(1)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_TINYINT + *(*C.int8_t)(p) = C.int8_t(rowData) + + case int16: + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(2)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_SMALLINT + *(*C.int16_t)(p) = C.int16_t(rowData) + + case int32: + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(4)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_INT + *(*C.int32_t)(p) = C.int32_t(rowData) + + case int64: + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(8)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_BIGINT + *(*C.int64_t)(p) = C.int64_t(rowData) + + case int: + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(8)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_BIGINT + *(*C.int64_t)(p) = C.int64_t(int64(rowData)) + + case uint8: + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(1)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_UTINYINT + *(*C.uint8_t)(p) = C.uint8_t(rowData) + + case uint16: + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(2)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_USMALLINT + *(*C.uint16_t)(p) = C.uint16_t(rowData) + + case uint32: + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(4)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_UINT + *(*C.uint32_t)(p) = C.uint32_t(rowData) + + case uint64: + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(8)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_UBIGINT + *(*C.uint64_t)(p) = C.uint64_t(rowData) + + case uint: + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(8)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_UBIGINT + *(*C.uint64_t)(p) = C.uint64_t(uint64(rowData)) + + case float32: + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(4)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_FLOAT + *(*C.float)(p) = C.float(rowData) + + case float64: + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(8)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_DOUBLE + *(*C.double)(p) = C.double(rowData) + + case []byte: + valueLength := len(rowData) + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(valueLength)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_BINARY + C.memcpy(p, unsafe.Pointer(&rowData[0]), C.size_t(valueLength)) + lengthList = unsafe.Pointer(C.calloc(C.size_t(C.uint(1)), C.size_t(C.uint(4)))) + needFreePointer = append(needFreePointer, lengthList) + *(*C.int32_t)(lengthList) = C.int32_t(valueLength) + case string: + valueLength := len(rowData) + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(valueLength)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_BINARY + x := *(*[]byte)(unsafe.Pointer(&rowData)) + C.memcpy(p, unsafe.Pointer(&x[0]), C.size_t(valueLength)) + lengthList = unsafe.Pointer(C.calloc(C.size_t(C.uint(1)), C.size_t(C.uint(4)))) + needFreePointer = append(needFreePointer, lengthList) + *(*C.int32_t)(lengthList) = C.int32_t(valueLength) + case time.Time: + buffer := make([]byte, 0, 35) + value := rowData.AppendFormat(buffer, time.RFC3339Nano) + valueLength := len(value) + p = unsafe.Pointer(C.malloc(C.size_t(C.uint(valueLength)))) + needFreePointer = append(needFreePointer, p) + bind.buffer_type = C.TSDB_DATA_TYPE_BINARY + x := *(*[]byte)(unsafe.Pointer(&value)) + C.memcpy(p, unsafe.Pointer(&x[0]), C.size_t(valueLength)) + lengthList = unsafe.Pointer(C.calloc(C.size_t(C.uint(1)), C.size_t(C.uint(4)))) + needFreePointer = append(needFreePointer, lengthList) + *(*C.int32_t)(lengthList) = C.int32_t(valueLength) + default: + return nil, needFreePointer, fmt.Errorf("data type error, expect bool, int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, []byte, string, time.Time, but got %T, value: %v", data, data) + } + bind.buffer = p + bind.length = (*C.int32_t)(lengthList) + bind.is_null = (*C.char)(nullList) + } + return binds, needFreePointer, nil +} + +// TaosStmt2Exec int taos_stmt2_exec(TAOS_STMT2 *stmt, int *affected_rows); +func TaosStmt2Exec(stmt unsafe.Pointer) int { + return int(C.taos_stmt2_exec(stmt, nil)) +} + +// TaosStmt2Close int taos_stmt2_close(TAOS_STMT2 *stmt); +func TaosStmt2Close(stmt unsafe.Pointer) int { + return int(C.taos_stmt2_close(stmt)) +} + +// TaosStmt2IsInsert int taos_stmt2_is_insert(TAOS_STMT2 *stmt, int *insert); +func TaosStmt2IsInsert(stmt unsafe.Pointer) (is bool, errorCode int) { + p := C.malloc(C.size_t(4)) + isInsert := (*C.int)(p) + defer C.free(p) + errorCode = int(C.taos_stmt2_is_insert(stmt, isInsert)) + return int(*isInsert) == 1, errorCode +} + +// TaosStmt2GetFields int taos_stmt2_get_fields(TAOS_STMT2 *stmt, TAOS_FIELD_T field_type, int *count, TAOS_FIELD_E **fields); +func TaosStmt2GetFields(stmt unsafe.Pointer, fieldType int) (code, count int, fields unsafe.Pointer) { + code = int(C.taos_stmt2_get_fields(stmt, C.TAOS_FIELD_T(fieldType), (*C.int)(unsafe.Pointer(&count)), (**C.TAOS_FIELD_E)(unsafe.Pointer(&fields)))) + return +} + +// TaosStmt2FreeFields void taos_stmt2_free_fields(TAOS_STMT2 *stmt, TAOS_FIELD_E *fields); +func TaosStmt2FreeFields(stmt unsafe.Pointer, fields unsafe.Pointer) { + if fields == nil { + return + } + C.taos_stmt2_free_fields(stmt, (*C.TAOS_FIELD_E)(fields)) +} + +// TaosStmt2Error char *taos_stmt2_error(TAOS_STMT2 *stmt) +func TaosStmt2Error(stmt unsafe.Pointer) string { + return C.GoString(C.taos_stmt2_error(stmt)) +} + +func TaosStmt2BindBinary(stmt2 unsafe.Pointer, data []byte, colIdx int32) error { + totalLength := binary.LittleEndian.Uint32(data[stmt.TotalLengthPosition:]) + if totalLength != uint32(len(data)) { + return fmt.Errorf("total length not match, expect %d, but get %d", len(data), totalLength) + } + var freePointer []unsafe.Pointer + defer func() { + for i := len(freePointer) - 1; i >= 0; i-- { + if freePointer[i] != nil { + C.free(freePointer[i]) + } + } + }() + dataP := unsafe.Pointer(C.CBytes(data)) + freePointer = append(freePointer, dataP) + count := binary.LittleEndian.Uint32(data[stmt.CountPosition:]) + tagCount := binary.LittleEndian.Uint32(data[stmt.TagCountPosition:]) + colCount := binary.LittleEndian.Uint32(data[stmt.ColCountPosition:]) + tableNamesOffset := binary.LittleEndian.Uint32(data[stmt.TableNamesOffsetPosition:]) + tagsOffset := binary.LittleEndian.Uint32(data[stmt.TagsOffsetPosition:]) + colsOffset := binary.LittleEndian.Uint32(data[stmt.ColsOffsetPosition:]) + // check table names + if tableNamesOffset > 0 { + tableNameEnd := tableNamesOffset + count*2 + // table name lengths out of range + if tableNameEnd > totalLength { + return fmt.Errorf("table name lengths out of range, total length: %d, tableNamesLengthEnd: %d", totalLength, tableNameEnd) + } + for i := uint32(0); i < count; i++ { + tableNameLength := binary.LittleEndian.Uint16(data[tableNamesOffset+i*2:]) + tableNameEnd += uint32(tableNameLength) + } + if tableNameEnd > totalLength { + return fmt.Errorf("table names out of range, total length: %d, tableNameTotalLength: %d", totalLength, tableNameEnd) + } + } + // check tags + if tagsOffset > 0 { + if tagCount == 0 { + return fmt.Errorf("tag count is zero, but tags offset is not zero") + } + tagsEnd := tagsOffset + count*4 + if tagsEnd > totalLength { + return fmt.Errorf("tags lengths out of range, total length: %d, tagsLengthEnd: %d", totalLength, tagsEnd) + } + for i := uint32(0); i < count; i++ { + tagLength := binary.LittleEndian.Uint32(data[tagsOffset+i*4:]) + if tagLength == 0 { + return fmt.Errorf("tag length is zero, data index: %d", i) + } + tagsEnd += tagLength + } + if tagsEnd > totalLength { + return fmt.Errorf("tags out of range, total length: %d, tagsTotalLength: %d", totalLength, tagsEnd) + } + } + // check cols + if colsOffset > 0 { + if colCount == 0 { + return fmt.Errorf("col count is zero, but cols offset is not zero") + } + colsEnd := colsOffset + count*4 + if colsEnd > totalLength { + return fmt.Errorf("cols lengths out of range, total length: %d, colsLengthEnd: %d", totalLength, colsEnd) + } + for i := uint32(0); i < count; i++ { + colLength := binary.LittleEndian.Uint32(data[colsOffset+i*4:]) + if colLength == 0 { + return fmt.Errorf("col length is zero, data: %d", i) + } + colsEnd += colLength + } + if colsEnd > totalLength { + return fmt.Errorf("cols out of range, total length: %d, colsTotalLength: %d", totalLength, colsEnd) + } + } + cBindv := C.TAOS_STMT2_BINDV{} + cBindv.count = C.int(count) + if tableNamesOffset > 0 { + tableNameLengthP := tools.AddPointer(dataP, uintptr(tableNamesOffset)) + cTableNames := C.malloc(C.size_t(uintptr(count) * PointerSize)) + freePointer = append(freePointer, cTableNames) + tableDataP := tools.AddPointer(tableNameLengthP, uintptr(count)*2) + var tableNamesArrayP unsafe.Pointer + for i := uint32(0); i < count; i++ { + tableNamesArrayP = tools.AddPointer(cTableNames, uintptr(i)*PointerSize) + *(**C.char)(tableNamesArrayP) = (*C.char)(tableDataP) + tableNameLength := *(*uint16)(tools.AddPointer(tableNameLengthP, uintptr(i*2))) + if tableNameLength == 0 { + return fmt.Errorf("table name length is zero, data index: %d", i) + } + tableDataP = tools.AddPointer(tableDataP, uintptr(tableNameLength)) + } + cBindv.tbnames = (**C.char)(cTableNames) + } else { + cBindv.tbnames = nil + } + if tagsOffset > 0 { + tags, needFreePointer, err := generateStmt2Binds(count, tagCount, dataP, tagsOffset) + freePointer = append(freePointer, needFreePointer...) + if err != nil { + return fmt.Errorf("generate tags error: %s", err.Error()) + } + cBindv.tags = (**C.TAOS_STMT2_BIND)(tags) + } else { + cBindv.tags = nil + } + if colsOffset > 0 { + cols, needFreePointer, err := generateStmt2Binds(count, colCount, dataP, colsOffset) + freePointer = append(freePointer, needFreePointer...) + if err != nil { + return fmt.Errorf("generate cols error: %s", err.Error()) + } + cBindv.bind_cols = (**C.TAOS_STMT2_BIND)(cols) + } else { + cBindv.bind_cols = nil + } + code := int(C.taos_stmt2_bind_param(stmt2, &cBindv, C.int32_t(colIdx))) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + return taosError.NewError(code, errStr) + } + return nil +} + +func generateStmt2Binds(count uint32, fieldCount uint32, dataP unsafe.Pointer, fieldsOffset uint32) (unsafe.Pointer, []unsafe.Pointer, error) { + var freePointer []unsafe.Pointer + bindsCList := unsafe.Pointer(C.malloc(C.size_t(uintptr(count) * PointerSize))) + freePointer = append(freePointer, bindsCList) + // dataLength [count]uint32 + // length have checked in TaosStmt2BindBinary + baseLengthPointer := tools.AddPointer(dataP, uintptr(fieldsOffset)) + // dataBuffer + dataPointer := tools.AddPointer(baseLengthPointer, uintptr(count)*4) + var bindsPointer unsafe.Pointer + for tableIndex := uint32(0); tableIndex < count; tableIndex++ { + bindsPointer = tools.AddPointer(bindsCList, uintptr(tableIndex)*PointerSize) + binds := unsafe.Pointer(C.malloc(C.size_t(C.size_t(fieldCount) * C.size_t(unsafe.Sizeof(C.TAOS_STMT2_BIND{}))))) + freePointer = append(freePointer, binds) + var bindDataP unsafe.Pointer + var bindDataTotalLength uint32 + var num int32 + var haveLength byte + var bufferLength uint32 + for fieldIndex := uint32(0); fieldIndex < fieldCount; fieldIndex++ { + // field data + bindDataP = dataPointer + // totalLength + bindDataTotalLength = *(*uint32)(bindDataP) + bindDataP = tools.AddPointer(bindDataP, common.UInt32Size) + bind := (*C.TAOS_STMT2_BIND)(unsafe.Pointer(uintptr(binds) + uintptr(fieldIndex)*unsafe.Sizeof(C.TAOS_STMT2_BIND{}))) + // buffer_type + bind.buffer_type = *(*C.int)(bindDataP) + bindDataP = tools.AddPointer(bindDataP, common.Int32Size) + // num + num = *(*int32)(bindDataP) + bind.num = C.int(num) + bindDataP = tools.AddPointer(bindDataP, common.Int32Size) + // is_null + bind.is_null = (*C.char)(bindDataP) + bindDataP = tools.AddPointer(bindDataP, uintptr(num)) + // haveLength + haveLength = *(*byte)(bindDataP) + bindDataP = tools.AddPointer(bindDataP, common.Int8Size) + if haveLength == 0 { + bind.length = nil + } else { + // length [num]int32 + bind.length = (*C.int32_t)(bindDataP) + bindDataP = tools.AddPointer(bindDataP, common.Int32Size*uintptr(num)) + } + // bufferLength + bufferLength = *(*uint32)(bindDataP) + bindDataP = tools.AddPointer(bindDataP, common.UInt32Size) + // buffer + if bufferLength == 0 { + bind.buffer = nil + } else { + bind.buffer = bindDataP + } + bindDataP = tools.AddPointer(bindDataP, uintptr(bufferLength)) + // check bind data length + bindDataLen := uintptr(bindDataP) - uintptr(dataPointer) + if bindDataLen != uintptr(bindDataTotalLength) { + return nil, freePointer, fmt.Errorf("bind data length not match, expect %d, but get %d, tableIndex:%d", bindDataTotalLength, bindDataLen, tableIndex) + } + dataPointer = bindDataP + } + *(**C.TAOS_STMT2_BIND)(bindsPointer) = (*C.TAOS_STMT2_BIND)(binds) + } + return bindsCList, freePointer, nil +} diff --git a/driver/wrapper/stmt2_test.go b/driver/wrapper/stmt2_test.go new file mode 100644 index 00000000..f92b148f --- /dev/null +++ b/driver/wrapper/stmt2_test.go @@ -0,0 +1,5076 @@ +package wrapper + +import ( + "database/sql/driver" + "fmt" + "testing" + "time" + "unsafe" + + "github.com/stretchr/testify/assert" + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + "github.com/taosdata/taosadapter/v3/driver/common/stmt" + taosError "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" +) + +type stmt2Result struct { + res unsafe.Pointer + affected int + n int +} +type StmtCallBackTest struct { + ExecResult chan *stmt2Result +} + +func (s *StmtCallBackTest) ExecCall(res unsafe.Pointer, affected int, code int) { + s.ExecResult <- &stmt2Result{ + res: res, + affected: affected, + n: code, + } +} + +func NewStmtCallBackTest() *StmtCallBackTest { + return &StmtCallBackTest{ + ExecResult: make(chan *stmt2Result, 1), + } +} + +func TestStmt2BindData(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + err = exec(conn, "drop database if exists test_stmt2") + if err != nil { + t.Error(err) + return + } + }() + err = exec(conn, "create database if not exists test_stmt2 precision 'ms' keep 36500") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "use test_stmt2") + if err != nil { + t.Error(err) + return + } + now := time.Now().Round(time.Millisecond) + next1S := now.Add(time.Second) + next2S := now.Add(2 * time.Second) + + tests := []struct { + name string + tbType string + pos string + params []*stmt.TaosStmt2BindData + expectValue [][]driver.Value + }{ + { + name: "int", + tbType: "ts timestamp, v int", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + {now}, + {int32(1)}, + }, + }}, + expectValue: [][]driver.Value{ + {now, int32(1)}, + }, + }, + { + name: "int null", + tbType: "ts timestamp, v int", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + {now}, + {nil}, + }, + }}, + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "int null 3 cols", + tbType: "ts timestamp, v int", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + int32(1), + nil, + int32(2), + }, + }, + }}, + expectValue: [][]driver.Value{ + {now, int32(1)}, + {next1S, nil}, + {next2S, int32(2)}, + }, + }, + { + name: "bool", + tbType: "ts timestamp, v bool", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{{now}, {true}}, + }}, + + expectValue: [][]driver.Value{{now, true}}, + }, + { + name: "bool false", + tbType: "ts timestamp, v bool", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{{now}, {false}}, + }}, + + expectValue: [][]driver.Value{{now, false}}, + }, + { + name: "bool null", + tbType: "ts timestamp, v bool", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{{now}, {nil}}, + }}, + + expectValue: [][]driver.Value{{now, nil}}, + }, + { + name: "bool null 3 cols", + tbType: "ts timestamp, v bool", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + bool(true), + nil, + bool(false), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, true}, + {next1S, nil}, + {next2S, false}, + }, + }, + { + name: "tinyint", + tbType: "ts timestamp, v tinyint", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{{now}, {int8(1)}}, + }}, + + expectValue: [][]driver.Value{ + {now, int8(1)}, + }, + }, + { + name: "tinyint null", + tbType: "ts timestamp, v tinyint", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, { + nil, + }}, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "tinyint null 3 cols", + tbType: "ts timestamp, v tinyint", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, { + int8(1), + nil, + int8(2), + }}, + }}, + + expectValue: [][]driver.Value{ + {now, int8(1)}, + {next1S, nil}, + {next2S, int8(2)}, + }, + }, + { + name: "smallint", + tbType: "ts timestamp, v smallint", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + int16(1), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, int16(1)}, + }, + }, + { + name: "smallint null", + tbType: "ts timestamp, v smallint", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "smallint null 3 cols", + tbType: "ts timestamp, v smallint", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + int16(1), + nil, + int16(2), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, int16(1)}, + {next1S, nil}, + {next2S, int16(2)}, + }, + }, + { + name: "bigint", + tbType: "ts timestamp, v bigint", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + int64(1), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, int64(1)}, + }, + }, + { + name: "bigint null", + tbType: "ts timestamp, v bigint", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "bigint null 3 cols", + tbType: "ts timestamp, v bigint", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + int64(1), + nil, + int64(2), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, int64(1)}, + {next1S, nil}, + {next2S, int64(2)}, + }, + }, + + { + name: "tinyint unsigned", + tbType: "ts timestamp, v tinyint unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + uint8(1), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, uint8(1)}, + }, + }, + { + name: "tinyint unsigned null", + tbType: "ts timestamp, v tinyint unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "tinyint unsigned null 3 cols", + tbType: "ts timestamp, v tinyint unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + uint8(1), + nil, + uint8(2), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, uint8(1)}, + {next1S, nil}, + {next2S, uint8(2)}, + }, + }, + + { + name: "smallint unsigned", + tbType: "ts timestamp, v smallint unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + uint16(1), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, uint16(1)}, + }, + }, + { + name: "smallint unsigned null", + tbType: "ts timestamp, v smallint unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "smallint unsigned null 3 cols", + tbType: "ts timestamp, v smallint unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + uint16(1), + nil, + uint16(2), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, uint16(1)}, + {next1S, nil}, + {next2S, uint16(2)}, + }, + }, + + { + name: "int unsigned", + tbType: "ts timestamp, v int unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + uint32(1), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, uint32(1)}, + }, + }, + { + name: "int unsigned null", + tbType: "ts timestamp, v int unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "int unsigned null 3 cols", + tbType: "ts timestamp, v int unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + uint32(1), + nil, + uint32(2), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, uint32(1)}, + {next1S, nil}, + {next2S, uint32(2)}, + }, + }, + + { + name: "bigint unsigned", + tbType: "ts timestamp, v bigint unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + uint64(1), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, uint64(1)}, + }, + }, + { + name: "bigint unsigned null", + tbType: "ts timestamp, v bigint unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "bigint unsigned null 3 cols", + tbType: "ts timestamp, v bigint unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + uint64(1), + nil, + uint64(2), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, uint64(1)}, + {next1S, nil}, + {next2S, uint64(2)}, + }, + }, + + { + name: "float", + tbType: "ts timestamp, v float", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + float32(1.2), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, float32(1.2)}, + }, + }, + { + name: "float null", + tbType: "ts timestamp, v float", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "float null 3 cols", + tbType: "ts timestamp, v float", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + float32(1.2), + nil, + float32(2.2), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, float32(1.2)}, + {next1S, nil}, + {next2S, float32(2.2)}, + }, + }, + + { + name: "double", + tbType: "ts timestamp, v double", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + float64(1.2), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, float64(1.2)}, + }, + }, + { + name: "double null", + tbType: "ts timestamp, v double", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "double null 3 cols", + tbType: "ts timestamp, v double", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + float64(1.2), + nil, + float64(2.2), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, float64(1.2)}, + {next1S, nil}, + {next2S, float64(2.2)}, + }, + }, + + { + name: "binary", + tbType: "ts timestamp, v binary(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + []byte("yes"), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, "yes"}, + }, + }, + { + name: "binary null", + tbType: "ts timestamp, v binary(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "binary null 3 cols", + tbType: "ts timestamp, v binary(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + []byte("yes"), + nil, + []byte("中文"), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, "yes"}, + {next1S, nil}, + {next2S, "中文"}, + }, + }, + + { + name: "varbinary", + tbType: "ts timestamp, v varbinary(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + []byte("yes"), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, []byte("yes")}, + }, + }, + { + name: "varbinary null", + tbType: "ts timestamp, v varbinary(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + + { + name: "varbinary null 3 cols", + tbType: "ts timestamp, v varbinary(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + []byte("yes"), + nil, + []byte("中文"), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, []byte("yes")}, + {next1S, nil}, + {next2S, []byte("中文")}, + }, + }, + + { + name: "geometry", + tbType: "ts timestamp, v geometry(100)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}}, + }, + }, + { + name: "geometry null", + tbType: "ts timestamp, v geometry(100)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "geometry null 3 cols", + tbType: "ts timestamp, v geometry(100)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + nil, + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}}, + {next1S, nil}, + {next2S, []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}}, + }, + }, + + { + name: "nchar", + tbType: "ts timestamp, v nchar(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + []byte("yes"), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, "yes"}, + }, + }, + { + name: "nchar null", + tbType: "ts timestamp, v nchar(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "nchar null 3 cols", + tbType: "ts timestamp, v nchar(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + []byte("yes"), + nil, + []byte("中文"), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, "yes"}, + {next1S, nil}, + {next2S, "中文"}, + }, + }, + + { + name: "nchar bind string", + tbType: "ts timestamp, v nchar(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + "yes", + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, "yes"}, + }, + }, + + { + name: "nchar bind string null 3 cols", + tbType: "ts timestamp, v nchar(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + "yes", + nil, + "中文", + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, "yes"}, + {next1S, nil}, + {next2S, "中文"}, + }, + }, + + { + name: "binary bind string", + tbType: "ts timestamp, v binary(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + "yes", + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, "yes"}, + }, + }, + + { + name: "binary bind string null 3 cols", + tbType: "ts timestamp, v binary(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + "yes", + nil, + "中文", + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, "yes"}, + {next1S, nil}, + {next2S, "中文"}, + }, + }, + + { + name: "varbinary bind string", + tbType: "ts timestamp, v varbinary(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + "yes", + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, []byte("yes")}, + }, + }, + + { + name: "varbinary bind string null 3 cols", + tbType: "ts timestamp, v varbinary(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + "yes", + nil, + "中文", + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, []byte("yes")}, + {next1S, nil}, + {next2S, []byte("中文")}, + }, + }, + + { + name: "timestamp", + tbType: "ts timestamp, v timestamp", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + now, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, now}, + }, + }, + } + for i, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tbType := tc.tbType + tbName := fmt.Sprintf("test_fast_insert_%02d", i) + drop := fmt.Sprintf("drop table if exists %s", tbName) + create := fmt.Sprintf("create table if not exists %s(%s)", tbName, tbType) + pos := tc.pos + sql := fmt.Sprintf("insert into %s values(%s)", tbName, pos) + var err error + if err = exec(conn, drop); err != nil { + t.Error(err) + return + } + if err = exec(conn, create); err != nil { + t.Error(err) + return + } + caller := NewStmtCallBackTest() + handler := cgo.NewHandle(caller) + insertStmt := TaosStmt2Init(conn, 0xcc123, false, false, handler) + code := TaosStmt2Prepare(insertStmt, sql) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + isInsert, code := TaosStmt2IsInsert(insertStmt) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + assert.True(t, isInsert) + code, count, cfields := TaosStmt2GetFields(insertStmt, stmt.TAOS_FIELD_COL) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + defer TaosStmt2FreeFields(insertStmt, cfields) + assert.Equal(t, 2, count) + fields := StmtParseFields(count, cfields) + err = TaosStmt2BindParam(insertStmt, true, tc.params, fields, nil, -1) + if err != nil { + t.Error(err) + return + } + code = TaosStmt2Exec(insertStmt) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + r := <-caller.ExecResult + if r.n != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(r.n, errStr) + t.Error(err) + return + } + t.Log(r.affected) + //time.Sleep(time.Second) + code = TaosStmt2Close(insertStmt) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + result, err := query(conn, fmt.Sprintf("select * from %s order by ts asc", tbName)) + if err != nil { + t.Error(err) + return + } + assert.Equal(t, tc.expectValue, result) + }) + } + +} + +func TestStmt2BindBinary(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + err = exec(conn, "drop database if exists test_stmt2_binary") + if err != nil { + t.Error(err) + return + } + }() + err = exec(conn, "create database if not exists test_stmt2_binary precision 'ms' keep 36500") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "use test_stmt2_binary") + if err != nil { + t.Error(err) + return + } + now := time.Now().Round(time.Millisecond) + next1S := now.Add(time.Second) + next2S := now.Add(2 * time.Second) + + tests := []struct { + name string + tbType string + pos string + params []*stmt.TaosStmt2BindData + expectValue [][]driver.Value + }{ + { + name: "int", + tbType: "ts timestamp, v int", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + {now}, + {int32(1)}, + }, + }}, + expectValue: [][]driver.Value{ + {now, int32(1)}, + }, + }, + { + name: "int null", + tbType: "ts timestamp, v int", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + {now}, + {nil}, + }, + }}, + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "int null 3 cols", + tbType: "ts timestamp, v int", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + int32(1), + nil, + int32(2), + }, + }, + }}, + expectValue: [][]driver.Value{ + {now, int32(1)}, + {next1S, nil}, + {next2S, int32(2)}, + }, + }, + { + name: "bool", + tbType: "ts timestamp, v bool", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{{now}, {bool(true)}}, + }}, + + expectValue: [][]driver.Value{{now, true}}, + }, + { + name: "bool null", + tbType: "ts timestamp, v bool", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{{now}, {nil}}, + }}, + + expectValue: [][]driver.Value{{now, nil}}, + }, + { + name: "bool null 3 cols", + tbType: "ts timestamp, v bool", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + bool(true), + nil, + bool(false), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, true}, + {next1S, nil}, + {next2S, false}, + }, + }, + { + name: "tinyint", + tbType: "ts timestamp, v tinyint", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{{now}, {int8(1)}}, + }}, + + expectValue: [][]driver.Value{ + {now, int8(1)}, + }, + }, + { + name: "tinyint null", + tbType: "ts timestamp, v tinyint", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, { + nil, + }}, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "tinyint null 3 cols", + tbType: "ts timestamp, v tinyint", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, { + int8(1), + nil, + int8(2), + }}, + }}, + + expectValue: [][]driver.Value{ + {now, int8(1)}, + {next1S, nil}, + {next2S, int8(2)}, + }, + }, + { + name: "smallint", + tbType: "ts timestamp, v smallint", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + int16(1), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, int16(1)}, + }, + }, + { + name: "smallint null", + tbType: "ts timestamp, v smallint", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "smallint null 3 cols", + tbType: "ts timestamp, v smallint", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + int16(1), + nil, + int16(2), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, int16(1)}, + {next1S, nil}, + {next2S, int16(2)}, + }, + }, + { + name: "bigint", + tbType: "ts timestamp, v bigint", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + int64(1), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, int64(1)}, + }, + }, + { + name: "bigint null", + tbType: "ts timestamp, v bigint", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "bigint null 3 cols", + tbType: "ts timestamp, v bigint", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + int64(1), + nil, + int64(2), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, int64(1)}, + {next1S, nil}, + {next2S, int64(2)}, + }, + }, + + { + name: "tinyint unsigned", + tbType: "ts timestamp, v tinyint unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + uint8(1), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, uint8(1)}, + }, + }, + { + name: "tinyint unsigned null", + tbType: "ts timestamp, v tinyint unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "tinyint unsigned null 3 cols", + tbType: "ts timestamp, v tinyint unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + uint8(1), + nil, + uint8(2), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, uint8(1)}, + {next1S, nil}, + {next2S, uint8(2)}, + }, + }, + + { + name: "smallint unsigned", + tbType: "ts timestamp, v smallint unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + uint16(1), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, uint16(1)}, + }, + }, + { + name: "smallint unsigned null", + tbType: "ts timestamp, v smallint unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "smallint unsigned null 3 cols", + tbType: "ts timestamp, v smallint unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + uint16(1), + nil, + uint16(2), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, uint16(1)}, + {next1S, nil}, + {next2S, uint16(2)}, + }, + }, + + { + name: "int unsigned", + tbType: "ts timestamp, v int unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + uint32(1), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, uint32(1)}, + }, + }, + { + name: "int unsigned null", + tbType: "ts timestamp, v int unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "int unsigned null 3 cols", + tbType: "ts timestamp, v int unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + uint32(1), + nil, + uint32(2), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, uint32(1)}, + {next1S, nil}, + {next2S, uint32(2)}, + }, + }, + + { + name: "bigint unsigned", + tbType: "ts timestamp, v bigint unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + uint64(1), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, uint64(1)}, + }, + }, + { + name: "bigint unsigned null", + tbType: "ts timestamp, v bigint unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "bigint unsigned null 3 cols", + tbType: "ts timestamp, v bigint unsigned", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + uint64(1), + nil, + uint64(2), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, uint64(1)}, + {next1S, nil}, + {next2S, uint64(2)}, + }, + }, + + { + name: "float", + tbType: "ts timestamp, v float", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + float32(1.2), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, float32(1.2)}, + }, + }, + { + name: "float null", + tbType: "ts timestamp, v float", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "float null 3 cols", + tbType: "ts timestamp, v float", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + float32(1.2), + nil, + float32(2.2), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, float32(1.2)}, + {next1S, nil}, + {next2S, float32(2.2)}, + }, + }, + + { + name: "double", + tbType: "ts timestamp, v double", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + float64(1.2), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, float64(1.2)}, + }, + }, + { + name: "double null", + tbType: "ts timestamp, v double", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "double null 3 cols", + tbType: "ts timestamp, v double", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + float64(1.2), + nil, + float64(2.2), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, float64(1.2)}, + {next1S, nil}, + {next2S, float64(2.2)}, + }, + }, + + { + name: "binary", + tbType: "ts timestamp, v binary(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + []byte("yes"), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, "yes"}, + }, + }, + { + name: "binary null", + tbType: "ts timestamp, v binary(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "binary null 3 cols", + tbType: "ts timestamp, v binary(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + []byte("yes"), + nil, + []byte("中文"), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, "yes"}, + {next1S, nil}, + {next2S, "中文"}, + }, + }, + + { + name: "varbinary", + tbType: "ts timestamp, v varbinary(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + []byte("yes"), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, []byte("yes")}, + }, + }, + { + name: "varbinary null", + tbType: "ts timestamp, v varbinary(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + + { + name: "varbinary null 3 cols", + tbType: "ts timestamp, v varbinary(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + []byte("yes"), + nil, + []byte("中文"), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, []byte("yes")}, + {next1S, nil}, + {next2S, []byte("中文")}, + }, + }, + + { + name: "geometry", + tbType: "ts timestamp, v geometry(100)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}}, + }, + }, + { + name: "geometry null", + tbType: "ts timestamp, v geometry(100)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "geometry null 3 cols", + tbType: "ts timestamp, v geometry(100)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + nil, + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}}, + {next1S, nil}, + {next2S, []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}}, + }, + }, + + { + name: "nchar", + tbType: "ts timestamp, v nchar(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + []byte("yes"), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, "yes"}, + }, + }, + { + name: "nchar null", + tbType: "ts timestamp, v nchar(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + nil, + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, nil}, + }, + }, + { + name: "nchar null 3 cols", + tbType: "ts timestamp, v nchar(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + []byte("yes"), + nil, + []byte("中文"), + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, "yes"}, + {next1S, nil}, + {next2S, "中文"}, + }, + }, + + { + name: "nchar bind string", + tbType: "ts timestamp, v nchar(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + "yes", + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, "yes"}, + }, + }, + + { + name: "nchar bind string null 3 cols", + tbType: "ts timestamp, v nchar(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + "yes", + nil, + "中文", + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, "yes"}, + {next1S, nil}, + {next2S, "中文"}, + }, + }, + + { + name: "binary bind string", + tbType: "ts timestamp, v binary(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + "yes", + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, "yes"}, + }, + }, + + { + name: "binary bind string null 3 cols", + tbType: "ts timestamp, v binary(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + "yes", + nil, + "中文", + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, "yes"}, + {next1S, nil}, + {next2S, "中文"}, + }, + }, + + { + name: "varbinary bind string", + tbType: "ts timestamp, v varbinary(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + }, + { + "yes", + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, []byte("yes")}, + }, + }, + + { + name: "varbinary bind string null 3 cols", + tbType: "ts timestamp, v varbinary(20)", + pos: "?, ?", + params: []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + { + now, + next1S, + next2S, + }, + { + "yes", + nil, + "中文", + }, + }, + }}, + + expectValue: [][]driver.Value{ + {now, []byte("yes")}, + {next1S, nil}, + {next2S, []byte("中文")}, + }, + }, + } + for i, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tbType := tc.tbType + tbName := fmt.Sprintf("test_fast_insert_%02d", i) + drop := fmt.Sprintf("drop table if exists %s", tbName) + create := fmt.Sprintf("create table if not exists %s(%s)", tbName, tbType) + pos := tc.pos + sql := fmt.Sprintf("insert into %s values(%s)", tbName, pos) + var err error + if err = exec(conn, drop); err != nil { + t.Error(err) + return + } + if err = exec(conn, create); err != nil { + t.Error(err) + return + } + caller := NewStmtCallBackTest() + handler := cgo.NewHandle(caller) + insertStmt := TaosStmt2Init(conn, 0xcc123, false, false, handler) + code := TaosStmt2Prepare(insertStmt, sql) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + isInsert, code := TaosStmt2IsInsert(insertStmt) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + assert.True(t, isInsert) + code, count, cfields := TaosStmt2GetFields(insertStmt, stmt.TAOS_FIELD_COL) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + defer TaosStmt2FreeFields(insertStmt, cfields) + assert.Equal(t, 2, count) + fields := StmtParseFields(count, cfields) + bs, err := stmt.MarshalStmt2Binary(tc.params, true, fields, nil) + if err != nil { + t.Error("marshal binary error:", err) + return + } + err = TaosStmt2BindBinary(insertStmt, bs, -1) + if !assert.NoError(t, err, bs) { + return + } + //return + code = TaosStmt2Exec(insertStmt) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + r := <-caller.ExecResult + if r.n != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(r.n, errStr) + t.Error(err) + return + } + t.Log(r.affected) + code = TaosStmt2Close(insertStmt) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + result, err := query(conn, fmt.Sprintf("select * from %s order by ts asc", tbName)) + if err != nil { + t.Error(err) + return + } + assert.Equal(t, tc.expectValue, result) + }) + } + +} + +func TestStmt2AllType(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + err = exec(conn, "drop database if exists test_stmt2_all") + if err != nil { + t.Error(err) + return + } + }() + err = exec(conn, "create database if not exists test_stmt2_all precision 'ms' keep 36500") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "use test_stmt2_all") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "create table if not exists all_stb("+ + "ts timestamp, "+ + "v1 bool, "+ + "v2 tinyint, "+ + "v3 smallint, "+ + "v4 int, "+ + "v5 bigint, "+ + "v6 tinyint unsigned, "+ + "v7 smallint unsigned, "+ + "v8 int unsigned, "+ + "v9 bigint unsigned, "+ + "v10 float, "+ + "v11 double, "+ + "v12 binary(20), "+ + "v13 varbinary(20), "+ + "v14 geometry(100), "+ + "v15 nchar(20))"+ + "tags("+ + "tts timestamp, "+ + "tv1 bool, "+ + "tv2 tinyint, "+ + "tv3 smallint, "+ + "tv4 int, "+ + "tv5 bigint, "+ + "tv6 tinyint unsigned, "+ + "tv7 smallint unsigned, "+ + "tv8 int unsigned, "+ + "tv9 bigint unsigned, "+ + "tv10 float, "+ + "tv11 double, "+ + "tv12 binary(20), "+ + "tv13 varbinary(20), "+ + "tv14 geometry(100), "+ + "tv15 nchar(20))") + if err != nil { + t.Error(err) + return + } + caller := NewStmtCallBackTest() + handler := cgo.NewHandle(caller) + insertStmt := TaosStmt2Init(conn, 0xcc123, false, false, handler) + prepareInsertSql := "insert into ? using all_stb tags(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + code := TaosStmt2Prepare(insertStmt, prepareInsertSql) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + params := []*stmt.TaosStmt2BindData{{ + TableName: "ctb1", + }} + err = TaosStmt2BindParam(insertStmt, true, params, nil, nil, -1) + if err != nil { + t.Error(err) + return + } + + code, count, cTablefields := TaosStmt2GetFields(insertStmt, stmt.TAOS_FIELD_TBNAME) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + assert.Equal(t, 1, count) + assert.Equal(t, unsafe.Pointer(nil), cTablefields) + + isInsert, code := TaosStmt2IsInsert(insertStmt) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + assert.True(t, isInsert) + code, count, cColFields := TaosStmt2GetFields(insertStmt, stmt.TAOS_FIELD_COL) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + defer TaosStmt2FreeFields(insertStmt, cColFields) + assert.Equal(t, 16, count) + colFields := StmtParseFields(count, cColFields) + t.Log(colFields) + code, count, cTagfields := TaosStmt2GetFields(insertStmt, stmt.TAOS_FIELD_TAG) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + defer TaosStmt2FreeFields(insertStmt, cTagfields) + assert.Equal(t, 16, count) + tagFields := StmtParseFields(count, cTagfields) + t.Log(tagFields) + now := time.Now() + //colTypes := []int8{ + // common.TSDB_DATA_TYPE_TIMESTAMP, + // common.TSDB_DATA_TYPE_BOOL, + // common.TSDB_DATA_TYPE_TINYINT, + // common.TSDB_DATA_TYPE_SMALLINT, + // common.TSDB_DATA_TYPE_INT, + // common.TSDB_DATA_TYPE_BIGINT, + // common.TSDB_DATA_TYPE_UTINYINT, + // common.TSDB_DATA_TYPE_USMALLINT, + // common.TSDB_DATA_TYPE_UINT, + // common.TSDB_DATA_TYPE_UBIGINT, + // common.TSDB_DATA_TYPE_FLOAT, + // common.TSDB_DATA_TYPE_DOUBLE, + // common.TSDB_DATA_TYPE_BINARY, + // common.TSDB_DATA_TYPE_VARBINARY, + // common.TSDB_DATA_TYPE_GEOMETRY, + // common.TSDB_DATA_TYPE_NCHAR, + //} + params2 := []*stmt.TaosStmt2BindData{{ + TableName: "ctb1", + Tags: []driver.Value{ + // TIMESTAMP + now, + // BOOL + true, + // TINYINT + int8(1), + // SMALLINT + int16(1), + // INT + int32(1), + // BIGINT + int64(1), + // UTINYINT + uint8(1), + // USMALLINT + uint16(1), + // UINT + uint32(1), + // UBIGINT + uint64(1), + // FLOAT + float32(1.2), + // DOUBLE + float64(1.2), + // BINARY + []byte("binary"), + // VARBINARY + []byte("varbinary"), + // GEOMETRY + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + // NCHAR + "nchar", + }, + Cols: [][]driver.Value{ + { + now, + now.Add(time.Second), + now.Add(time.Second * 2), + }, + { + true, + nil, + false, + }, + { + int8(11), + nil, + int8(12), + }, + { + int16(11), + nil, + int16(12), + }, + { + int32(11), + nil, + int32(12), + }, + { + int64(11), + nil, + int64(12), + }, + { + uint8(11), + nil, + uint8(12), + }, + { + uint16(11), + nil, + uint16(12), + }, + { + uint32(11), + nil, + uint32(12), + }, + { + uint64(11), + nil, + uint64(12), + }, + { + float32(11.2), + nil, + float32(12.2), + }, + { + float64(11.2), + nil, + float64(12.2), + }, + { + []byte("binary1"), + nil, + []byte("binary2"), + }, + { + []byte("varbinary1"), + nil, + []byte("varbinary2"), + }, + { + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + nil, + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + }, + { + "nchar1", + nil, + "nchar2", + }, + }, + }} + + err = TaosStmt2BindParam(insertStmt, true, params2, colFields, tagFields, -1) + if err != nil { + t.Error(err) + return + } + code = TaosStmt2Exec(insertStmt) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + r := <-caller.ExecResult + if r.n != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(r.n, errStr) + t.Error(err) + return + } + t.Log(r.affected) + + code = TaosStmt2Close(insertStmt) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } +} + +func TestStmt2AllTypeBytes(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + err = exec(conn, "drop database if exists test_stmt2_all") + if err != nil { + t.Error(err) + return + } + }() + err = exec(conn, "create database if not exists test_stmt2_all_bytes precision 'ms' keep 36500") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "use test_stmt2_all_bytes") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "create table if not exists all_stb("+ + "ts timestamp, "+ + "v1 bool, "+ + "v2 tinyint, "+ + "v3 smallint, "+ + "v4 int, "+ + "v5 bigint, "+ + "v6 tinyint unsigned, "+ + "v7 smallint unsigned, "+ + "v8 int unsigned, "+ + "v9 bigint unsigned, "+ + "v10 float, "+ + "v11 double, "+ + "v12 binary(20), "+ + "v13 varbinary(20), "+ + "v14 geometry(100), "+ + "v15 nchar(20))"+ + "tags("+ + "tts timestamp, "+ + "tv1 bool, "+ + "tv2 tinyint, "+ + "tv3 smallint, "+ + "tv4 int, "+ + "tv5 bigint, "+ + "tv6 tinyint unsigned, "+ + "tv7 smallint unsigned, "+ + "tv8 int unsigned, "+ + "tv9 bigint unsigned, "+ + "tv10 float, "+ + "tv11 double, "+ + "tv12 binary(20), "+ + "tv13 varbinary(20), "+ + "tv14 geometry(100), "+ + "tv15 nchar(20))") + if err != nil { + t.Error(err) + return + } + caller := NewStmtCallBackTest() + handler := cgo.NewHandle(caller) + insertStmt := TaosStmt2Init(conn, 0xcc123, false, false, handler) + prepareInsertSql := "insert into ? using all_stb tags(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + code := TaosStmt2Prepare(insertStmt, prepareInsertSql) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + params := []*stmt.TaosStmt2BindData{{ + TableName: "ctb1", + }} + bs, err := stmt.MarshalStmt2Binary(params, true, nil, nil) + if err != nil { + t.Error(err) + return + } + err = TaosStmt2BindBinary(insertStmt, bs, -1) + if err != nil { + t.Error(err) + return + } + + code, count, cTablefields := TaosStmt2GetFields(insertStmt, stmt.TAOS_FIELD_TBNAME) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + assert.Equal(t, 1, count) + assert.Equal(t, unsafe.Pointer(nil), cTablefields) + + isInsert, code := TaosStmt2IsInsert(insertStmt) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + assert.True(t, isInsert) + code, count, cColFields := TaosStmt2GetFields(insertStmt, stmt.TAOS_FIELD_COL) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + defer TaosStmt2FreeFields(insertStmt, cColFields) + assert.Equal(t, 16, count) + colFields := StmtParseFields(count, cColFields) + t.Log(colFields) + code, count, cTagfields := TaosStmt2GetFields(insertStmt, stmt.TAOS_FIELD_TAG) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + defer TaosStmt2FreeFields(insertStmt, cTagfields) + assert.Equal(t, 16, count) + tagFields := StmtParseFields(count, cTagfields) + t.Log(tagFields) + now := time.Now() + //colTypes := []int8{ + // common.TSDB_DATA_TYPE_TIMESTAMP, + // common.TSDB_DATA_TYPE_BOOL, + // common.TSDB_DATA_TYPE_TINYINT, + // common.TSDB_DATA_TYPE_SMALLINT, + // common.TSDB_DATA_TYPE_INT, + // common.TSDB_DATA_TYPE_BIGINT, + // common.TSDB_DATA_TYPE_UTINYINT, + // common.TSDB_DATA_TYPE_USMALLINT, + // common.TSDB_DATA_TYPE_UINT, + // common.TSDB_DATA_TYPE_UBIGINT, + // common.TSDB_DATA_TYPE_FLOAT, + // common.TSDB_DATA_TYPE_DOUBLE, + // common.TSDB_DATA_TYPE_BINARY, + // common.TSDB_DATA_TYPE_VARBINARY, + // common.TSDB_DATA_TYPE_GEOMETRY, + // common.TSDB_DATA_TYPE_NCHAR, + //} + params2 := []*stmt.TaosStmt2BindData{{ + TableName: "ctb1", + Tags: []driver.Value{ + // TIMESTAMP + now, + // BOOL + true, + // TINYINT + int8(1), + // SMALLINT + int16(1), + // INT + int32(1), + // BIGINT + int64(1), + // UTINYINT + uint8(1), + // USMALLINT + uint16(1), + // UINT + uint32(1), + // UBIGINT + uint64(1), + // FLOAT + float32(1.2), + // DOUBLE + float64(1.2), + // BINARY + []byte("binary"), + // VARBINARY + []byte("varbinary"), + // GEOMETRY + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + // NCHAR + "nchar", + }, + Cols: [][]driver.Value{ + { + now, + now.Add(time.Second), + now.Add(time.Second * 2), + }, + { + true, + nil, + false, + }, + { + int8(11), + nil, + int8(12), + }, + { + int16(11), + nil, + int16(12), + }, + { + int32(11), + nil, + int32(12), + }, + { + int64(11), + nil, + int64(12), + }, + { + uint8(11), + nil, + uint8(12), + }, + { + uint16(11), + nil, + uint16(12), + }, + { + uint32(11), + nil, + uint32(12), + }, + { + uint64(11), + nil, + uint64(12), + }, + { + float32(11.2), + nil, + float32(12.2), + }, + { + float64(11.2), + nil, + float64(12.2), + }, + { + []byte("binary1"), + nil, + []byte("binary2"), + }, + { + []byte("varbinary1"), + nil, + []byte("varbinary2"), + }, + { + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + nil, + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + }, + { + "nchar1", + nil, + "nchar2", + }, + }, + }} + bs, err = stmt.MarshalStmt2Binary(params2, true, colFields, tagFields) + if err != nil { + t.Error(err) + return + } + err = TaosStmt2BindBinary(insertStmt, bs, -1) + if err != nil { + t.Error(err) + return + } + code = TaosStmt2Exec(insertStmt) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + r := <-caller.ExecResult + if r.n != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(r.n, errStr) + t.Error(err) + return + } + t.Log(r.affected) + + code = TaosStmt2Close(insertStmt) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } +} + +func TestStmt2Query(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + err = exec(conn, "drop database if exists test_stmt2_query") + if err != nil { + t.Error(err) + return + } + }() + err = exec(conn, "create database if not exists test_stmt2_query precision 'ms' keep 36500") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "use test_stmt2_query") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "create table if not exists t(ts timestamp,v int)") + if err != nil { + t.Error(err) + return + } + caller := NewStmtCallBackTest() + handler := cgo.NewHandle(caller) + stmt2 := TaosStmt2Init(conn, 0xcc123, false, false, handler) + prepareInsertSql := "insert into t values (?,?)" + code := TaosStmt2Prepare(stmt2, prepareInsertSql) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + isInsert, code := TaosStmt2IsInsert(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + assert.True(t, isInsert) + now := time.Now().Round(time.Millisecond) + colTypes := []*stmt.StmtField{ + { + FieldType: common.TSDB_DATA_TYPE_TIMESTAMP, + Precision: common.PrecisionMilliSecond, + }, + { + FieldType: common.TSDB_DATA_TYPE_INT, + }, + } + params := []*stmt.TaosStmt2BindData{ + { + TableName: "t", + Cols: [][]driver.Value{ + { + now, + now.Add(time.Second), + }, + { + int32(1), + int32(2), + }, + }, + }, + { + TableName: "t", + Cols: [][]driver.Value{ + { + now.Add(time.Second * 2), + now.Add(time.Second * 3), + }, + { + int32(3), + int32(4), + }, + }, + }, + } + err = TaosStmt2BindParam(stmt2, true, params, colTypes, nil, -1) + if err != nil { + t.Error(err) + return + } + code = TaosStmt2Exec(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + r := <-caller.ExecResult + if r.n != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(r.n, errStr) + t.Error(err) + return + } + assert.Equal(t, 4, r.affected) + code = TaosStmt2Prepare(stmt2, "select * from t where ts >= ? and ts <= ?") + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + isInsert, code = TaosStmt2IsInsert(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + assert.False(t, isInsert) + params = []*stmt.TaosStmt2BindData{ + { + Cols: [][]driver.Value{ + { + now, + }, + { + now.Add(time.Second * 3), + }, + }, + }, + } + + err = TaosStmt2BindParam(stmt2, false, params, nil, nil, -1) + if err != nil { + t.Error(err) + return + } + code = TaosStmt2Exec(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + r = <-caller.ExecResult + if r.n != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(r.n, errStr) + t.Error(err) + return + } + res := r.res + fileCount := TaosNumFields(res) + rh, err := ReadColumn(res, fileCount) + if err != nil { + t.Error(err) + return + } + precision := TaosResultPrecision(res) + var result [][]driver.Value + for { + columns, errCode, block := TaosFetchRawBlock(res) + if errCode != 0 { + errStr := TaosErrorStr(res) + err = taosError.NewError(errCode, errStr) + t.Error(err) + return + } + if columns == 0 { + break + } + r := parser.ReadBlock(block, columns, rh.ColTypes, precision) + result = append(result, r...) + } + assert.Equal(t, 4, len(result)) + assert.Equal(t, now, result[0][0]) + assert.Equal(t, now.Add(time.Second), result[1][0]) + assert.Equal(t, now.Add(time.Second*2), result[2][0]) + assert.Equal(t, now.Add(time.Second*3), result[3][0]) + assert.Equal(t, int32(1), result[0][1]) + assert.Equal(t, int32(2), result[1][1]) + assert.Equal(t, int32(3), result[2][1]) + assert.Equal(t, int32(4), result[3][1]) + code = TaosStmt2Close(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } +} + +func TestStmt2QueryBytes(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + err = exec(conn, "drop database if exists test_stmt2_query_bytes") + if err != nil { + t.Error(err) + return + } + }() + err = exec(conn, "create database if not exists test_stmt2_query_bytes precision 'ms' keep 36500") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "use test_stmt2_query_bytes") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "create table if not exists t(ts timestamp,v int)") + if err != nil { + t.Error(err) + return + } + caller := NewStmtCallBackTest() + handler := cgo.NewHandle(caller) + stmt2 := TaosStmt2Init(conn, 0xcc123, false, false, handler) + prepareInsertSql := "insert into t values (?,?)" + code := TaosStmt2Prepare(stmt2, prepareInsertSql) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + isInsert, code := TaosStmt2IsInsert(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + assert.True(t, isInsert) + now := time.Now().Round(time.Millisecond) + colTypes := []*stmt.StmtField{ + { + FieldType: common.TSDB_DATA_TYPE_TIMESTAMP, + Precision: common.PrecisionMilliSecond, + }, + { + FieldType: common.TSDB_DATA_TYPE_INT, + }, + } + params := []*stmt.TaosStmt2BindData{ + { + TableName: "t", + Cols: [][]driver.Value{ + { + now, + now.Add(time.Second), + }, + { + int32(1), + int32(2), + }, + }, + }, + { + TableName: "t", + Cols: [][]driver.Value{ + { + now.Add(time.Second * 2), + now.Add(time.Second * 3), + }, + { + int32(3), + int32(4), + }, + }, + }, + } + bs, err := stmt.MarshalStmt2Binary(params, true, colTypes, nil) + if err != nil { + t.Error(err) + return + } + err = TaosStmt2BindBinary(stmt2, bs, -1) + if err != nil { + t.Error(err) + return + } + code = TaosStmt2Exec(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + r := <-caller.ExecResult + if r.n != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(r.n, errStr) + t.Error(err) + return + } + assert.Equal(t, 4, r.affected) + code = TaosStmt2Prepare(stmt2, "select * from t where ts >= ? and ts <= ?") + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + isInsert, code = TaosStmt2IsInsert(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + assert.False(t, isInsert) + params = []*stmt.TaosStmt2BindData{ + { + Cols: [][]driver.Value{ + { + now, + }, + { + now.Add(time.Second * 3), + }, + }, + }, + } + bs, err = stmt.MarshalStmt2Binary(params, false, nil, nil) + if err != nil { + t.Error(err) + return + } + err = TaosStmt2BindBinary(stmt2, bs, -1) + if err != nil { + t.Error(err) + return + } + code = TaosStmt2Exec(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + r = <-caller.ExecResult + if r.n != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(r.n, errStr) + t.Error(err) + return + } + res := r.res + fileCount := TaosNumFields(res) + rh, err := ReadColumn(res, fileCount) + if err != nil { + t.Error(err) + return + } + precision := TaosResultPrecision(res) + var result [][]driver.Value + for { + columns, errCode, block := TaosFetchRawBlock(res) + if errCode != 0 { + errStr := TaosErrorStr(res) + err = taosError.NewError(errCode, errStr) + t.Error(err) + return + } + if columns == 0 { + break + } + r := parser.ReadBlock(block, columns, rh.ColTypes, precision) + result = append(result, r...) + } + assert.Equal(t, 4, len(result)) + assert.Equal(t, now, result[0][0]) + assert.Equal(t, now.Add(time.Second), result[1][0]) + assert.Equal(t, now.Add(time.Second*2), result[2][0]) + assert.Equal(t, now.Add(time.Second*3), result[3][0]) + assert.Equal(t, int32(1), result[0][1]) + assert.Equal(t, int32(2), result[1][1]) + assert.Equal(t, int32(3), result[2][1]) + assert.Equal(t, int32(4), result[3][1]) + code = TaosStmt2Close(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } +} + +func TestStmt2QueryAllType(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + err = exec(conn, "drop database if exists test_stmt2_query_all") + if err != nil { + t.Error(err) + return + } + }() + err = exec(conn, "create database if not exists test_stmt2_query_all precision 'ms' keep 36500") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "use test_stmt2_query_all") + if err != nil { + t.Error(err) + return + } + + err = exec(conn, "create table if not exists t("+ + "ts timestamp, "+ + "v1 bool, "+ + "v2 tinyint, "+ + "v3 smallint, "+ + "v4 int, "+ + "v5 bigint, "+ + "v6 tinyint unsigned, "+ + "v7 smallint unsigned, "+ + "v8 int unsigned, "+ + "v9 bigint unsigned, "+ + "v10 float, "+ + "v11 double, "+ + "v12 binary(20), "+ + "v13 varbinary(20), "+ + "v14 geometry(100), "+ + "v15 nchar(20))") + if err != nil { + t.Error(err) + return + } + caller := NewStmtCallBackTest() + handler := cgo.NewHandle(caller) + stmt2 := TaosStmt2Init(conn, 0xcc123, false, false, handler) + prepareInsertSql := "insert into t values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + colTypes := []*stmt.StmtField{ + {FieldType: common.TSDB_DATA_TYPE_TIMESTAMP, Precision: common.PrecisionMilliSecond}, + {FieldType: common.TSDB_DATA_TYPE_BOOL}, + {FieldType: common.TSDB_DATA_TYPE_TINYINT}, + {FieldType: common.TSDB_DATA_TYPE_SMALLINT}, + {FieldType: common.TSDB_DATA_TYPE_INT}, + {FieldType: common.TSDB_DATA_TYPE_BIGINT}, + {FieldType: common.TSDB_DATA_TYPE_UTINYINT}, + {FieldType: common.TSDB_DATA_TYPE_USMALLINT}, + {FieldType: common.TSDB_DATA_TYPE_UINT}, + {FieldType: common.TSDB_DATA_TYPE_UBIGINT}, + {FieldType: common.TSDB_DATA_TYPE_FLOAT}, + {FieldType: common.TSDB_DATA_TYPE_DOUBLE}, + {FieldType: common.TSDB_DATA_TYPE_BINARY}, + {FieldType: common.TSDB_DATA_TYPE_VARBINARY}, + {FieldType: common.TSDB_DATA_TYPE_GEOMETRY}, + {FieldType: common.TSDB_DATA_TYPE_NCHAR}, + } + + now := time.Now() + params2 := []*stmt.TaosStmt2BindData{{ + TableName: "t", + Cols: [][]driver.Value{ + { + now, + now.Add(time.Second), + now.Add(time.Second * 2), + }, + { + true, + nil, + false, + }, + { + int8(11), + nil, + int8(12), + }, + { + int16(11), + nil, + int16(12), + }, + { + int32(11), + nil, + int32(12), + }, + { + int64(11), + nil, + int64(12), + }, + { + uint8(11), + nil, + uint8(12), + }, + { + uint16(11), + nil, + uint16(12), + }, + { + uint32(11), + nil, + uint32(12), + }, + { + uint64(11), + nil, + uint64(12), + }, + { + float32(11.2), + nil, + float32(12.2), + }, + { + float64(11.2), + nil, + float64(12.2), + }, + { + []byte("binary1"), + nil, + []byte("binary2"), + }, + { + []byte("varbinary1"), + nil, + []byte("varbinary2"), + }, + { + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + nil, + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + }, + { + "nchar1", + nil, + "nchar2", + }, + }, + }} + code := TaosStmt2Prepare(stmt2, prepareInsertSql) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + isInsert, code := TaosStmt2IsInsert(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + assert.True(t, isInsert) + err = TaosStmt2BindParam(stmt2, true, params2, colTypes, nil, -1) + if err != nil { + t.Error(err) + return + } + code = TaosStmt2Exec(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + r := <-caller.ExecResult + if r.n != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(r.n, errStr) + t.Error(err) + return + } + t.Log(r.affected) + assert.Equal(t, 3, r.affected) + code = TaosStmt2Prepare(stmt2, "select * from t where ts =? and v1 = ? and v2 = ? and v3 = ? and v4 = ? and v5 = ? and v6 = ? and v7 = ? and v8 = ? and v9 = ? and v10 = ? and v11 = ? and v12 = ? and v13 = ? and v14 = ? and v15 = ? ") + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + isInsert, code = TaosStmt2IsInsert(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + assert.False(t, isInsert) + params := []*stmt.TaosStmt2BindData{ + { + Cols: [][]driver.Value{ + {now}, + {true}, + {int8(11)}, + {int16(11)}, + {int32(11)}, + {int64(11)}, + {uint8(11)}, + {uint16(11)}, + {uint32(11)}, + {uint64(11)}, + {float32(11.2)}, + {float64(11.2)}, + {[]byte("binary1")}, + {[]byte("varbinary1")}, + {"point(100 100)"}, + {"nchar1"}, + }, + }, + } + err = TaosStmt2BindParam(stmt2, false, params, nil, nil, -1) + if err != nil { + t.Error(err) + return + } + code = TaosStmt2Exec(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + r = <-caller.ExecResult + if r.n != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(r.n, errStr) + t.Error(err) + return + } + res := r.res + fileCount := TaosNumFields(res) + rh, err := ReadColumn(res, fileCount) + if err != nil { + t.Error(err) + return + } + precision := TaosResultPrecision(res) + var result [][]driver.Value + for { + columns, errCode, block := TaosFetchRawBlock(res) + if errCode != 0 { + errStr := TaosErrorStr(res) + err = taosError.NewError(errCode, errStr) + t.Error(err) + return + } + if columns == 0 { + break + } + r := parser.ReadBlock(block, columns, rh.ColTypes, precision) + result = append(result, r...) + } + t.Log(result) + assert.Len(t, result, 1) +} + +func TestStmt2QueryAllTypeBytes(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + err = exec(conn, "drop database if exists test_stmt2_query_all_bytes") + if err != nil { + t.Error(err) + return + } + }() + err = exec(conn, "create database if not exists test_stmt2_query_all_bytes precision 'ms' keep 36500") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "use test_stmt2_query_all_bytes") + if err != nil { + t.Error(err) + return + } + + err = exec(conn, "create table if not exists t("+ + "ts timestamp, "+ + "v1 bool, "+ + "v2 tinyint, "+ + "v3 smallint, "+ + "v4 int, "+ + "v5 bigint, "+ + "v6 tinyint unsigned, "+ + "v7 smallint unsigned, "+ + "v8 int unsigned, "+ + "v9 bigint unsigned, "+ + "v10 float, "+ + "v11 double, "+ + "v12 binary(20), "+ + "v13 varbinary(20), "+ + "v14 geometry(100), "+ + "v15 nchar(20))") + if err != nil { + t.Error(err) + return + } + caller := NewStmtCallBackTest() + handler := cgo.NewHandle(caller) + stmt2 := TaosStmt2Init(conn, 0xcc123, false, false, handler) + prepareInsertSql := "insert into t values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + colTypes := []*stmt.StmtField{ + {FieldType: common.TSDB_DATA_TYPE_TIMESTAMP, Precision: common.PrecisionMilliSecond}, + {FieldType: common.TSDB_DATA_TYPE_BOOL}, + {FieldType: common.TSDB_DATA_TYPE_TINYINT}, + {FieldType: common.TSDB_DATA_TYPE_SMALLINT}, + {FieldType: common.TSDB_DATA_TYPE_INT}, + {FieldType: common.TSDB_DATA_TYPE_BIGINT}, + {FieldType: common.TSDB_DATA_TYPE_UTINYINT}, + {FieldType: common.TSDB_DATA_TYPE_USMALLINT}, + {FieldType: common.TSDB_DATA_TYPE_UINT}, + {FieldType: common.TSDB_DATA_TYPE_UBIGINT}, + {FieldType: common.TSDB_DATA_TYPE_FLOAT}, + {FieldType: common.TSDB_DATA_TYPE_DOUBLE}, + {FieldType: common.TSDB_DATA_TYPE_BINARY}, + {FieldType: common.TSDB_DATA_TYPE_VARBINARY}, + {FieldType: common.TSDB_DATA_TYPE_GEOMETRY}, + {FieldType: common.TSDB_DATA_TYPE_NCHAR}, + } + + now := time.Now() + params2 := []*stmt.TaosStmt2BindData{{ + TableName: "t", + Cols: [][]driver.Value{ + { + now, + now.Add(time.Second), + now.Add(time.Second * 2), + }, + { + true, + nil, + false, + }, + { + int8(11), + nil, + int8(12), + }, + { + int16(11), + nil, + int16(12), + }, + { + int32(11), + nil, + int32(12), + }, + { + int64(11), + nil, + int64(12), + }, + { + uint8(11), + nil, + uint8(12), + }, + { + uint16(11), + nil, + uint16(12), + }, + { + uint32(11), + nil, + uint32(12), + }, + { + uint64(11), + nil, + uint64(12), + }, + { + float32(11.2), + nil, + float32(12.2), + }, + { + float64(11.2), + nil, + float64(12.2), + }, + { + []byte("binary1"), + nil, + []byte("binary2"), + }, + { + []byte("varbinary1"), + nil, + []byte("varbinary2"), + }, + { + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + nil, + []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + }, + { + "nchar1", + nil, + "nchar2", + }, + }, + }} + code := TaosStmt2Prepare(stmt2, prepareInsertSql) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + isInsert, code := TaosStmt2IsInsert(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + assert.True(t, isInsert) + bs, err := stmt.MarshalStmt2Binary(params2, true, colTypes, nil) + if err != nil { + t.Error(err) + return + } + err = TaosStmt2BindBinary(stmt2, bs, -1) + if err != nil { + t.Error(err) + return + } + code = TaosStmt2Exec(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + r := <-caller.ExecResult + if r.n != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(r.n, errStr) + t.Error(err) + return + } + t.Log(r.affected) + assert.Equal(t, 3, r.affected) + code = TaosStmt2Prepare(stmt2, "select * from t where ts =? and v1 = ? and v2 = ? and v3 = ? and v4 = ? and v5 = ? and v6 = ? and v7 = ? and v8 = ? and v9 = ? and v10 = ? and v11 = ? and v12 = ? and v13 = ? and v14 = ? and v15 = ? ") + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + isInsert, code = TaosStmt2IsInsert(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + assert.False(t, isInsert) + params := []*stmt.TaosStmt2BindData{ + { + Cols: [][]driver.Value{ + {now}, + {true}, + {int8(11)}, + {int16(11)}, + {int32(11)}, + {int64(11)}, + {uint8(11)}, + {uint16(11)}, + {uint32(11)}, + {uint64(11)}, + {float32(11.2)}, + {float64(11.2)}, + {[]byte("binary1")}, + {[]byte("varbinary1")}, + {"point(100 100)"}, + {"nchar1"}, + }, + }, + } + bs, err = stmt.MarshalStmt2Binary(params, false, nil, nil) + if err != nil { + t.Error(err) + return + } + err = TaosStmt2BindBinary(stmt2, bs, -1) + if err != nil { + t.Error(err) + return + } + code = TaosStmt2Exec(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + r = <-caller.ExecResult + if r.n != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(r.n, errStr) + t.Error(err) + return + } + res := r.res + fileCount := TaosNumFields(res) + rh, err := ReadColumn(res, fileCount) + if err != nil { + t.Error(err) + return + } + precision := TaosResultPrecision(res) + var result [][]driver.Value + for { + columns, errCode, block := TaosFetchRawBlock(res) + if errCode != 0 { + errStr := TaosErrorStr(res) + err = taosError.NewError(errCode, errStr) + t.Error(err) + return + } + if columns == 0 { + break + } + r := parser.ReadBlock(block, columns, rh.ColTypes, precision) + result = append(result, r...) + } + t.Log(result) + assert.Len(t, result, 1) +} + +func TestStmt2Json(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + err = exec(conn, "drop database if exists test_stmt2_json") + if err != nil { + t.Error(err) + return + } + }() + err = exec(conn, "create database if not exists test_stmt2_json precision 'ms' keep 36500") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "use test_stmt2_json") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "create table if not exists test_json_stb(ts timestamp, v int) tags (t json)") + if err != nil { + t.Error(err) + return + } + caller := NewStmtCallBackTest() + handler := cgo.NewHandle(caller) + stmt2 := TaosStmt2Init(conn, 0xcc123, false, false, handler) + defer func() { + code := TaosStmt2Close(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + }() + prepareInsertSql := "insert into ? using test_json_stb tags(?) values (?,?)" + code := TaosStmt2Prepare(stmt2, prepareInsertSql) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + now := time.Now().Round(time.Millisecond) + params := []*stmt.TaosStmt2BindData{{ + TableName: "ctb1", + Tags: []driver.Value{[]byte(`{"a":1,"b":"xx"}`)}, + Cols: [][]driver.Value{ + {now}, + {int32(1)}, + }, + }} + colTypes := []*stmt.StmtField{ + {FieldType: common.TSDB_DATA_TYPE_TIMESTAMP, Precision: common.PrecisionMilliSecond}, + {FieldType: common.TSDB_DATA_TYPE_INT}, + } + tagTypes := []*stmt.StmtField{ + {FieldType: common.TSDB_DATA_TYPE_JSON}, + } + err = TaosStmt2BindParam(stmt2, true, params, colTypes, tagTypes, -1) + if err != nil { + t.Error(err) + return + } + code = TaosStmt2Exec(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + r := <-caller.ExecResult + if r.n != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(r.n, errStr) + t.Error(err) + return + } + assert.Equal(t, 1, r.affected) + + TaosStmt2Prepare(stmt2, "select * from test_json_stb where t->'a' = ?") + params = []*stmt.TaosStmt2BindData{{ + Cols: [][]driver.Value{ + {int32(1)}, + }, + }} + err = TaosStmt2BindParam(stmt2, false, params, nil, nil, -1) + if err != nil { + t.Error(err) + return + } + code = TaosStmt2Exec(stmt2) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + r = <-caller.ExecResult + if r.n != 0 { + errStr := TaosStmt2Error(stmt2) + err = taosError.NewError(r.n, errStr) + t.Error(err) + return + } + res := r.res + fileCount := TaosNumFields(res) + rh, err := ReadColumn(res, fileCount) + if err != nil { + t.Error(err) + return + } + precision := TaosResultPrecision(res) + var result [][]driver.Value + for { + columns, errCode, block := TaosFetchRawBlock(res) + if errCode != 0 { + errStr := TaosErrorStr(res) + err = taosError.NewError(errCode, errStr) + t.Error(err) + return + } + if columns == 0 { + break + } + r := parser.ReadBlock(block, columns, rh.ColTypes, precision) + result = append(result, r...) + } + t.Log(result) + assert.Equal(t, 1, len(result)) +} + +func TestStmt2BindMultiTables(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + err = exec(conn, "drop database if exists test_stmt2_multi") + if err != nil { + t.Error(err) + return + } + }() + err = exec(conn, "create database if not exists test_stmt2_multi precision 'ms' keep 36500") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "use test_stmt2_multi") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "create table if not exists stb(ts timestamp, v bigint) tags(tv int)") + if err != nil { + t.Error(err) + return + } + caller := NewStmtCallBackTest() + handler := cgo.NewHandle(caller) + insertStmt := TaosStmt2Init(conn, 0xcc123, false, false, handler) + prepareInsertSql := "insert into ? using stb tags(?) values (?,?)" + code := TaosStmt2Prepare(insertStmt, prepareInsertSql) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + binds := []*stmt.TaosStmt2BindData{ + { + TableName: "table1", + Cols: [][]driver.Value{ + { + // ts 1726803356466 + time.Unix(1726803356, 466000000), + }, + { + int64(1), + }, + }, + Tags: []driver.Value{int32(1)}, + }, + { + TableName: "table2", + Cols: [][]driver.Value{ + { + // ts 1726803356466 + time.Unix(1726803356, 466000000), + }, + { + int64(2), + }, + }, + Tags: []driver.Value{int32(2)}, + }, + { + TableName: "table3", + Cols: [][]driver.Value{ + { + // ts 1726803356466 + time.Unix(1726803356, 466000000), + }, + { + int64(3), + }, + }, + Tags: []driver.Value{int32(3)}, + }, + } + colType := []*stmt.StmtField{ + { + FieldType: common.TSDB_DATA_TYPE_TIMESTAMP, + Precision: common.PrecisionMilliSecond, + }, + { + FieldType: common.TSDB_DATA_TYPE_BIGINT, + }, + } + tagType := []*stmt.StmtField{ + { + FieldType: common.TSDB_DATA_TYPE_INT, + }, + } + + isInsert, code := TaosStmt2IsInsert(insertStmt) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + assert.True(t, isInsert) + + err = TaosStmt2BindParam(insertStmt, true, binds, colType, tagType, -1) + if err != nil { + t.Error(err) + return + } + code = TaosStmt2Exec(insertStmt) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + r := <-caller.ExecResult + if r.n != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(r.n, errStr) + t.Error(err) + return + } + t.Log(r.affected) + + code = TaosStmt2Close(insertStmt) + if code != 0 { + errStr := TaosStmt2Error(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } +} + +func TestTaosStmt2BindBinaryParse(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + err = exec(conn, "drop database if exists test_stmt2_binary_parse") + if err != nil { + t.Error(err) + return + } + }() + err = exec(conn, "create database if not exists test_stmt2_binary_parse precision 'ms' keep 36500") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "use test_stmt2_binary_parse") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "create table test1 (ts timestamp, v int)") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "create table stb (ts timestamp, v int) tags(tv int)") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "create table test2 (ts timestamp, v binary(100))") + if err != nil { + t.Error(err) + return + } + type args struct { + sql string + data []byte + colIdx int32 + } + tests := []struct { + name string + args args + wantErr assert.ErrorAssertionFunc + }{ + { + name: "normal table name", + args: args{ + sql: "insert into ? values (?,?)", + data: []byte{ + // total Length + 0x24, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x00, 0x00, 0x00, 0x00, + // ColCount + 0x00, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x1c, 0x00, 0x00, 0x00, + // TagsOffset + 0x00, 0x00, 0x00, 0x00, + // ColOffset + 0x00, 0x00, 0x00, 0x00, + // table names + // TableNameLength + 0x06, 0x00, + // test1 + 0x74, 0x65, 0x73, 0x74, 0x31, 0x00, + }, + colIdx: -1, + }, + wantErr: assert.NoError, + }, + { + name: "empty table name", + args: args{ + sql: "insert into ? values (?,?)", + data: []byte{ + // total Length + 0x1e, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x00, 0x00, 0x00, 0x00, + // ColCount + 0x00, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x1c, 0x00, 0x00, 0x00, + // TagsOffset + 0x00, 0x00, 0x00, 0x00, + // ColOffset + 0x00, 0x00, 0x00, 0x00, + // table names + // TableNameLength + 0x00, 0x00, + }, + colIdx: -1, + }, + wantErr: assert.Error, + }, + { + name: "wrong total length", + args: args{ + sql: "insert into ? values (?,?)", + data: []byte{ + // total Length + 0x24, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x00, 0x00, 0x00, 0x00, + // ColCount + 0x00, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x1c, 0x00, 0x00, 0x00, + // TagsOffset + 0x00, 0x00, 0x00, 0x00, + // ColOffset + 0x00, 0x00, 0x00, 0x00, + // table names + // TableNameLength + 0x06, 0x00, + // test1 + 0x74, 0x65, 0x73, 0x74, 0x31, 0x00, + // + 0x00, + }, + colIdx: -1, + }, + wantErr: assert.Error, + }, + { + name: "wrong table name offset", + args: args{ + sql: "insert into ? values (?,?)", + data: []byte{ + // total Length + 0x24, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x00, 0x00, 0x00, 0x00, + // ColCount + 0x00, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x24, 0x00, 0x00, 0x00, + // TagsOffset + 0x00, 0x00, 0x00, 0x00, + // ColOffset + 0x00, 0x00, 0x00, 0x00, + // table names + // TableNameLength + 0x06, 0x00, + // test1 + 0x74, 0x65, 0x73, 0x74, 0x31, 0x00, + }, + colIdx: -1, + }, + wantErr: assert.Error, + }, + { + name: "wrong table name length", + args: args{ + sql: "insert into ? values (?,?)", + data: []byte{ + // total Length + 0x24, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x00, 0x00, 0x00, 0x00, + // ColCount + 0x00, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x1c, 0x00, 0x00, 0x00, + // TagsOffset + 0x00, 0x00, 0x00, 0x00, + // ColOffset + 0x00, 0x00, 0x00, 0x00, + // table names + // TableNameLength + 0x07, 0x00, + // test1 + 0x74, 0x65, 0x73, 0x74, 0x31, 0x00, + }, + colIdx: -1, + }, + wantErr: assert.Error, + }, + { + name: "normal col", + args: args{ + sql: "insert into test1 values (?,?)", + data: []byte{ + // total Length + 0x50, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x00, 0x00, 0x00, 0x00, + // ColCount + 0x02, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x00, 0x00, 0x00, 0x00, + // TagsOffset + 0x00, 0x00, 0x00, 0x00, + // ColOffset + 0x1c, 0x00, 0x00, 0x00, + // cols + 0x30, 0x00, 0x00, 0x00, + + 0x1a, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0xba, 0x08, 0x32, 0x27, 0x92, 0x01, 0x00, 0x00, + + 0x16, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x7b, 0x00, 0x00, 0x00, + }, + colIdx: -1, + }, + wantErr: assert.NoError, + }, + { + name: "col zero length", + args: args{ + sql: "insert into test1 values (?,?)", + data: []byte{ + // total Length + 0x50, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x00, 0x00, 0x00, 0x00, + // ColCount + 0x02, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x00, 0x00, 0x00, 0x00, + // TagsOffset + 0x00, 0x00, 0x00, 0x00, + // ColOffset + 0x1c, 0x00, 0x00, 0x00, + // cols + 0x00, 0x00, 0x00, 0x00, + + 0x1a, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0xba, 0x08, 0x32, 0x27, 0x92, 0x01, 0x00, 0x00, + + 0x16, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x7b, 0x00, 0x00, 0x00, + }, + colIdx: -1, + }, + wantErr: assert.Error, + }, + { + name: "wrong col offset", + args: args{ + sql: "insert into test1 values (?,?)", + data: []byte{ + // total Length + 0x50, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x00, 0x00, 0x00, 0x00, + // ColCount + 0x02, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x00, 0x00, 0x00, 0x00, + // TagsOffset + 0x00, 0x00, 0x00, 0x00, + // ColOffset + 0x50, 0x00, 0x00, 0x00, + // cols + 0x30, 0x00, 0x00, 0x00, + + 0x1a, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0xba, 0x08, 0x32, 0x27, 0x92, 0x01, 0x00, 0x00, + + 0x16, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x7b, 0x00, 0x00, 0x00, + }, + colIdx: -1, + }, + wantErr: assert.Error, + }, + { + name: "wrong col length", + args: args{ + sql: "insert into test1 values (?,?)", + data: []byte{ + // total Length + 0x50, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x00, 0x00, 0x00, 0x00, + // ColCount + 0x02, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x00, 0x00, 0x00, 0x00, + // TagsOffset + 0x00, 0x00, 0x00, 0x00, + // ColOffset + 0x1c, 0x00, 0x00, 0x00, + // cols + 0x50, 0x00, 0x00, 0x00, + + 0x1a, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0xba, 0x08, 0x32, 0x27, 0x92, 0x01, 0x00, 0x00, + + 0x16, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x7b, 0x00, 0x00, 0x00, + }, + colIdx: -1, + }, + wantErr: assert.Error, + }, + { + name: "wrong col bind length", + args: args{ + sql: "insert into test1 values (?,?)", + data: []byte{ + // total Length + 0x50, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x00, 0x00, 0x00, 0x00, + // ColCount + 0x02, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x00, 0x00, 0x00, 0x00, + // TagsOffset + 0x00, 0x00, 0x00, 0x00, + // ColOffset + 0x1c, 0x00, 0x00, 0x00, + // cols + 0x30, 0x00, 0x00, 0x00, + + 0x1b, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0xba, 0x08, 0x32, 0x27, 0x92, 0x01, 0x00, 0x00, + + 0x16, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x7b, 0x00, 0x00, 0x00, + }, + colIdx: -1, + }, + wantErr: assert.Error, + }, + { + name: "normal col count", + args: args{ + sql: "insert into test1 values (?,?)", + data: []byte{ + // total Length + 0x50, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x00, 0x00, 0x00, 0x00, + // ColCount + 0x00, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x00, 0x00, 0x00, 0x00, + // TagsOffset + 0x00, 0x00, 0x00, 0x00, + // ColOffset + 0x1c, 0x00, 0x00, 0x00, + // cols + 0x30, 0x00, 0x00, 0x00, + + 0x1a, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0xba, 0x08, 0x32, 0x27, 0x92, 0x01, 0x00, 0x00, + + 0x16, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x04, 0x00, 0x00, 0x00, + 0x7b, 0x00, 0x00, 0x00, + }, + colIdx: -1, + }, + wantErr: assert.Error, + }, + { + name: "normal tag", + args: args{ + sql: "insert into ? using stb tags(?) values (?,?)", + data: []byte{ + // total Length + 0x40, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x01, 0x00, 0x00, 0x00, + // ColCount + 0x00, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x1c, 0x00, 0x00, 0x00, + // TagsOffset + 0x22, 0x00, 0x00, 0x00, + // ColOffset + 0x00, 0x00, 0x00, 0x00, + // table names + 0x04, 0x00, 0x63, 0x74, 0x62, 0x00, + // tags + 0x1a, 0x00, 0x00, 0x00, + + 0x1a, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0xc8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + colIdx: -1, + }, + wantErr: assert.NoError, + }, + { + name: "tag zero length", + args: args{ + sql: "insert into ? using stb tags(?) values (?,?)", + data: []byte{ + // total Length + 0x40, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x01, 0x00, 0x00, 0x00, + // ColCount + 0x00, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x1c, 0x00, 0x00, 0x00, + // TagsOffset + 0x22, 0x00, 0x00, 0x00, + // ColOffset + 0x00, 0x00, 0x00, 0x00, + // table names + 0x04, 0x00, 0x63, 0x74, 0x62, 0x00, + // tags + 0x00, 0x00, 0x00, 0x00, + + 0x1a, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0xc8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + colIdx: -1, + }, + wantErr: assert.Error, + }, + { + name: "wrong tag offset", + args: args{ + sql: "insert into ? using stb tags(?) values (?,?)", + data: []byte{ + // total Length + 0x40, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x01, 0x00, 0x00, 0x00, + // ColCount + 0x00, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x1c, 0x00, 0x00, 0x00, + // TagsOffset + 0x40, 0x00, 0x00, 0x00, + // ColOffset + 0x00, 0x00, 0x00, 0x00, + // table names + 0x04, 0x00, 0x63, 0x74, 0x62, 0x00, + // tags + 0x1a, 0x00, 0x00, 0x00, + + 0x1a, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0xc8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + colIdx: -1, + }, + wantErr: assert.Error, + }, + { + name: "wrong tag length", + args: args{ + sql: "insert into ? using stb tags(?) values (?,?)", + data: []byte{ + // total Length + 0x40, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x01, 0x00, 0x00, 0x00, + // ColCount + 0x00, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x1c, 0x00, 0x00, 0x00, + // TagsOffset + 0x22, 0x00, 0x00, 0x00, + // ColOffset + 0x00, 0x00, 0x00, 0x00, + // table names + 0x04, 0x00, 0x63, 0x74, 0x62, 0x00, + // tags + 0x40, 0x00, 0x00, 0x00, + + 0x1a, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0xc8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + colIdx: -1, + }, + wantErr: assert.Error, + }, + { + name: "wrong tag bind length", + args: args{ + sql: "insert into ? using stb tags(?) values (?,?)", + data: []byte{ + // total Length + 0x40, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x01, 0x00, 0x00, 0x00, + // ColCount + 0x00, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x1c, 0x00, 0x00, 0x00, + // TagsOffset + 0x22, 0x00, 0x00, 0x00, + // ColOffset + 0x00, 0x00, 0x00, 0x00, + // table names + 0x04, 0x00, 0x63, 0x74, 0x62, 0x00, + // tags + 0x1a, 0x00, 0x00, 0x00, + + 0x40, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0xc8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + colIdx: -1, + }, + wantErr: assert.Error, + }, + { + name: "wrong tag count", + args: args{ + sql: "insert into ? using stb tags(?) values (?,?)", + data: []byte{ + // total Length + 0x40, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x00, 0x00, 0x00, 0x00, + // ColCount + 0x00, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x1c, 0x00, 0x00, 0x00, + // TagsOffset + 0x22, 0x00, 0x00, 0x00, + // ColOffset + 0x00, 0x00, 0x00, 0x00, + // table names + 0x04, 0x00, 0x63, 0x74, 0x62, 0x00, + // tags + 0x1a, 0x00, 0x00, 0x00, + + 0x1a, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0xc8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, + colIdx: -1, + }, + wantErr: assert.Error, + }, + { + name: "wrong param count", + args: args{ + sql: "insert into test1 values (?,?)", + data: []byte{ + // total Length + 0x3A, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x00, 0x00, 0x00, 0x00, + // ColCount + 0x01, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x00, 0x00, 0x00, 0x00, + // TagsOffset + 0x00, 0x00, 0x00, 0x00, + // ColOffset + 0x1c, 0x00, 0x00, 0x00, + // cols + 0x1a, 0x00, 0x00, 0x00, + + 0x1a, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, + 0x00, + 0x08, 0x00, 0x00, 0x00, + 0xba, 0x08, 0x32, 0x27, 0x92, 0x01, 0x00, 0x00, + }, + colIdx: -1, + }, + wantErr: assert.Error, + }, + { + name: "bind binary", + args: args{ + sql: "insert into test2 values (?,?)", + data: []byte{ + // total Length + 0x78, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x00, 0x00, 0x00, 0x00, + // ColCount + 0x02, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x00, 0x00, 0x00, 0x00, + // TagsOffset + 0x00, 0x00, 0x00, 0x00, + // ColOffset + 0x1c, 0x00, 0x00, 0x00, + // cols + // col length + 0x58, 0x00, 0x00, 0x00, + //table 0 cols + //col 0 + //total length + 0x2c, 0x00, 0x00, 0x00, + //type + 0x09, 0x00, 0x00, 0x00, + //num + 0x03, 0x00, 0x00, 0x00, + //is null + 0x00, + 0x00, + 0x00, + // haveLength + 0x00, + // buffer length + 0x18, 0x00, 0x00, 0x00, + 0x32, 0x2b, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, 0x1a, 0x2f, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, 0x02, 0x33, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, + + //col 1 + //total length + 0x2c, 0x00, 0x00, 0x00, + //type + 0x08, 0x00, 0x00, 0x00, + //num + 0x03, 0x00, 0x00, 0x00, + //is null + 0x00, + 0x01, + 0x00, + // haveLength + 0x01, + // length + 0x06, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, + // buffer length + 0x0c, 0x00, 0x00, 0x00, + 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, + }, + colIdx: -1, + }, + wantErr: assert.NoError, + }, + { + name: "empty buffer", + args: args{ + sql: "insert into test2 values (?,?)", + data: []byte{ + // total Length + 0x4c, 0x00, 0x00, 0x00, + // tableCount + 0x01, 0x00, 0x00, 0x00, + // TagCount + 0x00, 0x00, 0x00, 0x00, + // ColCount + 0x02, 0x00, 0x00, 0x00, + // TableNamesOffset + 0x00, 0x00, 0x00, 0x00, + // TagsOffset + 0x00, 0x00, 0x00, 0x00, + // ColOffset + 0x1c, 0x00, 0x00, 0x00, + // cols + // col length + 0x2c, 0x00, 0x00, 0x00, + //table 0 cols + //col 0 + //total length + 0x1a, 0x00, 0x00, 0x00, + //type + 0x09, 0x00, 0x00, 0x00, + //num + 0x01, 0x00, 0x00, 0x00, + //is null + 0x00, + // haveLength + 0x00, + // buffer length + 0x08, 0x00, 0x00, 0x00, + 0x32, 0x2b, 0x80, 0x0d, 0x92, 0x01, 0x00, 0x00, + + //col 1 + //total length + 0x12, 0x00, 0x00, 0x00, + //type + 0x04, 0x00, 0x00, 0x00, + //num + 0x01, 0x00, 0x00, 0x00, + //is null + 0x01, + // haveLength + 0x00, + // buffer length + 0x00, 0x00, 0x00, 0x00, + }, + colIdx: -1, + }, + wantErr: assert.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + caller := NewStmtCallBackTest() + handler := cgo.NewHandle(caller) + stmt2 := TaosStmt2Init(conn, 0xdd123, false, false, handler) + defer TaosStmt2Close(stmt2) + code := TaosStmt2Prepare(stmt2, tt.args.sql) + if code != 0 { + errStr := TaosStmt2Error(stmt2) + err := taosError.NewError(code, errStr) + t.Error(err) + return + } + tt.wantErr(t, TaosStmt2BindBinary(stmt2, tt.args.data, tt.args.colIdx), fmt.Sprintf("TaosStmt2BindBinary(%v, %v, %v)", stmt2, tt.args.data, tt.args.colIdx)) + }) + } +} diff --git a/driver/wrapper/stmt2async.go b/driver/wrapper/stmt2async.go new file mode 100644 index 00000000..cc3babb6 --- /dev/null +++ b/driver/wrapper/stmt2async.go @@ -0,0 +1,26 @@ +package wrapper + +/* +#include +#include +#include +#include + +*/ +import "C" +import ( + "unsafe" + + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" +) + +type TaosStmt2CallbackCaller interface { + ExecCall(res unsafe.Pointer, affected int, code int) +} + +//export Stmt2ExecCallback +func Stmt2ExecCallback(p unsafe.Pointer, res *C.TAOS_RES, code C.int) { + caller := (*(*cgo.Handle)(p)).Value().(TaosStmt2CallbackCaller) + affectedRows := int(C.taos_affected_rows(unsafe.Pointer(res))) + caller.ExecCall(unsafe.Pointer(res), affectedRows, int(code)) +} diff --git a/driver/wrapper/stmt_test.go b/driver/wrapper/stmt_test.go new file mode 100644 index 00000000..9d63b7a9 --- /dev/null +++ b/driver/wrapper/stmt_test.go @@ -0,0 +1,1367 @@ +package wrapper + +import ( + "database/sql/driver" + "fmt" + "testing" + "time" + "unsafe" + + "github.com/stretchr/testify/assert" + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/common/param" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + stmtCommon "github.com/taosdata/taosadapter/v3/driver/common/stmt" + taosError "github.com/taosdata/taosadapter/v3/driver/errors" + taosTypes "github.com/taosdata/taosadapter/v3/driver/types" +) + +// @author: xftan +// @date: 2022/1/27 17:27 +// @description: test stmt with taos_stmt_bind_param_batch +func TestStmt(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + err = exec(conn, "drop database if exists test_wrapper") + if err != nil { + t.Error(err) + return + } + }() + err = exec(conn, "create database if not exists test_wrapper precision 'ms' keep 36500") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "use test_wrapper") + if err != nil { + t.Error(err) + return + } + now := time.Now() + for i, tc := range []struct { + tbType string + pos string + params [][]driver.Value + bindType []*taosTypes.ColumnType + expectValue interface{} + }{ + { + tbType: "ts timestamp, v int", + pos: "?, ?", + params: [][]driver.Value{{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}}, {taosTypes.TaosInt(1)}}, + bindType: []*taosTypes.ColumnType{{Type: taosTypes.TaosTimestampType}, {Type: taosTypes.TaosIntType}}, + expectValue: int32(1), + }, + { + tbType: "ts timestamp, v bool", + pos: "?, ?", + params: [][]driver.Value{{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}}, {taosTypes.TaosBool(true)}}, + bindType: []*taosTypes.ColumnType{{Type: taosTypes.TaosTimestampType}, {Type: taosTypes.TaosBoolType}}, + expectValue: true, + }, + { + tbType: "ts timestamp, v tinyint", + pos: "?, ?", + params: [][]driver.Value{{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}}, {taosTypes.TaosTinyint(1)}}, + bindType: []*taosTypes.ColumnType{{Type: taosTypes.TaosTimestampType}, {Type: taosTypes.TaosTinyintType}}, + expectValue: int8(1), + }, + { + tbType: "ts timestamp, v smallint", + pos: "?, ?", + params: [][]driver.Value{{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}}, {taosTypes.TaosSmallint(1)}}, + bindType: []*taosTypes.ColumnType{{Type: taosTypes.TaosTimestampType}, {Type: taosTypes.TaosSmallintType}}, + expectValue: int16(1), + }, + { + tbType: "ts timestamp, v bigint", + pos: "?, ?", + params: [][]driver.Value{{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}}, {taosTypes.TaosBigint(1)}}, + bindType: []*taosTypes.ColumnType{{Type: taosTypes.TaosTimestampType}, {Type: taosTypes.TaosBigintType}}, + expectValue: int64(1), + }, + { + tbType: "ts timestamp, v tinyint unsigned", + pos: "?, ?", + params: [][]driver.Value{{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}}, {taosTypes.TaosUTinyint(1)}}, + bindType: []*taosTypes.ColumnType{{Type: taosTypes.TaosTimestampType}, {Type: taosTypes.TaosUTinyintType}}, + expectValue: uint8(1), + }, + { + tbType: "ts timestamp, v smallint unsigned", + pos: "?, ?", + params: [][]driver.Value{{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}}, {taosTypes.TaosUSmallint(1)}}, + bindType: []*taosTypes.ColumnType{{Type: taosTypes.TaosTimestampType}, {Type: taosTypes.TaosUSmallintType}}, + expectValue: uint16(1), + }, + { + tbType: "ts timestamp, v int unsigned", + pos: "?, ?", + params: [][]driver.Value{{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}}, {taosTypes.TaosUInt(1)}}, + bindType: []*taosTypes.ColumnType{{Type: taosTypes.TaosTimestampType}, {Type: taosTypes.TaosUIntType}}, + expectValue: uint32(1), + }, + { + tbType: "ts timestamp, v bigint unsigned", + pos: "?, ?", + params: [][]driver.Value{{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}}, {taosTypes.TaosUBigint(1)}}, + bindType: []*taosTypes.ColumnType{{Type: taosTypes.TaosTimestampType}, {Type: taosTypes.TaosUBigintType}}, + expectValue: uint64(1), + }, + { + tbType: "ts timestamp, v float", + pos: "?, ?", + params: [][]driver.Value{{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}}, {taosTypes.TaosFloat(1.2)}}, + bindType: []*taosTypes.ColumnType{{Type: taosTypes.TaosTimestampType}, {Type: taosTypes.TaosFloatType}}, + expectValue: float32(1.2), + }, + { + tbType: "ts timestamp, v double", + pos: "?, ?", + params: [][]driver.Value{{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}}, {taosTypes.TaosDouble(1.2)}}, + bindType: []*taosTypes.ColumnType{{Type: taosTypes.TaosTimestampType}, {Type: taosTypes.TaosDoubleType}}, + expectValue: 1.2, + }, + { + tbType: "ts timestamp, v binary(8)", + pos: "?, ?", + params: [][]driver.Value{{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}}, {taosTypes.TaosBinary("yes")}}, + bindType: []*taosTypes.ColumnType{{Type: taosTypes.TaosTimestampType}, { + Type: taosTypes.TaosBinaryType, + MaxLen: 3, + }}, + expectValue: "yes", + }, //3 + { + tbType: "ts timestamp, v varbinary(8)", + pos: "?, ?", + params: [][]driver.Value{{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}}, {taosTypes.TaosVarBinary("yes")}}, + bindType: []*taosTypes.ColumnType{{Type: taosTypes.TaosTimestampType}, { + Type: taosTypes.TaosVarBinaryType, + MaxLen: 3, + }}, + expectValue: []byte("yes"), + }, //3 + { + tbType: "ts timestamp, v geometry(100)", + pos: "?, ?", + params: [][]driver.Value{{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}}, {taosTypes.TaosGeometry{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}}}, + bindType: []*taosTypes.ColumnType{{Type: taosTypes.TaosTimestampType}, { + Type: taosTypes.TaosGeometryType, + MaxLen: 100, + }}, + expectValue: []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + }, //3 + { + tbType: "ts timestamp, v nchar(8)", + pos: "?, ?", + params: [][]driver.Value{{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}}, {taosTypes.TaosNchar("yes")}}, + bindType: []*taosTypes.ColumnType{{Type: taosTypes.TaosTimestampType}, { + Type: taosTypes.TaosNcharType, + MaxLen: 3, + }}, + expectValue: "yes", + }, //3 + { + tbType: "ts timestamp, v nchar(8)", + pos: "?, ?", + params: [][]driver.Value{{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}}, {nil}}, + bindType: []*taosTypes.ColumnType{{Type: taosTypes.TaosTimestampType}, { + Type: taosTypes.TaosNcharType, + MaxLen: 1, + }}, + expectValue: nil, + }, //1 + } { + tbName := fmt.Sprintf("test_fast_insert_%02d", i) + tbType := tc.tbType + drop := fmt.Sprintf("drop table if exists %s", tbName) + create := fmt.Sprintf("create table if not exists %s(%s)", tbName, tbType) + name := fmt.Sprintf("%02d-%s", i, tbType) + pos := tc.pos + sql := fmt.Sprintf("insert into %s values(%s)", tbName, pos) + var err error + t.Run(name, func(t *testing.T) { + if err = exec(conn, drop); err != nil { + t.Error(err) + return + } + if err = exec(conn, create); err != nil { + t.Error(err) + return + } + insertStmt := TaosStmtInit(conn) + code := TaosStmtPrepare(insertStmt, sql) + if code != 0 { + errStr := TaosStmtErrStr(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + isInsert, code := TaosStmtIsInsert(insertStmt) + if code != 0 { + errStr := TaosStmtErrStr(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + if !isInsert { + t.Errorf("expect insert stmt") + return + } + code = TaosStmtBindParamBatch(insertStmt, tc.params, tc.bindType) + if code != 0 { + errStr := TaosStmtErrStr(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + code = TaosStmtAddBatch(insertStmt) + if code != 0 { + errStr := TaosStmtErrStr(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + code = TaosStmtExecute(insertStmt) + if code != 0 { + errStr := TaosStmtErrStr(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + code = TaosStmtClose(insertStmt) + if code != 0 { + errStr := TaosStmtErrStr(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + result, err := query(conn, fmt.Sprintf("select v from %s", tbName)) + if err != nil { + t.Error(err) + return + } + if len(result) != 1 { + t.Errorf("expect %d got %d", 1, len(result)) + return + } + assert.Equal(t, tc.expectValue, result[0][0]) + }) + } + +} + +// @author: xftan +// @date: 2022/1/27 17:27 +// @description: test stmt insert with taos_stmt_bind_param +func TestStmtExec(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + err = exec(conn, "drop database if exists test_wrapper") + if err != nil { + t.Error(err) + return + } + }() + err = exec(conn, "create database if not exists test_wrapper precision 'us' keep 36500") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "use test_wrapper") + if err != nil { + t.Error(err) + return + } + now := time.Now() + for i, tc := range []struct { + tbType string + pos string + params []driver.Value + expectValue interface{} + }{ + { + tbType: "ts timestamp, v int", + pos: "?, ?", + params: []driver.Value{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}, taosTypes.TaosInt(1)}, + expectValue: int32(1), + }, + { + tbType: "ts timestamp, v bool", + pos: "?, ?", + params: []driver.Value{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}, taosTypes.TaosBool(true)}, + expectValue: true, + }, + { + tbType: "ts timestamp, v tinyint", + pos: "?, ?", + params: []driver.Value{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}, taosTypes.TaosTinyint(1)}, + expectValue: int8(1), + }, + { + tbType: "ts timestamp, v smallint", + pos: "?, ?", + params: []driver.Value{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}, taosTypes.TaosSmallint(1)}, + expectValue: int16(1), + }, + { + tbType: "ts timestamp, v bigint", + pos: "?, ?", + params: []driver.Value{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}, taosTypes.TaosBigint(1)}, + expectValue: int64(1), + }, + { + tbType: "ts timestamp, v tinyint unsigned", + pos: "?, ?", + params: []driver.Value{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}, taosTypes.TaosUTinyint(1)}, + expectValue: uint8(1), + }, + { + tbType: "ts timestamp, v smallint unsigned", + pos: "?, ?", + params: []driver.Value{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}, taosTypes.TaosUSmallint(1)}, + expectValue: uint16(1), + }, + { + tbType: "ts timestamp, v int unsigned", + pos: "?, ?", + params: []driver.Value{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}, taosTypes.TaosUInt(1)}, + expectValue: uint32(1), + }, + { + tbType: "ts timestamp, v bigint unsigned", + pos: "?, ?", + params: []driver.Value{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}, taosTypes.TaosUBigint(1)}, + expectValue: uint64(1), + }, + { + tbType: "ts timestamp, v float", + pos: "?, ?", + params: []driver.Value{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}, taosTypes.TaosFloat(1.2)}, + expectValue: float32(1.2), + }, + { + tbType: "ts timestamp, v double", + pos: "?, ?", + params: []driver.Value{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}, taosTypes.TaosDouble(1.2)}, + expectValue: 1.2, + }, + { + tbType: "ts timestamp, v binary(8)", + pos: "?, ?", + params: []driver.Value{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}, taosTypes.TaosBinary("yes")}, + expectValue: "yes", + }, //3 + { + tbType: "ts timestamp, v varbinary(8)", + pos: "?, ?", + params: []driver.Value{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}, taosTypes.TaosVarBinary("yes")}, + expectValue: []byte("yes"), + }, //3 + { + tbType: "ts timestamp, v geometry(100)", + pos: "?, ?", + params: []driver.Value{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}, taosTypes.TaosGeometry{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}}, + expectValue: []byte{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x40}, + }, //3 + { + tbType: "ts timestamp, v nchar(8)", + pos: "?, ?", + params: []driver.Value{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}, taosTypes.TaosNchar("yes")}, + expectValue: "yes", + }, //3 + { + tbType: "ts timestamp, v nchar(8)", + pos: "?, ?", + params: []driver.Value{taosTypes.TaosTimestamp{T: now, Precision: common.PrecisionMilliSecond}, nil}, + expectValue: nil, + }, //1 + } { + tbName := fmt.Sprintf("test_fast_insert_2_%02d", i) + tbType := tc.tbType + drop := fmt.Sprintf("drop table if exists %s", tbName) + create := fmt.Sprintf("create table if not exists %s(%s)", tbName, tbType) + name := fmt.Sprintf("%02d-%s", i, tbType) + pos := tc.pos + sql := fmt.Sprintf("insert into %s values(%s)", tbName, pos) + var err error + t.Run(name, func(t *testing.T) { + if err = exec(conn, drop); err != nil { + t.Error(err) + return + } + if err = exec(conn, create); err != nil { + t.Error(err) + return + } + insertStmt := TaosStmtInit(conn) + code := TaosStmtPrepare(insertStmt, sql) + if code != 0 { + errStr := TaosStmtErrStr(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + code = TaosStmtBindParam(insertStmt, tc.params) + if code != 0 { + errStr := TaosStmtErrStr(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + code = TaosStmtAddBatch(insertStmt) + if code != 0 { + errStr := TaosStmtErrStr(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + code = TaosStmtExecute(insertStmt) + if code != 0 { + errStr := TaosStmtErrStr(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + affectedRows := TaosStmtAffectedRowsOnce(insertStmt) + if affectedRows != 1 { + t.Errorf("expect 1 got %d", affectedRows) + return + } + code = TaosStmtClose(insertStmt) + if code != 0 { + errStr := TaosStmtErrStr(insertStmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + result, err := query(conn, fmt.Sprintf("select v from %s", tbName)) + if err != nil { + t.Error(err) + return + } + if len(result) != 1 { + t.Errorf("expect %d got %d", 1, len(result)) + return + } + assert.Equal(t, tc.expectValue, result[0][0]) + }) + } +} + +// @author: xftan +// @date: 2023/10/13 11:30 +// @description: test stmt query +func TestStmtQuery(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + err = exec(conn, "create database if not exists test_wrapper precision 'us' keep 36500") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "use test_wrapper") + if err != nil { + t.Error(err) + return + } + for i, tc := range []struct { + tbType string + data string + clause string + params *param.Param + skip bool + }{ + { + tbType: "ts timestamp, v int", + data: "0, 1", + clause: "v = ?", + params: param.NewParam(1).AddInt(1), + }, + { + tbType: "ts timestamp, v bool", + data: "now, true", + clause: "v = ?", + params: param.NewParam(1).AddBool(true), + }, + { + tbType: "ts timestamp, v tinyint", + data: "now, 3", + clause: "v = ?", + params: param.NewParam(1).AddTinyint(3), + }, + { + tbType: "ts timestamp, v smallint", + data: "now, 5", + clause: "v = ?", + params: param.NewParam(1).AddSmallint(5), + }, + { + tbType: "ts timestamp, v int", + data: "now, 6", + clause: "v = ?", + params: param.NewParam(1).AddInt(6), + }, + { + tbType: "ts timestamp, v bigint", + data: "now, 7", + clause: "v = ?", + params: param.NewParam(1).AddBigint(7), + }, + { + tbType: "ts timestamp, v tinyint unsigned", + data: "now, 1", + clause: "v = ?", + params: param.NewParam(1).AddUTinyint(1), + }, + { + tbType: "ts timestamp, v smallint unsigned", + data: "now, 2", + clause: "v = ?", + params: param.NewParam(1).AddUSmallint(2), + }, + { + tbType: "ts timestamp, v int unsigned", + data: "now, 3", + clause: "v = ?", + params: param.NewParam(1).AddUInt(3), + }, + { + tbType: "ts timestamp, v bigint unsigned", + data: "now, 4", + clause: "v = ?", + params: param.NewParam(1).AddUBigint(4), + }, + { + tbType: "ts timestamp, v tinyint unsigned", + data: "now, 1", + clause: "v = ?", + params: param.NewParam(1).AddUTinyint(1), + }, + { + tbType: "ts timestamp, v smallint unsigned", + data: "now, 2", + clause: "v = ?", + params: param.NewParam(1).AddUSmallint(2), + }, + { + tbType: "ts timestamp, v int unsigned", + data: "now, 3", + clause: "v = ?", + params: param.NewParam(1).AddUInt(3), + }, + { + tbType: "ts timestamp, v bigint unsigned", + data: "now, 4", + clause: "v = ?", + params: param.NewParam(1).AddUBigint(4), + }, + { + tbType: "ts timestamp, v float", + data: "now, 1.2", + clause: "v = ?", + params: param.NewParam(1).AddFloat(1.2), + }, + { + tbType: "ts timestamp, v double", + data: "now, 1.3", + clause: "v = ?", + params: param.NewParam(1).AddDouble(1.3), + }, + { + tbType: "ts timestamp, v double", + data: "now, 1.4", + clause: "v = ?", + params: param.NewParam(1).AddDouble(1.4), + }, + { + tbType: "ts timestamp, v binary(8)", + data: "now, 'yes'", + clause: "v = ?", + params: param.NewParam(1).AddBinary([]byte("yes")), + }, + { + tbType: "ts timestamp, v nchar(8)", + data: "now, 'OK'", + clause: "v = ?", + params: param.NewParam(1).AddNchar("OK"), + }, + { + tbType: "ts timestamp, v nchar(8)", + data: "1622282105000000, 'NOW'", + clause: "ts = ? and v = ?", + params: param.NewParam(2).AddTimestamp(time.Unix(1622282105, 0), common.PrecisionMicroSecond).AddBinary([]byte("NOW")), + }, + { + tbType: "ts timestamp, v nchar(8)", + data: "1622282105000000, 'NOW'", + clause: "ts = ? and v = ?", + params: param.NewParam(2).AddBigint(1622282105000000).AddBinary([]byte("NOW")), + }, + } { + tbName := fmt.Sprintf("test_stmt_query%02d", i) + tbType := tc.tbType + create := fmt.Sprintf("create table if not exists %s(%s)", tbName, tbType) + insert := fmt.Sprintf("insert into %s values(%s)", tbName, tc.data) + params := tc.params + sql := fmt.Sprintf("select * from %s where %s", tbName, tc.clause) + name := fmt.Sprintf("%02d-%s", i, tbType) + var err error + t.Run(name, func(t *testing.T) { + if tc.skip { + t.Skip("Skip, not support yet") + } + if err = exec(conn, create); err != nil { + t.Error(err) + return + } + if err = exec(conn, insert); err != nil { + t.Error(err) + return + } + + rows, err := StmtQuery(t, conn, sql, params) + if err != nil { + t.Error(err) + return + } + t.Log(rows) + }) + } +} + +func query(conn unsafe.Pointer, sql string) ([][]driver.Value, error) { + res := TaosQuery(conn, sql) + defer TaosFreeResult(res) + code := TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + return nil, taosError.NewError(code, errStr) + } + fileCount := TaosNumFields(res) + rh, err := ReadColumn(res, fileCount) + if err != nil { + return nil, err + } + precision := TaosResultPrecision(res) + var result [][]driver.Value + for { + columns, errCode, block := TaosFetchRawBlock(res) + if errCode != 0 { + errStr := TaosErrorStr(res) + return nil, taosError.NewError(errCode, errStr) + } + if columns == 0 { + break + } + r := parser.ReadBlock(block, columns, rh.ColTypes, precision) + result = append(result, r...) + } + return result, nil +} + +func StmtQuery(t *testing.T, conn unsafe.Pointer, sql string, params *param.Param) (rows [][]driver.Value, err error) { + stmt := TaosStmtInit(conn) + if stmt == nil { + err = taosError.NewError(0xffff, "failed to init stmt") + return + } + defer TaosStmtClose(stmt) + code := TaosStmtPrepare(stmt, sql) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + return nil, taosError.NewError(code, errStr) + } + value := params.GetValues() + code = TaosStmtBindParam(stmt, value) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + return nil, taosError.NewError(code, errStr) + } + code = TaosStmtExecute(stmt) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + return nil, taosError.NewError(code, errStr) + } + res := TaosStmtUseResult(stmt) + numFields := TaosFieldCount(res) + rowsHeader, err := ReadColumn(res, numFields) + t.Log(rowsHeader) + if err != nil { + return nil, err + } + precision := TaosResultPrecision(res) + var data [][]driver.Value + for { + blockSize, errCode, block := TaosFetchRawBlock(res) + if errCode != int(taosError.SUCCESS) { + errStr := TaosErrorStr(res) + err := taosError.NewError(code, errStr) + return nil, err + } + if blockSize == 0 { + break + } + d := parser.ReadBlock(block, blockSize, rowsHeader.ColTypes, precision) + data = append(data, d...) + } + return data, nil +} + +// @author: xftan +// @date: 2023/10/13 11:30 +// @description: test get field +func TestGetFields(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + stmt := TaosStmtInit(conn) + defer func() { + err = exec(conn, "drop database if exists test_stmt_field") + if err != nil { + t.Error(err) + return + } + }() + err = exec(conn, "create database if not exists test_stmt_field") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "create table if not exists test_stmt_field.all_type(ts timestamp,"+ + "c1 bool,"+ + "c2 tinyint,"+ + "c3 smallint,"+ + "c4 int,"+ + "c5 bigint,"+ + "c6 tinyint unsigned,"+ + "c7 smallint unsigned,"+ + "c8 int unsigned,"+ + "c9 bigint unsigned,"+ + "c10 float,"+ + "c11 double,"+ + "c12 binary(20),"+ + "c13 nchar(20)"+ + ")"+ + "tags(tts timestamp,"+ + "tc1 bool,"+ + "tc2 tinyint,"+ + "tc3 smallint,"+ + "tc4 int,"+ + "tc5 bigint,"+ + "tc6 tinyint unsigned,"+ + "tc7 smallint unsigned,"+ + "tc8 int unsigned,"+ + "tc9 bigint unsigned,"+ + "tc10 float,"+ + "tc11 double,"+ + "tc12 binary(20),"+ + "tc13 nchar(20)"+ + ")") + if err != nil { + t.Error(err) + return + } + code := TaosStmtPrepare(stmt, "insert into ? using test_stmt_field.all_type tags(?,?,?,?,?,?,?,?,?,?,?,?,?,?) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?)") + if code != 0 { + errStr := TaosStmtErrStr(stmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + code = TaosStmtSetTBName(stmt, "test_stmt_field.ct2") + if code != 0 { + errStr := TaosStmtErrStr(stmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + code, tagCount, tagsP := TaosStmtGetTagFields(stmt) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + defer TaosStmtReclaimFields(stmt, tagsP) + code, columnCount, columnsP := TaosStmtGetColFields(stmt) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + defer TaosStmtReclaimFields(stmt, columnsP) + columns := StmtParseFields(columnCount, columnsP) + tags := StmtParseFields(tagCount, tagsP) + assert.Equal(t, []*stmtCommon.StmtField{ + {Name: "ts", FieldType: 9, Bytes: 8}, + {Name: "c1", FieldType: 1, Bytes: 1}, + {Name: "c2", FieldType: 2, Bytes: 1}, + {Name: "c3", FieldType: 3, Bytes: 2}, + {Name: "c4", FieldType: 4, Bytes: 4}, + {Name: "c5", FieldType: 5, Bytes: 8}, + {Name: "c6", FieldType: 11, Bytes: 1}, + {Name: "c7", FieldType: 12, Bytes: 2}, + {Name: "c8", FieldType: 13, Bytes: 4}, + {Name: "c9", FieldType: 14, Bytes: 8}, + {Name: "c10", FieldType: 6, Bytes: 4}, + {Name: "c11", FieldType: 7, Bytes: 8}, + {Name: "c12", FieldType: 8, Bytes: 22}, + {Name: "c13", FieldType: 10, Bytes: 82}, + }, columns) + assert.Equal(t, []*stmtCommon.StmtField{ + {Name: "tts", FieldType: 9, Bytes: 8}, + {Name: "tc1", FieldType: 1, Bytes: 1}, + {Name: "tc2", FieldType: 2, Bytes: 1}, + {Name: "tc3", FieldType: 3, Bytes: 2}, + {Name: "tc4", FieldType: 4, Bytes: 4}, + {Name: "tc5", FieldType: 5, Bytes: 8}, + {Name: "tc6", FieldType: 11, Bytes: 1}, + {Name: "tc7", FieldType: 12, Bytes: 2}, + {Name: "tc8", FieldType: 13, Bytes: 4}, + {Name: "tc9", FieldType: 14, Bytes: 8}, + {Name: "tc10", FieldType: 6, Bytes: 4}, + {Name: "tc11", FieldType: 7, Bytes: 8}, + {Name: "tc12", FieldType: 8, Bytes: 22}, + {Name: "tc13", FieldType: 10, Bytes: 82}, + }, tags) +} + +// @author: xftan +// @date: 2023/10/13 11:30 +// @description: test get fields with common table +func TestGetFieldsCommonTable(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + stmt := TaosStmtInit(conn) + defer func() { + err = exec(conn, "drop database if exists test_stmt_field") + if err != nil { + t.Error(err) + return + } + }() + err = exec(conn, "create database if not exists test_stmt_field") + if err != nil { + t.Error(err) + return + } + TaosSelectDB(conn, "test_stmt_field") + err = exec(conn, "create table if not exists ct(ts timestamp,"+ + "c1 bool,"+ + "c2 tinyint,"+ + "c3 smallint,"+ + "c4 int,"+ + "c5 bigint,"+ + "c6 tinyint unsigned,"+ + "c7 smallint unsigned,"+ + "c8 int unsigned,"+ + "c9 bigint unsigned,"+ + "c10 float,"+ + "c11 double,"+ + "c12 binary(20),"+ + "c13 nchar(20)"+ + ")") + if err != nil { + t.Error(err) + return + } + code := TaosStmtPrepare(stmt, "insert into ct values (?,?,?,?,?,?,?,?,?,?,?,?,?,?)") + if code != 0 { + errStr := TaosStmtErrStr(stmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + code, num, _ := TaosStmtGetTagFields(stmt) + assert.Equal(t, 0, code) + assert.Equal(t, 0, num) + code, columnCount, columnsP := TaosStmtGetColFields(stmt) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + defer TaosStmtReclaimFields(stmt, columnsP) + columns := StmtParseFields(columnCount, columnsP) + assert.Equal(t, []*stmtCommon.StmtField{ + {Name: "ts", FieldType: 9, Bytes: 8}, + {Name: "c1", FieldType: 1, Bytes: 1}, + {Name: "c2", FieldType: 2, Bytes: 1}, + {Name: "c3", FieldType: 3, Bytes: 2}, + {Name: "c4", FieldType: 4, Bytes: 4}, + {Name: "c5", FieldType: 5, Bytes: 8}, + {Name: "c6", FieldType: 11, Bytes: 1}, + {Name: "c7", FieldType: 12, Bytes: 2}, + {Name: "c8", FieldType: 13, Bytes: 4}, + {Name: "c9", FieldType: 14, Bytes: 8}, + {Name: "c10", FieldType: 6, Bytes: 4}, + {Name: "c11", FieldType: 7, Bytes: 8}, + {Name: "c12", FieldType: 8, Bytes: 22}, + {Name: "c13", FieldType: 10, Bytes: 82}, + }, columns) +} + +func exec(conn unsafe.Pointer, sql string) error { + res := TaosQuery(conn, sql) + defer TaosFreeResult(res) + code := TaosError(res) + if code != 0 { + errStr := TaosErrorStr(res) + return taosError.NewError(code, errStr) + } + return nil +} + +// @author: xftan +// @date: 2023/10/13 11:31 +// @description: test stmt set tags +func TestTaosStmtSetTags(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + err = exec(conn, "drop database if exists test_wrapper") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "create database if not exists test_wrapper precision 'us' keep 36500") + if err != nil { + t.Error(err) + return + } + defer func() { + _ = exec(conn, "drop database if exists test_wrapper") + }() + err = exec(conn, "create table if not exists test_wrapper.tgs(ts timestamp,v int) tags (tts timestamp,"+ + "t1 bool,"+ + "t2 tinyint,"+ + "t3 smallint,"+ + "t4 int,"+ + "t5 bigint,"+ + "t6 tinyint unsigned,"+ + "t7 smallint unsigned,"+ + "t8 int unsigned,"+ + "t9 bigint unsigned,"+ + "t10 float,"+ + "t11 double,"+ + "t12 binary(20),"+ + "t13 nchar(20))") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "create table if not exists test_wrapper.json_tag (ts timestamp,v int) tags (info json)") + if err != nil { + t.Error(err) + return + } + stmt := TaosStmtInit(conn) + if stmt == nil { + err = taosError.NewError(0xffff, "failed to init stmt") + t.Error(err) + return + } + //defer TaosStmtClose(stmt) + code := TaosStmtPrepare(stmt, "insert into ? using test_wrapper.tgs tags(?,?,?,?,?,?,?,?,?,?,?,?,?,?) values (?,?)") + if code != 0 { + errStr := TaosStmtErrStr(stmt) + t.Error(taosError.NewError(code, errStr)) + return + } + + code = TaosStmtSetTBName(stmt, "test_wrapper.t0") + if code != 0 { + errStr := TaosStmtErrStr(stmt) + t.Error(taosError.NewError(code, errStr)) + return + } + now := time.Now() + code = TaosStmtSetTags(stmt, param.NewParam(14). + AddTimestamp(now, common.PrecisionMicroSecond). + AddBool(true). + AddTinyint(2). + AddSmallint(3). + AddInt(4). + AddBigint(5). + AddUTinyint(6). + AddUSmallint(7). + AddUInt(8). + AddUBigint(9). + AddFloat(10). + AddDouble(11). + AddBinary([]byte("binary")). + AddNchar("nchar"). + GetValues()) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + t.Error(taosError.NewError(code, errStr)) + return + } + code = TaosStmtBindParam(stmt, param.NewParam(2).AddTimestamp(now, common.PrecisionMicroSecond).AddInt(100).GetValues()) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + t.Error(taosError.NewError(code, errStr)) + return + } + code = TaosStmtAddBatch(stmt) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + t.Error(taosError.NewError(code, errStr)) + return + } + code = TaosStmtExecute(stmt) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + t.Error(taosError.NewError(code, errStr)) + return + } + code = TaosStmtSetSubTBName(stmt, "test_wrapper.t1") + if code != 0 { + errStr := TaosStmtErrStr(stmt) + t.Error(taosError.NewError(code, errStr)) + return + } + code = TaosStmtSetTags(stmt, param.NewParam(14). + AddNull(). + AddNull(). + AddNull(). + AddNull(). + AddNull(). + AddNull(). + AddNull(). + AddNull(). + AddNull(). + AddNull(). + AddNull(). + AddNull(). + AddNull(). + AddNull(). + GetValues()) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + t.Error(taosError.NewError(code, errStr)) + return + } + code = TaosStmtBindParam(stmt, param.NewParam(2).AddTimestamp(now, common.PrecisionMicroSecond).AddInt(101).GetValues()) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + t.Error(taosError.NewError(code, errStr)) + return + } + code = TaosStmtAddBatch(stmt) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + t.Error(taosError.NewError(code, errStr)) + return + } + code = TaosStmtExecute(stmt) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + t.Error(taosError.NewError(code, errStr)) + return + } + + code = TaosStmtPrepare(stmt, "insert into ? using test_wrapper.json_tag tags(?) values (?,?)") + if code != 0 { + errStr := TaosStmtErrStr(stmt) + t.Error(taosError.NewError(code, errStr)) + return + } + code = TaosStmtSetTBName(stmt, "test_wrapper.t2") + if code != 0 { + errStr := TaosStmtErrStr(stmt) + t.Error(taosError.NewError(code, errStr)) + return + } + code = TaosStmtSetTags(stmt, param.NewParam(1).AddJson([]byte(`{"a":"b"}`)).GetValues()) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + t.Error(taosError.NewError(code, errStr)) + return + } + code = TaosStmtBindParam(stmt, param.NewParam(2).AddTimestamp(now, common.PrecisionMicroSecond).AddInt(102).GetValues()) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + t.Error(taosError.NewError(code, errStr)) + return + } + code = TaosStmtAddBatch(stmt) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + t.Error(taosError.NewError(code, errStr)) + return + } + code = TaosStmtExecute(stmt) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + t.Error(taosError.NewError(code, errStr)) + return + } + code = TaosStmtClose(stmt) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + t.Error(taosError.NewError(code, errStr)) + return + } + data, err := query(conn, "select tbname,tgs.* from test_wrapper.tgs where v >= 100") + if err != nil { + t.Error(err) + return + } + + assert.Equal(t, 2, len(data)) + for i := 0; i < 2; i++ { + + switch data[i][0] { + case "t0": + assert.Equal(t, now.UTC().UnixNano()/1e3, data[i][1].(time.Time).UTC().UnixNano()/1e3) + assert.Equal(t, int32(100), data[i][2].(int32)) + assert.Equal(t, now.UTC().UnixNano()/1e3, data[i][3].(time.Time).UTC().UnixNano()/1e3) + assert.Equal(t, true, data[i][4].(bool)) + assert.Equal(t, int8(2), data[i][5].(int8)) + assert.Equal(t, int16(3), data[i][6].(int16)) + assert.Equal(t, int32(4), data[i][7].(int32)) + assert.Equal(t, int64(5), data[i][8].(int64)) + assert.Equal(t, uint8(6), data[i][9].(uint8)) + assert.Equal(t, uint16(7), data[i][10].(uint16)) + assert.Equal(t, uint32(8), data[i][11].(uint32)) + assert.Equal(t, uint64(9), data[i][12].(uint64)) + assert.Equal(t, float32(10), data[i][13].(float32)) + assert.Equal(t, float64(11), data[i][14].(float64)) + assert.Equal(t, "binary", data[i][15].(string)) + assert.Equal(t, "nchar", data[i][16].(string)) + case "t1": + assert.Equal(t, now.UTC().UnixNano()/1e3, data[i][1].(time.Time).UTC().UnixNano()/1e3) + assert.Equal(t, int32(101), data[i][2].(int32)) + for j := 0; j < 14; j++ { + assert.Nil(t, data[i][3+j]) + } + } + } + + data, err = query(conn, "select tbname,json_tag.* from test_wrapper.json_tag where v >= 100") + if err != nil { + t.Error(err) + return + } + assert.Equal(t, 1, len(data)) + assert.Equal(t, "t2", data[0][0].(string)) + assert.Equal(t, now.UTC().UnixNano()/1e3, data[0][1].(time.Time).UTC().UnixNano()/1e3) + assert.Equal(t, int32(102), data[0][2].(int32)) + assert.Equal(t, []byte(`{"a":"b"}`), data[0][3].([]byte)) +} + +func TestTaosStmtGetParam(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + assert.NoError(t, err) + defer TaosClose(conn) + + err = exec(conn, "drop database if exists test_stmt_get_param") + assert.NoError(t, err) + err = exec(conn, "create database if not exists test_stmt_get_param") + assert.NoError(t, err) + defer func() { + err = exec(conn, "drop database if exists test_stmt_get_param") + assert.NoError(t, err) + }() + + err = exec(conn, + "create table if not exists test_stmt_get_param.stb(ts TIMESTAMP,current float,voltage int,phase float) TAGS (groupid int,location varchar(24))") + assert.NoError(t, err) + + stmt := TaosStmtInit(conn) + assert.NotNilf(t, stmt, "failed to init stmt") + defer TaosStmtClose(stmt) + + code := TaosStmtPrepare(stmt, "insert into test_stmt_get_param.tb_0 using test_stmt_get_param.stb tags(?,?) values (?,?,?,?)") + assert.Equal(t, 0, code, TaosStmtErrStr(stmt)) + + dt, dl, err := TaosStmtGetParam(stmt, 0) // ts + assert.NoError(t, err) + assert.Equal(t, 9, dt) + assert.Equal(t, 8, dl) + + dt, dl, err = TaosStmtGetParam(stmt, 1) // current + assert.NoError(t, err) + assert.Equal(t, 6, dt) + assert.Equal(t, 4, dl) + + dt, dl, err = TaosStmtGetParam(stmt, 2) // voltage + assert.NoError(t, err) + assert.Equal(t, 4, dt) + assert.Equal(t, 4, dl) + + dt, dl, err = TaosStmtGetParam(stmt, 3) // phase + assert.NoError(t, err) + assert.Equal(t, 6, dt) + assert.Equal(t, 4, dl) + + _, _, err = TaosStmtGetParam(stmt, 4) // invalid index + assert.Error(t, err) +} + +func TestStmtJson(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + err = exec(conn, "drop database if exists test_stmt_json") + if err != nil { + t.Error(err) + return + } + }() + err = exec(conn, "create database if not exists test_stmt_json precision 'ms' keep 36500") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "use test_stmt_json") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "create table test_json_stb(ts timestamp, v int) tags (t json)") + if err != nil { + t.Error(err) + return + } + stmt := TaosStmtInitWithReqID(conn, 0xbb123) + defer func() { + code := TaosStmtClose(stmt) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + }() + prepareInsertSql := "insert into ? using test_json_stb tags(?) values (?,?)" + code := TaosStmtPrepare(stmt, prepareInsertSql) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + code = TaosStmtSetTBNameTags(stmt, "ctb1", param.NewParam(1).AddJson([]byte(`{"a":1,"b":"xx"}`)).GetValues()) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + now := time.Now().Round(time.Millisecond) + args := param.NewParam(2).AddTimestamp(now, common.PrecisionMilliSecond).AddInt(1).GetValues() + code = TaosStmtBindParam(stmt, args) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + + code = TaosStmtAddBatch(stmt) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + code = TaosStmtExecute(stmt) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + affected := TaosStmtAffectedRowsOnce(stmt) + assert.Equal(t, 1, affected) + + code = TaosStmtPrepare(stmt, "select * from test_json_stb where t->'a' = ?") + if code != 0 { + errStr := TaosStmtErrStr(stmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + count, code := TaosStmtNumParams(stmt) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + assert.Equal(t, 1, count) + code = TaosStmtBindParam(stmt, param.NewParam(1).AddBigint(1).GetValues()) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + code = TaosStmtExecute(stmt) + if code != 0 { + errStr := TaosStmtErrStr(stmt) + err = taosError.NewError(code, errStr) + t.Error(err) + return + } + res := TaosStmtUseResult(stmt) + + fileCount := TaosNumFields(res) + rh, err := ReadColumn(res, fileCount) + if err != nil { + t.Error(err) + return + } + precision := TaosResultPrecision(res) + var result [][]driver.Value + for { + columns, errCode, block := TaosFetchRawBlock(res) + if errCode != 0 { + errStr := TaosErrorStr(res) + err = taosError.NewError(errCode, errStr) + t.Error(err) + return + } + if columns == 0 { + break + } + r := parser.ReadBlock(block, columns, rh.ColTypes, precision) + result = append(result, r...) + } + t.Log(result) +} diff --git a/driver/wrapper/taosc.go b/driver/wrapper/taosc.go new file mode 100644 index 00000000..e4ccb12b --- /dev/null +++ b/driver/wrapper/taosc.go @@ -0,0 +1,289 @@ +package wrapper + +/* +#cgo CFLAGS: -IC:/TDengine/include -I/usr/include +#cgo linux LDFLAGS: -L/usr/lib -ltaos +#cgo windows LDFLAGS: -LC:/TDengine/driver -ltaos +#cgo darwin LDFLAGS: -L/usr/local/lib -ltaos +#include +#include +#include +#include +extern void QueryCallback(void *param,TAOS_RES *,int code); +extern void FetchRowsCallback(void *param,TAOS_RES *,int numOfRows); +extern void FetchRawBlockCallback(void *param,TAOS_RES *,int numOfRows); +int taos_options_wrapper(TSDB_OPTION option, char *arg) { + return taos_options(option,arg); +}; +void taos_fetch_rows_a_wrapper(TAOS_RES *res, void *param){ + return taos_fetch_rows_a(res,FetchRowsCallback,param); +}; +void taos_query_a_wrapper(TAOS *taos,const char *sql, void *param){ + return taos_query_a(taos,sql,QueryCallback,param); +}; +void taos_query_a_with_req_id_wrapper(TAOS *taos,const char *sql, void *param, int64_t reqID){ + return taos_query_a_with_reqid(taos, sql, QueryCallback, param, reqID); +}; +void taos_fetch_raw_block_a_wrapper(TAOS_RES *res, void *param){ + return taos_fetch_raw_block_a(res,FetchRawBlockCallback,param); +}; +*/ +import "C" +import ( + "strings" + "unsafe" + + "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" + "github.com/taosdata/taosadapter/v3/tools" +) + +// TaosFreeResult void taos_free_result(TAOS_RES *res); +func TaosFreeResult(res unsafe.Pointer) { + C.taos_free_result(res) +} + +// TaosConnect TAOS *taos_connect(const char *ip, const char *user, const char *pass, const char *db, uint16_t port); +func TaosConnect(host, user, pass, db string, port int) (taos unsafe.Pointer, err error) { + cUser := C.CString(user) + defer C.free(unsafe.Pointer(cUser)) + cPass := C.CString(pass) + defer C.free(unsafe.Pointer(cPass)) + cdb := (*C.char)(nil) + if len(db) > 0 { + cdb = C.CString(db) + defer C.free(unsafe.Pointer(cdb)) + } + var taosObj unsafe.Pointer + if len(host) == 0 { + taosObj = C.taos_connect(nil, cUser, cPass, cdb, (C.ushort)(0)) + } else { + cHost := C.CString(host) + defer C.free(unsafe.Pointer(cHost)) + taosObj = C.taos_connect(cHost, cUser, cPass, cdb, (C.ushort)(port)) + } + + if taosObj == nil { + errCode := TaosError(nil) + return nil, errors.NewError(errCode, TaosErrorStr(nil)) + } + return taosObj, nil +} + +// TaosClose void taos_close(TAOS *taos); +func TaosClose(taosConnect unsafe.Pointer) { + C.taos_close(taosConnect) +} + +// TaosQuery TAOS_RES *taos_query(TAOS *taos, const char *sql); +func TaosQuery(taosConnect unsafe.Pointer, sql string) unsafe.Pointer { + cSql := C.CString(sql) + defer C.free(unsafe.Pointer(cSql)) + return unsafe.Pointer(C.taos_query(taosConnect, cSql)) +} + +// TaosQueryWithReqID TAOS_RES *taos_query_with_reqid(TAOS *taos, const char *sql, int64_t reqID); +func TaosQueryWithReqID(taosConn unsafe.Pointer, sql string, reqID int64) unsafe.Pointer { + cSql := C.CString(sql) + defer C.free(unsafe.Pointer(cSql)) + return unsafe.Pointer(C.taos_query_with_reqid(taosConn, cSql, (C.int64_t)(reqID))) +} + +// TaosError int taos_errno(TAOS_RES *tres); +func TaosError(result unsafe.Pointer) int { + return int(C.taos_errno(result)) +} + +// TaosErrorStr char *taos_errstr(TAOS_RES *tres); +func TaosErrorStr(result unsafe.Pointer) string { + return C.GoString(C.taos_errstr(result)) +} + +// TaosFieldCount int taos_field_count(TAOS_RES *res); +func TaosFieldCount(result unsafe.Pointer) int { + return int(C.taos_field_count(result)) +} + +// TaosAffectedRows int taos_affected_rows(TAOS_RES *res); +func TaosAffectedRows(result unsafe.Pointer) int { + return int(C.taos_affected_rows(result)) +} + +// TaosFetchFields TAOS_FIELD *taos_fetch_fields(TAOS_RES *res); +func TaosFetchFields(result unsafe.Pointer) unsafe.Pointer { + return unsafe.Pointer(C.taos_fetch_fields(result)) +} + +// TaosFetchBlock int taos_fetch_block(TAOS_RES *res, TAOS_ROW *rows); +func TaosFetchBlock(result unsafe.Pointer) (int, unsafe.Pointer) { + var block C.TAOS_ROW + b := unsafe.Pointer(&block) + blockSize := int(C.taos_fetch_block(result, (*C.TAOS_ROW)(b))) + return blockSize, b +} + +// TaosResultPrecision int taos_result_precision(TAOS_RES *res); +func TaosResultPrecision(result unsafe.Pointer) int { + return int(C.taos_result_precision(result)) +} + +// TaosNumFields int taos_num_fields(TAOS_RES *res); +func TaosNumFields(result unsafe.Pointer) int { + return int(C.taos_num_fields(result)) +} + +// TaosFetchRow TAOS_ROW taos_fetch_row(TAOS_RES *res); +func TaosFetchRow(result unsafe.Pointer) unsafe.Pointer { + return unsafe.Pointer(C.taos_fetch_row(result)) +} + +// TaosSelectDB int taos_select_db(TAOS *taos, const char *db); +func TaosSelectDB(taosConnect unsafe.Pointer, db string) int { + cDB := C.CString(db) + defer C.free(unsafe.Pointer(cDB)) + return int(C.taos_select_db(taosConnect, cDB)) +} + +// TaosOptions int taos_options(TSDB_OPTION option, const void *arg, ...); +func TaosOptions(option int, value string) int { + cValue := C.CString(value) + defer C.free(unsafe.Pointer(cValue)) + return int(C.taos_options_wrapper((C.TSDB_OPTION)(option), cValue)) +} + +// TaosQueryA void taos_query_a(TAOS *taos, const char *sql, void (*fp)(void *param, TAOS_RES *, int code), void *param); +func TaosQueryA(taosConnect unsafe.Pointer, sql string, caller cgo.Handle) { + cSql := C.CString(sql) + defer C.free(unsafe.Pointer(cSql)) + C.taos_query_a_wrapper(taosConnect, cSql, caller.Pointer()) +} + +// TaosQueryAWithReqID void taos_query_a_with_reqid(TAOS *taos, const char *sql, __taos_async_fn_t fp, void *param, int64_t reqid); +func TaosQueryAWithReqID(taosConn unsafe.Pointer, sql string, caller cgo.Handle, reqID int64) { + cSql := C.CString(sql) + defer C.free(unsafe.Pointer(cSql)) + C.taos_query_a_with_req_id_wrapper(taosConn, cSql, caller.Pointer(), (C.int64_t)(reqID)) +} + +// TaosFetchRowsA void taos_fetch_rows_a(TAOS_RES *res, void (*fp)(void *param, TAOS_RES *, int numOfRows), void *param); +func TaosFetchRowsA(res unsafe.Pointer, caller cgo.Handle) { + C.taos_fetch_rows_a_wrapper(res, caller.Pointer()) +} + +// TaosResetCurrentDB void taos_reset_current_db(TAOS *taos); +func TaosResetCurrentDB(taosConnect unsafe.Pointer) { + C.taos_reset_current_db(taosConnect) +} + +// TaosValidateSql int taos_validate_sql(TAOS *taos, const char *sql); +func TaosValidateSql(taosConnect unsafe.Pointer, sql string) int { + cSql := C.CString(sql) + defer C.free(unsafe.Pointer(cSql)) + return int(C.taos_validate_sql(taosConnect, cSql)) +} + +// TaosIsUpdateQuery bool taos_is_update_query(TAOS_RES *res); +func TaosIsUpdateQuery(res unsafe.Pointer) bool { + return bool(C.taos_is_update_query(res)) +} + +// TaosFetchLengths int* taos_fetch_lengths(TAOS_RES *res); +func TaosFetchLengths(res unsafe.Pointer) unsafe.Pointer { + return unsafe.Pointer(C.taos_fetch_lengths(res)) +} + +// TaosFetchRawBlockA void taos_fetch_raw_block_a(TAOS_RES* res, __taos_async_fn_t fp, void* param); +func TaosFetchRawBlockA(res unsafe.Pointer, caller cgo.Handle) { + C.taos_fetch_raw_block_a_wrapper(res, caller.Pointer()) +} + +// TaosGetRawBlock const void *taos_get_raw_block(TAOS_RES* res); +func TaosGetRawBlock(result unsafe.Pointer) unsafe.Pointer { + return unsafe.Pointer(C.taos_get_raw_block(result)) +} + +// TaosGetClientInfo const char *taos_get_client_info(); +func TaosGetClientInfo() string { + return C.GoString(C.taos_get_client_info()) +} + +// TaosLoadTableInfo taos_load_table_info(TAOS *taos, const char* tableNameList); +func TaosLoadTableInfo(taosConnect unsafe.Pointer, tableNameList []string) int { + s := strings.Join(tableNameList, ",") + buf := C.CString(s) + defer C.free(unsafe.Pointer(buf)) + return int(C.taos_load_table_info(taosConnect, buf)) +} + +// TaosGetTableVgID +// DLL_EXPORT int taos_get_table_vgId(TAOS *taos, const char *db, const char *table, int *vgId) +func TaosGetTableVgID(conn unsafe.Pointer, db, table string) (vgID int, code int) { + cDB := C.CString(db) + defer C.free(unsafe.Pointer(cDB)) + cTable := C.CString(table) + defer C.free(unsafe.Pointer(cTable)) + + code = int(C.taos_get_table_vgId(conn, cDB, cTable, (*C.int)(unsafe.Pointer(&vgID)))) + return +} + +// TaosGetTablesVgID DLL_EXPORT int taos_get_tables_vgId(TAOS *taos, const char *db, const char *table[], int tableNum, int *vgId) +func TaosGetTablesVgID(conn unsafe.Pointer, db string, tables []string) (vgIDs []int, code int) { + cDB := C.CString(db) + defer C.free(unsafe.Pointer(cDB)) + numTables := len(tables) + cTables := make([]*C.char, numTables) + needFree := make([]unsafe.Pointer, numTables) + defer func() { + for _, p := range needFree { + C.free(p) + } + }() + for i, table := range tables { + cTable := C.CString(table) + needFree[i] = unsafe.Pointer(cTable) + cTables[i] = cTable + } + p := C.malloc(C.sizeof_int * C.size_t(numTables)) + defer C.free(p) + code = int(C.taos_get_tables_vgId(conn, cDB, (**C.char)(&cTables[0]), (C.int)(numTables), (*C.int)(p))) + if code != 0 { + return nil, code + } + vgIDs = make([]int, numTables) + for i := 0; i < numTables; i++ { + vgIDs[i] = int(*(*C.int)(tools.AddPointer(p, uintptr(C.sizeof_int*C.int(i))))) + } + return +} + +//typedef enum { +//TAOS_CONN_MODE_BI = 0, +//} TAOS_CONN_MODE; +// +//DLL_EXPORT int taos_set_conn_mode(TAOS* taos, int mode, int value); + +func TaosSetConnMode(conn unsafe.Pointer, mode int, value int) int { + return int(C.taos_set_conn_mode(conn, C.int(mode), C.int(value))) +} + +// TaosGetCurrentDB DLL_EXPORT int taos_get_current_db(TAOS *taos, char *database, int len, int *required) +func TaosGetCurrentDB(conn unsafe.Pointer) (db string, err error) { + cDb := (*C.char)(C.malloc(195)) + defer C.free(unsafe.Pointer(cDb)) + var required int + + code := C.taos_get_current_db(conn, cDb, C.int(195), (*C.int)(unsafe.Pointer(&required))) + if code != 0 { + err = errors.NewError(int(code), TaosErrorStr(nil)) + } + db = C.GoString(cDb) + + return +} + +// TaosGetServerInfo DLL_EXPORT const char *taos_get_server_info(TAOS *taos) +func TaosGetServerInfo(conn unsafe.Pointer) string { + info := C.taos_get_server_info(conn) + return C.GoString(info) +} diff --git a/driver/wrapper/taosc_test.go b/driver/wrapper/taosc_test.go new file mode 100644 index 00000000..7a2c268e --- /dev/null +++ b/driver/wrapper/taosc_test.go @@ -0,0 +1,607 @@ +package wrapper + +import ( + "database/sql/driver" + "fmt" + "io" + "testing" + "time" + "unsafe" + + "github.com/stretchr/testify/assert" + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" +) + +// @author: xftan +// @date: 2022/1/27 17:29 +// @description: test taos_options +func TestTaosOptions(t *testing.T) { + type args struct { + option int + value string + } + tests := []struct { + name string + args args + want int + }{ + { + name: "test_options", + args: args{ + option: common.TSDB_OPTION_CONFIGDIR, + value: "/etc/taos", + }, + want: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := TaosOptions(tt.args.option, tt.args.value); got != tt.want { + t.Errorf("TaosOptions() = %v, want %v", got, tt.want) + } + }) + } +} + +type result struct { + res unsafe.Pointer + n int +} + +type TestCaller struct { + QueryResult chan *result + FetchResult chan *result +} + +func NewTestCaller() *TestCaller { + return &TestCaller{ + QueryResult: make(chan *result), + FetchResult: make(chan *result), + } +} + +func (t *TestCaller) QueryCall(res unsafe.Pointer, code int) { + t.QueryResult <- &result{ + res: res, + n: code, + } +} + +func (t *TestCaller) FetchCall(res unsafe.Pointer, numOfRows int) { + t.FetchResult <- &result{ + res: res, + n: numOfRows, + } +} + +// @author: xftan +// @date: 2022/1/27 17:29 +// @description: test taos_query_a +func TestTaosQueryA(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + var caller = NewTestCaller() + type args struct { + taosConnect unsafe.Pointer + sql string + caller *TestCaller + } + tests := []struct { + name string + args args + }{ + { + name: "test", + args: args{ + taosConnect: conn, + sql: "show databases", + caller: caller, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := cgo.NewHandle(tt.args.caller) + go TaosQueryA(tt.args.taosConnect, tt.args.sql, p) + r := <-tt.args.caller.QueryResult + t.Log("query finish") + count := TaosNumFields(r.res) + rowsHeader, err := ReadColumn(r.res, count) + precision := TaosResultPrecision(r.res) + if err != nil { + t.Error(err) + return + } + t.Logf("%#v", rowsHeader) + if r.n != 0 { + t.Error("query result", r.n) + return + } + res := r.res + for { + go TaosFetchRowsA(res, p) + r = <-tt.args.caller.FetchResult + if r.n == 0 { + t.Log("success") + TaosFreeResult(r.res) + break + } else { + res = r.res + for i := 0; i < r.n; i++ { + values := make([]driver.Value, len(rowsHeader.ColNames)) + row := TaosFetchRow(res) + lengths := FetchLengths(res, len(rowsHeader.ColNames)) + for j := range rowsHeader.ColTypes { + if row == nil { + t.Error(io.EOF) + return + } + values[j] = FetchRow(row, j, rowsHeader.ColTypes[j], lengths[j], precision) + } + } + t.Log("fetch rows a", r.n) + } + } + }) + } +} + +// @author: xftan +// @date: 2023/10/13 11:31 +// @description: test taos error +func TestError(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + res := TaosQuery(conn, "asd") + code := TaosError(res) + assert.NotEqual(t, code, 0) + errStr := TaosErrorStr(res) + assert.NotEmpty(t, errStr) +} + +// @author: xftan +// @date: 2023/10/13 11:31 +// @description: test affected rows +func TestAffectedRows(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + res := TaosQuery(conn, "drop database if exists affected_rows_test") + code := TaosError(res) + if code != 0 { + t.Error(errors.NewError(code, TaosErrorStr(res))) + return + } + TaosFreeResult(res) + }() + res := TaosQuery(conn, "create database if not exists affected_rows_test") + code := TaosError(res) + if code != 0 { + t.Error(errors.NewError(code, TaosErrorStr(res))) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, "create table if not exists affected_rows_test.t0(ts timestamp,v int)") + code = TaosError(res) + if code != 0 { + t.Error(errors.NewError(code, TaosErrorStr(res))) + return + } + TaosFreeResult(res) + res = TaosQuery(conn, "insert into affected_rows_test.t0 values(now,1)") + code = TaosError(res) + if code != 0 { + t.Error(errors.NewError(code, TaosErrorStr(res))) + return + } + affected := TaosAffectedRows(res) + assert.Equal(t, 1, affected) +} + +// @author: xftan +// @date: 2022/1/27 17:29 +// @description: test taos_reset_current_db +func TestTaosResetCurrentDB(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + type args struct { + taosConnect unsafe.Pointer + } + tests := []struct { + name string + args args + }{ + { + name: "test", + args: args{ + taosConnect: conn, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err = exec(tt.args.taosConnect, "create database if not exists log") + if err != nil { + t.Error(err) + return + } + TaosSelectDB(tt.args.taosConnect, "log") + result := TaosQuery(tt.args.taosConnect, "select database()") + code := TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + row := TaosFetchRow(result) + lengths := FetchLengths(result, 1) + currentDB := FetchRow(row, 0, 10, lengths[0]) + assert.Equal(t, "log", currentDB) + TaosFreeResult(result) + TaosResetCurrentDB(tt.args.taosConnect) + result = TaosQuery(tt.args.taosConnect, "select database()") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + row = TaosFetchRow(result) + lengths = FetchLengths(result, 1) + currentDB = FetchRow(row, 0, 10, lengths[0]) + assert.Nil(t, currentDB) + TaosFreeResult(result) + }) + } +} + +// @author: xftan +// @date: 2022/1/27 17:30 +// @description: test taos_validate_sql +func TestTaosValidateSql(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + type args struct { + taosConnect unsafe.Pointer + sql string + } + tests := []struct { + name string + args args + want int + }{ + { + name: "valid", + args: args{ + taosConnect: conn, + sql: "show grants", + }, + want: 0, + }, + { + name: "TSC_SQL_SYNTAX_ERROR", + args: args{ + taosConnect: conn, + sql: "slect 1", + }, + want: 9728, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := TaosValidateSql(tt.args.taosConnect, tt.args.sql); got&0xffff != tt.want { + t.Errorf("TaosValidateSql() = %v, want %v", got&0xffff, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 17:30 +// @description: test taos_is_update_query +func TestTaosIsUpdateQuery(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + tests := []struct { + name string + want bool + }{ + { + name: "create database if not exists is_update", + want: true, + }, + { + name: "drop database if exists is_update", + want: true, + }, + { + name: "show log.stables", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := TaosQuery(conn, tt.name) + defer TaosFreeResult(result) + if got := TaosIsUpdateQuery(result); got != tt.want { + t.Errorf("TaosIsUpdateQuery() = %v, want %v", got, tt.want) + } + }) + } +} + +// @author: xftan +// @date: 2022/1/27 17:30 +// @description: taos async raw block +func TestTaosResultBlock(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + var caller = NewTestCaller() + type args struct { + taosConnect unsafe.Pointer + sql string + caller *TestCaller + } + tests := []struct { + name string + args args + }{ + { + name: "test", + args: args{ + taosConnect: conn, + sql: "show users", + caller: caller, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := cgo.NewHandle(tt.args.caller) + go TaosQueryA(tt.args.taosConnect, tt.args.sql, p) + r := <-tt.args.caller.QueryResult + t.Log("query finish") + count := TaosNumFields(r.res) + rowsHeader, err := ReadColumn(r.res, count) + if err != nil { + t.Error(err) + return + } + //t.Logf("%#v", rowsHeader) + if r.n != 0 { + t.Error("query result", r.n) + return + } + res := r.res + precision := TaosResultPrecision(res) + for { + go TaosFetchRawBlockA(res, p) + r = <-tt.args.caller.FetchResult + if r.n == 0 { + t.Log("success") + TaosFreeResult(r.res) + break + } else { + res = r.res + block := TaosGetRawBlock(res) + assert.NotNil(t, block) + values := parser.ReadBlock(block, r.n, rowsHeader.ColTypes, precision) + _ = values + t.Log(values) + } + } + }) + } +} + +// @author: xftan +// @date: 2023/10/13 11:31 +// @description: test taos_get_client_info +func TestTaosGetClientInfo(t *testing.T) { + s := TaosGetClientInfo() + assert.NotEmpty(t, s) +} + +// @author: xftan +// @date: 2023/10/13 11:31 +// @description: test taos_load_table_info +func TestTaosLoadTableInfo(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + err = exec(conn, "drop database if exists info1") + if err != nil { + t.Error(err) + return + } + defer func() { + err = exec(conn, "drop database if exists info1") + if err != nil { + t.Error(err) + return + } + }() + err = exec(conn, "create database info1") + if err != nil { + t.Error(err) + return + } + err = exec(conn, "create table info1.t(ts timestamp,v int)") + if err != nil { + t.Error(err) + return + } + code := TaosLoadTableInfo(conn, []string{"info1.t"}) + if code != 0 { + errStr := TaosErrorStr(nil) + t.Error(errors.NewError(code, errStr)) + return + } + +} + +// @author: xftan +// @date: 2023/10/13 11:32 +// @description: test taos_get_table_vgId +func TestTaosGetTableVgID(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Fatal(err) + } + defer TaosClose(conn) + dbName := "table_vg_id_test" + + _ = exec(conn, fmt.Sprintf("drop database if exists %s", dbName)) + defer func() { + _ = exec(conn, fmt.Sprintf("drop database if exists %s", dbName)) + }() + if err = exec(conn, fmt.Sprintf("create database %s", dbName)); err != nil { + t.Fatal(err) + } + if err = exec(conn, fmt.Sprintf("create stable %s.meters (ts timestamp, current float, voltage int, phase float) "+ + "tags (location binary(64), groupId int)", dbName)); err != nil { + t.Fatal(err) + } + if err = exec(conn, fmt.Sprintf("create table %s.d0 using %s.meters tags ('California.SanFrancisco', 1)", dbName, dbName)); err != nil { + t.Fatal(err) + } + if err = exec(conn, fmt.Sprintf("create table %s.d1 using %s.meters tags ('California.LosAngles', 2)", dbName, dbName)); err != nil { + t.Fatal(err) + } + + vg1, code := TaosGetTableVgID(conn, dbName, "d0") + if code != 0 { + t.Fatal("fail") + } + vg2, code := TaosGetTableVgID(conn, dbName, "d0") + if code != 0 { + t.Fatal("fail") + } + if vg1 != vg2 { + t.Fatal("fail") + } + _, code = TaosGetTableVgID(conn, dbName, "d1") + if code != 0 { + t.Fatal("fail") + } + _, code = TaosGetTableVgID(conn, dbName, "d2") + if code != 0 { + t.Fatal("fail") + } +} + +// @author: xftan +// @date: 2023/10/13 11:32 +// @description: test taos_get_tables_vgId +func TestTaosGetTablesVgID(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Fatal(err) + } + defer TaosClose(conn) + dbName := "tables_vg_id_test" + + _ = exec(conn, fmt.Sprintf("drop database if exists %s", dbName)) + defer func() { + _ = exec(conn, fmt.Sprintf("drop database if exists %s", dbName)) + }() + if err = exec(conn, fmt.Sprintf("create database %s", dbName)); err != nil { + t.Fatal(err) + } + if err = exec(conn, fmt.Sprintf("create stable %s.meters (ts timestamp, current float, voltage int, phase float) "+ + "tags (location binary(64), groupId int)", dbName)); err != nil { + t.Fatal(err) + } + if err = exec(conn, fmt.Sprintf("create table %s.d0 using %s.meters tags ('California.SanFrancisco', 1)", dbName, dbName)); err != nil { + t.Fatal(err) + } + if err = exec(conn, fmt.Sprintf("create table %s.d1 using %s.meters tags ('California.LosAngles', 2)", dbName, dbName)); err != nil { + t.Fatal(err) + } + var vgs1 []int + var vgs2 []int + var code int + now := time.Now() + vgs1, code = TaosGetTablesVgID(conn, dbName, []string{"d0", "d1"}) + t.Log(time.Since(now)) + if code != 0 { + t.Fatal("fail") + } + assert.Equal(t, 2, len(vgs1)) + vgs2, code = TaosGetTablesVgID(conn, dbName, []string{"d0", "d1"}) + if code != 0 { + t.Fatal("fail") + } + assert.Equal(t, 2, len(vgs2)) + assert.Equal(t, vgs2, vgs1) +} + +func TestTaosSetConnMode(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + assert.NoError(t, err) + defer TaosClose(conn) + code := TaosSetConnMode(conn, 0, 1) + if code != 0 { + t.Errorf("TaosSetConnMode() error code= %d, msg: %s", code, TaosErrorStr(nil)) + } +} + +func TestTaosGetCurrentDB(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + assert.NoError(t, err) + defer TaosClose(conn) + dbName := "current_db_test" + _ = exec(conn, fmt.Sprintf("drop database if exists %s", dbName)) + err = exec(conn, fmt.Sprintf("create database %s", dbName)) + assert.NoError(t, err) + defer func() { + _ = exec(conn, fmt.Sprintf("drop database if exists %s", dbName)) + }() + _ = exec(conn, fmt.Sprintf("use %s", dbName)) + db, err := TaosGetCurrentDB(conn) + assert.NoError(t, err) + assert.Equal(t, dbName, db) +} + +func TestTaosGetServerInfo(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + assert.NoError(t, err) + defer TaosClose(conn) + info := TaosGetServerInfo(conn) + assert.NotEmpty(t, info) +} diff --git a/driver/wrapper/tmq.go b/driver/wrapper/tmq.go new file mode 100644 index 00000000..823dc426 --- /dev/null +++ b/driver/wrapper/tmq.go @@ -0,0 +1,334 @@ +package wrapper + +/* +#include +#include +#include +#include +extern void TMQCommitCB(tmq_t *, int32_t, void *param); +extern void TMQAutoCommitCB(tmq_t *, int32_t, void *param); +extern void TMQCommitOffsetCB(tmq_t *, int32_t, void *param); +*/ +import "C" +import ( + "sync" + "unsafe" + + "github.com/taosdata/taosadapter/v3/driver/common/tmq" + "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" + "github.com/taosdata/taosadapter/v3/tools" +) + +var tmqCommitCallbackResultPool = sync.Pool{} + +type TMQCommitCallbackResult struct { + ErrCode int32 + Consumer unsafe.Pointer +} + +func (t *TMQCommitCallbackResult) GetError() error { + if t.ErrCode == 0 { + return nil + } + errStr := TMQErr2Str(t.ErrCode) + return errors.NewError(int(t.ErrCode), errStr) +} + +func GetTMQCommitCallbackResult(errCode int32, consumer unsafe.Pointer) *TMQCommitCallbackResult { + t, ok := tmqCommitCallbackResultPool.Get().(*TMQCommitCallbackResult) + if ok { + t.ErrCode = errCode + t.Consumer = consumer + return t + } + return &TMQCommitCallbackResult{ErrCode: errCode, Consumer: consumer} +} + +func PutTMQCommitCallbackResult(result *TMQCommitCallbackResult) { + tmqCommitCallbackResultPool.Put(result) +} + +// TMQConfNew tmq_conf_t *tmq_conf_new(); +func TMQConfNew() unsafe.Pointer { + return unsafe.Pointer(C.tmq_conf_new()) +} + +// TMQConfSet tmq_conf_res_t tmq_conf_set(tmq_conf_t *conf, const char *key, const char *value); +func TMQConfSet(conf unsafe.Pointer, key string, value string) int32 { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + cValue := C.CString(value) + defer C.free(unsafe.Pointer(cValue)) + return int32(C.tmq_conf_set((*C.struct_tmq_conf_t)(conf), cKey, cValue)) +} + +// TMQConfDestroy void tmq_conf_destroy(tmq_conf_t *conf); +func TMQConfDestroy(conf unsafe.Pointer) { + C.tmq_conf_destroy((*C.struct_tmq_conf_t)(conf)) +} + +// TMQConfSetAutoCommitCB DLL_EXPORT void tmq_conf_set_auto_commit_cb(tmq_conf_t *conf, tmq_commit_cb *cb, void *param); +func TMQConfSetAutoCommitCB(conf unsafe.Pointer, h cgo.Handle) { + C.tmq_conf_set_auto_commit_cb((*C.struct_tmq_conf_t)(conf), (*C.tmq_commit_cb)(C.TMQAutoCommitCB), h.Pointer()) +} + +// TMQCommitAsync DLL_EXPORT void tmq_commit_async(tmq_t *tmq, const TAOS_RES *msg, tmq_commit_cb *cb, void *param); +func TMQCommitAsync(consumer unsafe.Pointer, message unsafe.Pointer, h cgo.Handle) { + C.tmq_commit_async((*C.tmq_t)(consumer), message, (*C.tmq_commit_cb)(C.TMQCommitCB), h.Pointer()) +} + +// TMQCommitSync DLL_EXPORT int32_t tmq_commit_sync(tmq_t *tmq, const TAOS_RES *msg); +func TMQCommitSync(consumer unsafe.Pointer, message unsafe.Pointer) int32 { + return int32(C.tmq_commit_sync((*C.tmq_t)(consumer), message)) +} + +// TMQListNew tmq_list_t *tmq_list_new(); +func TMQListNew() unsafe.Pointer { + return unsafe.Pointer(C.tmq_list_new()) +} + +// TMQListAppend int32_t tmq_list_append(tmq_list_t *, const char *); +func TMQListAppend(list unsafe.Pointer, str string) int32 { + cStr := C.CString(str) + defer C.free(unsafe.Pointer(cStr)) + return int32(C.tmq_list_append((*C.tmq_list_t)(list), cStr)) +} + +// TMQListDestroy void tmq_list_destroy(tmq_list_t *); +func TMQListDestroy(list unsafe.Pointer) { + C.tmq_list_destroy((*C.tmq_list_t)(list)) +} + +// TMQListGetSize int32_t tmq_list_get_size(const tmq_list_t *); +func TMQListGetSize(list unsafe.Pointer) int32 { + return int32(C.tmq_list_get_size((*C.tmq_list_t)(list))) +} + +// TMQListToCArray char **tmq_list_to_c_array(const tmq_list_t *); +func TMQListToCArray(list unsafe.Pointer, size int) []string { + head := unsafe.Pointer(C.tmq_list_to_c_array((*C.tmq_list_t)(list))) + result := make([]string, size) + for i := 0; i < size; i++ { + result[i] = C.GoString(*(**C.char)(tools.AddPointer(head, PointerSize*uintptr(i)))) + } + return result +} + +// TMQConsumerNew tmq_t *tmq_consumer_new1(tmq_conf_t *conf, char *errstr, int32_t errstrLen); +func TMQConsumerNew(conf unsafe.Pointer) (unsafe.Pointer, error) { + p := (*C.char)(C.calloc(C.size_t(C.uint(1024)), C.size_t(C.uint(1024)))) + defer C.free(unsafe.Pointer(p)) + tmq := unsafe.Pointer(C.tmq_consumer_new((*C.struct_tmq_conf_t)(conf), p, C.int32_t(1024))) + errStr := C.GoString(p) + if len(errStr) > 0 { + return nil, errors.NewError(-1, errStr) + } + if tmq == nil { + return nil, errors.NewError(-1, "new consumer return nil") + } + return tmq, nil +} + +// TMQErr2Str const char *tmq_err2str(int32_t); +func TMQErr2Str(code int32) string { + return C.GoString(C.tmq_err2str((C.int32_t)(code))) +} + +// TMQSubscribe tmq_resp_err_t tmq_subscribe(tmq_t *tmq, tmq_list_t *topic_list); +func TMQSubscribe(consumer unsafe.Pointer, topicList unsafe.Pointer) int32 { + return int32(C.tmq_subscribe((*C.tmq_t)(consumer), (*C.tmq_list_t)(topicList))) +} + +// TMQUnsubscribe tmq_resp_err_t tmq_unsubscribe(tmq_t *tmq); +func TMQUnsubscribe(consumer unsafe.Pointer) int32 { + return int32(C.tmq_unsubscribe((*C.tmq_t)(consumer))) +} + +// TMQSubscription tmq_resp_err_t tmq_subscription(tmq_t *tmq, tmq_list_t **topics); +func TMQSubscription(consumer unsafe.Pointer) (int32, unsafe.Pointer) { + list := C.tmq_list_new() + code := int32(C.tmq_subscription( + (*C.tmq_t)(consumer), + (**C.tmq_list_t)(&list), + )) + return code, unsafe.Pointer(list) +} + +// TMQConsumerPoll TAOS_RES *tmq_consumer_poll(tmq_t *tmq, int64_t blocking_time); +func TMQConsumerPoll(consumer unsafe.Pointer, blockingTime int64) unsafe.Pointer { + return unsafe.Pointer(C.tmq_consumer_poll((*C.tmq_t)(consumer), (C.int64_t)(blockingTime))) +} + +// TMQConsumerClose tmq_resp_err_t tmq_consumer_close(tmq_t *tmq); +func TMQConsumerClose(consumer unsafe.Pointer) int32 { + return int32(C.tmq_consumer_close((*C.tmq_t)(consumer))) +} + +// TMQGetTopicName char *tmq_get_topic_name(tmq_message_t *message); +func TMQGetTopicName(message unsafe.Pointer) string { + return C.GoString(C.tmq_get_topic_name(message)) +} + +// TMQGetVgroupID int32_t tmq_get_vgroup_id(tmq_message_t *message); +func TMQGetVgroupID(message unsafe.Pointer) int32 { + return int32(C.tmq_get_vgroup_id(message)) +} + +// TMQGetTableName DLL_EXPORT const char *tmq_get_table_name(TAOS_RES *res); +func TMQGetTableName(message unsafe.Pointer) string { + return C.GoString(C.tmq_get_table_name(message)) +} + +// TMQGetDBName const char *tmq_get_db_name(TAOS_RES *res); +func TMQGetDBName(message unsafe.Pointer) string { + return C.GoString(C.tmq_get_db_name(message)) +} + +// TMQGetResType DLL_EXPORT tmq_res_t tmq_get_res_type(TAOS_RES *res); +func TMQGetResType(message unsafe.Pointer) int32 { + return int32(C.tmq_get_res_type(message)) +} + +// TMQGetRaw DLL_EXPORT int32_t tmq_get_raw(TAOS_RES *res, tmq_raw_data *raw); +func TMQGetRaw(message unsafe.Pointer) (int32, unsafe.Pointer) { + var cRawMeta C.TAOS_FIELD_E + m := unsafe.Pointer(&cRawMeta) + code := int32(C.tmq_get_raw(message, (*C.tmq_raw_data)(m))) + return code, m +} + +// TMQWriteRaw DLL_EXPORT int32_t tmq_write_raw(TAOS *taos, tmq_raw_data raw); +func TMQWriteRaw(conn unsafe.Pointer, raw unsafe.Pointer) int32 { + return int32(C.tmq_write_raw(conn, (C.struct_tmq_raw_data)(*(*C.struct_tmq_raw_data)(raw)))) +} + +// TMQFreeRaw DLL_EXPORT void tmq_free_raw(tmq_raw_data raw); +func TMQFreeRaw(raw unsafe.Pointer) { + C.tmq_free_raw((C.struct_tmq_raw_data)(*(*C.struct_tmq_raw_data)(raw))) +} + +// TMQGetJsonMeta DLL_EXPORT char *tmq_get_json_meta(TAOS_RES *res); // Returning null means error. Returned result need to be freed by tmq_free_json_meta +func TMQGetJsonMeta(message unsafe.Pointer) unsafe.Pointer { + p := unsafe.Pointer(C.tmq_get_json_meta(message)) + return p +} + +// TMQFreeJsonMeta DLL_EXPORT void tmq_free_json_meta(char* jsonMeta); +func TMQFreeJsonMeta(jsonMeta unsafe.Pointer) { + C.tmq_free_json_meta((*C.char)(jsonMeta)) +} + +func ParseRawMeta(rawMeta unsafe.Pointer) (length uint32, metaType uint16, data unsafe.Pointer) { + meta := *(*C.tmq_raw_data)(rawMeta) + length = uint32(meta.raw_len) + metaType = uint16(meta.raw_type) + data = meta.raw + return +} + +func ParseJsonMeta(jsonMeta unsafe.Pointer) []byte { + var binaryVal []byte + if jsonMeta != nil { + i := 0 + c := byte(0) + for { + c = *((*byte)(unsafe.Pointer(uintptr(jsonMeta) + uintptr(i)))) + if c != 0 { + binaryVal = append(binaryVal, c) + i += 1 + } else { + break + } + } + } + return binaryVal +} + +func BuildRawMeta(length uint32, metaType uint16, data unsafe.Pointer) unsafe.Pointer { + meta := C.struct_tmq_raw_data{} + meta.raw = data + meta.raw_len = (C.uint32_t)(length) + meta.raw_type = (C.uint16_t)(metaType) + return unsafe.Pointer(&meta) +} + +// TMQGetTopicAssignment DLL_EXPORT int32_t tmq_get_topic_assignment(tmq_t *tmq, const char* pTopicName, tmq_topic_assignment **assignment, int32_t *numOfAssignment) +func TMQGetTopicAssignment(consumer unsafe.Pointer, topic string) (int32, []*tmq.Assignment) { + var assignment *C.tmq_topic_assignment + var numOfAssignment int32 + topicName := C.CString(topic) + defer C.free(unsafe.Pointer(topicName)) + code := int32(C.tmq_get_topic_assignment((*C.tmq_t)(consumer), topicName, (**C.tmq_topic_assignment)(unsafe.Pointer(&assignment)), (*C.int32_t)(&numOfAssignment))) + if code != 0 { + return code, nil + } + if assignment == nil { + return 0, nil + } + defer TMQFreeAssignment(unsafe.Pointer(assignment)) + result := make([]*tmq.Assignment, numOfAssignment) + for i := 0; i < int(numOfAssignment); i++ { + item := *(*C.tmq_topic_assignment)(unsafe.Pointer(uintptr(unsafe.Pointer(assignment)) + uintptr(C.sizeof_struct_tmq_topic_assignment*C.int(i)))) + result[i] = &tmq.Assignment{ + VGroupID: int32(item.vgId), + Offset: int64(item.currentOffset), + Begin: int64(item.begin), + End: int64(item.end), + } + } + return 0, result +} + +// TMQOffsetSeek DLL_EXPORT int32_t tmq_offset_seek(tmq_t* tmq, const char* pTopicName, int32_t vgroupHandle, int64_t offset); +func TMQOffsetSeek(consumer unsafe.Pointer, topic string, vGroupID int32, offset int64) int32 { + topicName := C.CString(topic) + defer C.free(unsafe.Pointer(topicName)) + return int32(C.tmq_offset_seek((*C.tmq_t)(consumer), topicName, (C.int32_t)(vGroupID), (C.int64_t)(offset))) +} + +// TMQGetVgroupOffset DLL_EXPORT int64_t tmq_get_vgroup_offset(TAOS_RES* res, int32_t vgroupId); +func TMQGetVgroupOffset(message unsafe.Pointer) int64 { + return int64(C.tmq_get_vgroup_offset(message)) +} + +// TMQFreeAssignment DLL_EXPORT void tmq_free_assignment(tmq_topic_assignment* pAssignment); +func TMQFreeAssignment(assignment unsafe.Pointer) { + if assignment == nil { + return + } + C.tmq_free_assignment((*C.tmq_topic_assignment)(assignment)) +} + +// TMQPosition DLL_EXPORT int64_t tmq_position(tmq_t *tmq, const char *pTopicName, int32_t vgId); +func TMQPosition(consumer unsafe.Pointer, topic string, vGroupID int32) int64 { + topicName := C.CString(topic) + defer C.free(unsafe.Pointer(topicName)) + return int64(C.tmq_position((*C.tmq_t)(consumer), topicName, (C.int32_t)(vGroupID))) +} + +// TMQCommitted DLL_EXPORT int64_t tmq_committed(tmq_t *tmq, const char *pTopicName, int32_t vgId); +func TMQCommitted(consumer unsafe.Pointer, topic string, vGroupID int32) int64 { + topicName := C.CString(topic) + defer C.free(unsafe.Pointer(topicName)) + return int64(C.tmq_committed((*C.tmq_t)(consumer), topicName, (C.int32_t)(vGroupID))) +} + +// TMQCommitOffsetSync DLL_EXPORT int32_t tmq_commit_offset_sync(tmq_t *tmq, const char *pTopicName, int32_t vgId, int64_t offset); +func TMQCommitOffsetSync(consumer unsafe.Pointer, topic string, vGroupID int32, offset int64) int32 { + topicName := C.CString(topic) + defer C.free(unsafe.Pointer(topicName)) + return int32(C.tmq_commit_offset_sync((*C.tmq_t)(consumer), topicName, (C.int32_t)(vGroupID), (C.int64_t)(offset))) +} + +// TMQCommitOffsetAsync DLL_EXPORT void tmq_commit_offset_async(tmq_t *tmq, const char *pTopicName, int32_t vgId, int64_t offset, tmq_commit_cb *cb, void *param); +func TMQCommitOffsetAsync(consumer unsafe.Pointer, topic string, vGroupID int32, offset int64, h cgo.Handle) { + topicName := C.CString(topic) + defer C.free(unsafe.Pointer(topicName)) + C.tmq_commit_offset_async((*C.tmq_t)(consumer), topicName, (C.int32_t)(vGroupID), (C.int64_t)(offset), (*C.tmq_commit_cb)(C.TMQCommitOffsetCB), h.Pointer()) +} + +// TMQGetConnect TAOS *tmq_get_connect(tmq_t *tmq) +func TMQGetConnect(consumer unsafe.Pointer) unsafe.Pointer { + return unsafe.Pointer(C.tmq_get_connect((*C.tmq_t)(consumer))) +} diff --git a/driver/wrapper/tmq_test.go b/driver/wrapper/tmq_test.go new file mode 100644 index 00000000..4f3879dc --- /dev/null +++ b/driver/wrapper/tmq_test.go @@ -0,0 +1,2012 @@ +package wrapper + +import ( + "database/sql/driver" + "testing" + "time" + "unsafe" + + jsoniter "github.com/json-iterator/go" + "github.com/stretchr/testify/assert" + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + tmqcommon "github.com/taosdata/taosadapter/v3/driver/common/tmq" + "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" +) + +// @author: xftan +// @date: 2023/10/13 11:32 +// @description: test tmq +func TestTMQ(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + result := TaosQuery(conn, "drop database if exists abc1") + code := TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + }() + result := TaosQuery(conn, "create database if not exists abc1 vgroups 2 WAL_RETENTION_PERIOD 86400") + code := TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "use abc1") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "create stable if not exists st1 (ts timestamp, c1 int, c2 float, c3 binary(10)) tags(t1 int)") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "create table if not exists ct0 using st1 tags(1000)") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "create table if not exists ct1 using st1 tags(2000)") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "create table if not exists ct3 using st1 tags(3000)") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + //create topic + defer func() { + result = TaosQuery(conn, "drop topic if exists topic_ctb_column") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + }() + result = TaosQuery(conn, "create topic if not exists topic_ctb_column as select ts, c1 from ct1") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + defer func() { + result = TaosQuery(conn, "drop topic if exists topic_ctb_column") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + }() + go func() { + for i := 0; i < 5; i++ { + result = TaosQuery(conn, "insert into ct1 values(now,1,2,'1')") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + time.Sleep(time.Millisecond) + } + }() + //build consumer + conf := TMQConfNew() + TMQConfSet(conf, "msg.with.table.name", "true") + // auto commit default is true then the commitCallback function will be called after 5 seconds + TMQConfSet(conf, "enable.auto.commit", "true") + TMQConfSet(conf, "group.id", "tg2") + TMQConfSet(conf, "auto.offset.reset", "earliest") + c := make(chan *TMQCommitCallbackResult, 1) + h := cgo.NewHandle(c) + TMQConfSetAutoCommitCB(conf, h) + go func() { + for r := range c { + t.Log("auto commit", r) + PutTMQCommitCallbackResult(r) + } + }() + tmq, err := TMQConsumerNew(conf) + if err != nil { + t.Error(err) + } + TMQConfDestroy(conf) + //build_topic_list + topicList := TMQListNew() + TMQListAppend(topicList, "topic_ctb_column") + + //sync_consume_loop + s := time.Now() + errCode := TMQSubscribe(tmq, topicList) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } + t.Log("sub", time.Since(s)) + errCode, list := TMQSubscription(tmq) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } + size := TMQListGetSize(list) + r := TMQListToCArray(list, int(size)) + assert.Equal(t, []string{"topic_ctb_column"}, r) + c2 := make(chan *TMQCommitCallbackResult, 1) + h2 := cgo.NewHandle(c2) + for i := 0; i < 5; i++ { + + message := TMQConsumerPoll(tmq, 500) + if message != nil { + t.Log(message) + topic := TMQGetTopicName(message) + assert.Equal(t, "topic_ctb_column", topic) + vgroupID := TMQGetVgroupID(message) + t.Log("vgroupID", vgroupID) + + for { + blockSize, errCode, block := TaosFetchRawBlock(message) + if errCode != int(errors.SUCCESS) { + errStr := TaosErrorStr(message) + err := errors.NewError(errCode, errStr) + t.Error(err) + TaosFreeResult(message) + return + } + if blockSize == 0 { + break + } + filedCount := TaosNumFields(message) + rh, err := ReadColumn(message, filedCount) + if err != nil { + t.Error(err) + return + } + precision := TaosResultPrecision(message) + //tableName := TMQGetTableName(message) + //assert.Equal(t, "ct1", tableName) + dbName := TMQGetDBName(message) + assert.Equal(t, "abc1", dbName) + data := parser.ReadBlock(block, blockSize, rh.ColTypes, precision) + t.Log(data) + } + TaosFreeResult(message) + TMQCommitAsync(tmq, nil, h2) + timer := time.NewTimer(time.Minute) + select { + case d := <-c2: + assert.Equal(t, int32(0), d.ErrCode) + PutTMQCommitCallbackResult(d) + timer.Stop() + break + case <-timer.C: + timer.Stop() + t.Error("wait tmq commit callback timeout") + return + } + } + } + + errCode = TMQConsumerClose(tmq) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } +} + +// @author: xftan +// @date: 2023/10/13 11:33 +// @description: test TMQList +func TestTMQList(t *testing.T) { + list := TMQListNew() + TMQListAppend(list, "1") + TMQListAppend(list, "2") + TMQListAppend(list, "3") + size := TMQListGetSize(list) + r := TMQListToCArray(list, int(size)) + assert.Equal(t, []string{"1", "2", "3"}, r) + TMQListDestroy(list) +} + +// @author: xftan +// @date: 2023/10/13 11:33 +// @description: test tmq subscribe db +func TestTMQDB(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + result := TaosQuery(conn, "drop database if exists tmq_test_db") + code := TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + }() + result := TaosQuery(conn, "create database if not exists tmq_test_db vgroups 2 WAL_RETENTION_PERIOD 86400") + code := TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "use tmq_test_db") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "create stable if not exists st1 (ts timestamp, c1 int, c2 float, c3 binary(10)) tags(t1 int)") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "create table if not exists ct0 using st1 tags(1000)") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "create table if not exists ct1 using st1 tags(2000)") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "create table if not exists ct3 using st1 tags(3000)") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + //create topic + result = TaosQuery(conn, "create topic if not exists test_tmq_db_topic as DATABASE tmq_test_db") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + defer func() { + result = TaosQuery(conn, "drop topic if exists test_tmq_db_topic") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + }() + go func() { + for i := 0; i < 5; i++ { + result = TaosQuery(conn, "insert into ct1 values(now,1,2,'1')") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + time.Sleep(time.Millisecond) + } + }() + //build consumer + conf := TMQConfNew() + // auto commit default is true then the commitCallback function will be called after 5 seconds + TMQConfSet(conf, "enable.auto.commit", "true") + TMQConfSet(conf, "group.id", "tg2") + TMQConfSet(conf, "msg.with.table.name", "true") + TMQConfSet(conf, "auto.offset.reset", "earliest") + c := make(chan *TMQCommitCallbackResult, 1) + h := cgo.NewHandle(c) + TMQConfSetAutoCommitCB(conf, h) + go func() { + for r := range c { + t.Log("auto commit", r) + PutTMQCommitCallbackResult(r) + } + }() + tmq, err := TMQConsumerNew(conf) + if err != nil { + t.Error(err) + } + TMQConfDestroy(conf) + //build_topic_list + topicList := TMQListNew() + TMQListAppend(topicList, "test_tmq_db_topic") + + //sync_consume_loop + errCode := TMQSubscribe(tmq, topicList) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } + errCode, list := TMQSubscription(tmq) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } + size := TMQListGetSize(list) + r := TMQListToCArray(list, int(size)) + assert.Equal(t, []string{"test_tmq_db_topic"}, r) + totalCount := 0 + c2 := make(chan *TMQCommitCallbackResult, 1) + h2 := cgo.NewHandle(c2) + for i := 0; i < 5; i++ { + message := TMQConsumerPoll(tmq, 500) + if message != nil { + t.Log(message) + topic := TMQGetTopicName(message) + assert.Equal(t, "test_tmq_db_topic", topic) + for { + blockSize, errCode, block := TaosFetchRawBlock(message) + if errCode != int(errors.SUCCESS) { + errStr := TaosErrorStr(message) + err := errors.NewError(errCode, errStr) + t.Error(err) + TaosFreeResult(message) + return + } + if blockSize == 0 { + break + } + tableName := TMQGetTableName(message) + assert.Equal(t, "ct1", tableName) + filedCount := TaosNumFields(message) + rh, err := ReadColumn(message, filedCount) + if err != nil { + t.Error(err) + return + } + precision := TaosResultPrecision(message) + totalCount += blockSize + data := parser.ReadBlock(block, blockSize, rh.ColTypes, precision) + t.Log(data) + } + TaosFreeResult(message) + + TMQCommitAsync(tmq, nil, h2) + timer := time.NewTimer(time.Minute) + select { + case d := <-c2: + assert.Nil(t, d.GetError()) + assert.Equal(t, int32(0), d.ErrCode) + PutTMQCommitCallbackResult(d) + timer.Stop() + break + case <-timer.C: + timer.Stop() + t.Error("wait tmq commit callback timeout") + return + } + } + } + + errCode = TMQConsumerClose(tmq) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } + assert.GreaterOrEqual(t, totalCount, 5) +} + +// @author: xftan +// @date: 2023/10/13 11:33 +// @description: test tmq subscribe multi tables +func TestTMQDBMultiTable(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + result := TaosQuery(conn, "drop database if exists tmq_test_db_multi") + code := TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + }() + result := TaosQuery(conn, "create database if not exists tmq_test_db_multi vgroups 2 WAL_RETENTION_PERIOD 86400") + code := TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "use tmq_test_db_multi") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "create table if not exists ct0 (ts timestamp, c1 int)") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "create table if not exists ct1 (ts timestamp, c1 int, c2 float)") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "create table if not exists ct2 (ts timestamp, c1 int, c2 float, c3 binary(10))") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + //create topic + result = TaosQuery(conn, "create topic if not exists test_tmq_db_multi_topic as DATABASE tmq_test_db_multi") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + defer func() { + result = TaosQuery(conn, "drop topic if exists test_tmq_db_multi_topic") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + }() + { + result = TaosQuery(conn, "insert into ct0 values(now,1)") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + } + { + result = TaosQuery(conn, "insert into ct1 values(now,1,2)") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + } + { + result = TaosQuery(conn, "insert into ct2 values(now,1,2,'3')") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + } + //build consumer + conf := TMQConfNew() + // auto commit default is true then the commitCallback function will be called after 5 seconds + TMQConfSet(conf, "enable.auto.commit", "true") + TMQConfSet(conf, "group.id", "tg2") + TMQConfSet(conf, "msg.with.table.name", "true") + TMQConfSet(conf, "auto.offset.reset", "earliest") + c := make(chan *TMQCommitCallbackResult, 1) + h := cgo.NewHandle(c) + TMQConfSetAutoCommitCB(conf, h) + go func() { + for r := range c { + t.Log("auto commit", r) + PutTMQCommitCallbackResult(r) + } + }() + tmq, err := TMQConsumerNew(conf) + if err != nil { + t.Error(err) + } + TMQConfDestroy(conf) + //build_topic_list + topicList := TMQListNew() + TMQListAppend(topicList, "test_tmq_db_multi_topic") + + //sync_consume_loop + errCode := TMQSubscribe(tmq, topicList) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } + errCode, list := TMQSubscription(tmq) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } + size := TMQListGetSize(list) + r := TMQListToCArray(list, int(size)) + assert.Equal(t, []string{"test_tmq_db_multi_topic"}, r) + totalCount := 0 + tables := map[string]struct{}{ + "ct0": {}, + "ct1": {}, + "ct2": {}, + } + c2 := make(chan *TMQCommitCallbackResult, 1) + h2 := cgo.NewHandle(c2) + for i := 0; i < 5; i++ { + message := TMQConsumerPoll(tmq, 500) + if message != nil { + t.Log(message) + topic := TMQGetTopicName(message) + assert.Equal(t, "test_tmq_db_multi_topic", topic) + for { + blockSize, errCode, block := TaosFetchRawBlock(message) + if errCode != int(errors.SUCCESS) { + errStr := TaosErrorStr(message) + err := errors.NewError(errCode, errStr) + t.Error(err) + TaosFreeResult(message) + return + } + if blockSize == 0 { + break + } + tableName := TMQGetTableName(message) + delete(tables, tableName) + filedCount := TaosNumFields(message) + rh, err := ReadColumn(message, filedCount) + if err != nil { + t.Error(err) + return + } + precision := TaosResultPrecision(message) + totalCount += blockSize + data := parser.ReadBlock(block, blockSize, rh.ColTypes, precision) + t.Log(data) + } + TaosFreeResult(message) + + TMQCommitAsync(tmq, nil, h2) + timer := time.NewTimer(time.Minute) + select { + case d := <-c2: + assert.Equal(t, int32(0), d.ErrCode) + PutTMQCommitCallbackResult(d) + timer.Stop() + break + case <-timer.C: + timer.Stop() + t.Error("wait tmq commit callback timeout") + return + } + } + } + errCode = TMQUnsubscribe(tmq) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } + errCode = TMQConsumerClose(tmq) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } + assert.GreaterOrEqual(t, totalCount, 3) + assert.Emptyf(t, tables, "tables name not empty", tables) +} + +// @author: xftan +// @date: 2023/10/13 11:33 +// @description: test tmq subscribe db with multi table insert +func TestTMQDBMultiInsert(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + result := TaosQuery(conn, "drop database if exists tmq_test_db_multi_insert") + code := TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + }() + result := TaosQuery(conn, "create database if not exists tmq_test_db_multi_insert vgroups 2 wal_retention_period 3600") + code := TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "use tmq_test_db_multi_insert") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "create table if not exists ct0 (ts timestamp, c1 int)") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "create table if not exists ct1 (ts timestamp, c1 int, c2 float)") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "create table if not exists ct2 (ts timestamp, c1 int, c2 float, c3 binary(10))") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + //create topic + result = TaosQuery(conn, "create topic if not exists tmq_test_db_multi_insert_topic as DATABASE tmq_test_db_multi_insert") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + defer func() { + result = TaosQuery(conn, "drop topic if exists tmq_test_db_multi_insert_topic") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + }() + { + result = TaosQuery(conn, "insert into ct0 values(now,1) ct1 values(now,1,2) ct2 values(now,1,2,'3')") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + } + //build consumer + conf := TMQConfNew() + // auto commit default is true then the commitCallback function will be called after 5 seconds + TMQConfSet(conf, "enable.auto.commit", "true") + TMQConfSet(conf, "group.id", "tg2") + TMQConfSet(conf, "msg.with.table.name", "true") + TMQConfSet(conf, "auto.offset.reset", "earliest") + c := make(chan *TMQCommitCallbackResult, 1) + h := cgo.NewHandle(c) + TMQConfSetAutoCommitCB(conf, h) + go func() { + for r := range c { + t.Log("auto commit", r) + PutTMQCommitCallbackResult(r) + } + }() + tmq, err := TMQConsumerNew(conf) + if err != nil { + t.Error(err) + } + TMQConfDestroy(conf) + //build_topic_list + topicList := TMQListNew() + TMQListAppend(topicList, "tmq_test_db_multi_insert_topic") + + //sync_consume_loop + errCode := TMQSubscribe(tmq, topicList) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } + errCode, list := TMQSubscription(tmq) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } + size := TMQListGetSize(list) + r := TMQListToCArray(list, int(size)) + assert.Equal(t, []string{"tmq_test_db_multi_insert_topic"}, r) + totalCount := 0 + var tables [][]string + c2 := make(chan *TMQCommitCallbackResult, 1) + h2 := cgo.NewHandle(c2) + for i := 0; i < 5; i++ { + message := TMQConsumerPoll(tmq, 500) + if message != nil { + t.Log(message) + topic := TMQGetTopicName(message) + assert.Equal(t, "tmq_test_db_multi_insert_topic", topic) + var table []string + for { + blockSize, errCode, block := TaosFetchRawBlock(message) + if errCode != int(errors.SUCCESS) { + errStr := TaosErrorStr(message) + err := errors.NewError(errCode, errStr) + t.Error(err) + TaosFreeResult(message) + return + } + if blockSize == 0 { + break + } + tableName := TMQGetTableName(message) + table = append(table, tableName) + filedCount := TaosNumFields(message) + rh, err := ReadColumn(message, filedCount) + if err != nil { + t.Error(err) + return + } + precision := TaosResultPrecision(message) + totalCount += blockSize + data := parser.ReadBlock(block, blockSize, rh.ColTypes, precision) + t.Log(data) + } + TaosFreeResult(message) + + TMQCommitAsync(tmq, nil, h2) + timer := time.NewTimer(time.Minute) + select { + case d := <-c2: + assert.Equal(t, int32(0), d.ErrCode) + PutTMQCommitCallbackResult(d) + timer.Stop() + break + case <-timer.C: + timer.Stop() + t.Error("wait tmq commit callback timeout") + return + } + tables = append(tables, table) + } + } + + errCode = TMQConsumerClose(tmq) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } + assert.GreaterOrEqual(t, totalCount, 3) + t.Log(tables) +} + +// @author: xftan +// @date: 2023/10/13 11:34 +// @description: tmq test modify meta +func TestTMQModify(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + result := TaosQuery(conn, "drop database if exists tmq_test_db_modify") + code := TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + result = TaosQuery(conn, "drop database if exists tmq_test_db_modify_target") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + }() + + result := TaosQuery(conn, "drop database if exists tmq_test_db_modify_target") + code := TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "drop database if exists tmq_test_db_modify") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + result = TaosQuery(conn, "create database if not exists tmq_test_db_modify_target vgroups 2 WAL_RETENTION_PERIOD 86400") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "create database if not exists tmq_test_db_modify vgroups 5 WAL_RETENTION_PERIOD 86400") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "use tmq_test_db_modify") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + //create topic + result = TaosQuery(conn, "create topic if not exists tmq_test_db_modify_topic with meta as DATABASE tmq_test_db_modify") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + defer func() { + result = TaosQuery(conn, "drop topic if exists tmq_test_db_modify_topic") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + }() + //build consumer + conf := TMQConfNew() + // auto commit default is true then the commitCallback function will be called after 5 seconds + TMQConfSet(conf, "enable.auto.commit", "true") + TMQConfSet(conf, "group.id", "tg2") + TMQConfSet(conf, "msg.with.table.name", "true") + TMQConfSet(conf, "auto.offset.reset", "earliest") + c := make(chan *TMQCommitCallbackResult, 1) + h := cgo.NewHandle(c) + TMQConfSetAutoCommitCB(conf, h) + go func() { + for r := range c { + t.Log("auto commit", r) + PutTMQCommitCallbackResult(r) + } + }() + tmq, err := TMQConsumerNew(conf) + if err != nil { + t.Error(err) + } + TMQConfDestroy(conf) + //build_topic_list + topicList := TMQListNew() + TMQListAppend(topicList, "tmq_test_db_modify_topic") + + //sync_consume_loop + errCode := TMQSubscribe(tmq, topicList) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } + errCode, list := TMQSubscription(tmq) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } + size := TMQListGetSize(list) + r := TMQListToCArray(list, int(size)) + assert.Equal(t, []string{"tmq_test_db_modify_topic"}, r) + c2 := make(chan *TMQCommitCallbackResult, 1) + h2 := cgo.NewHandle(c2) + targetConn, err := TaosConnect("", "root", "taosdata", "tmq_test_db_modify_target", 0) + assert.NoError(t, err) + defer TaosClose(targetConn) + result = TaosQuery(conn, "create table stb (ts timestamp,"+ + "c1 bool,"+ + "c2 tinyint,"+ + "c3 smallint,"+ + "c4 int,"+ + "c5 bigint,"+ + "c6 tinyint unsigned,"+ + "c7 smallint unsigned,"+ + "c8 int unsigned,"+ + "c9 bigint unsigned,"+ + "c10 float,"+ + "c11 double,"+ + "c12 binary(20),"+ + "c13 nchar(20)"+ + ")"+ + "tags(tts timestamp,"+ + "tc1 bool,"+ + "tc2 tinyint,"+ + "tc3 smallint,"+ + "tc4 int,"+ + "tc5 bigint,"+ + "tc6 tinyint unsigned,"+ + "tc7 smallint unsigned,"+ + "tc8 int unsigned,"+ + "tc9 bigint unsigned,"+ + "tc10 float,"+ + "tc11 double,"+ + "tc12 binary(20),"+ + "tc13 nchar(20)"+ + ")") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + pool := func(cb func(*tmqcommon.Meta, unsafe.Pointer)) { + message := TMQConsumerPoll(tmq, 500) + assert.NotNil(t, message) + topic := TMQGetTopicName(message) + assert.Equal(t, "tmq_test_db_modify_topic", topic) + messageType := TMQGetResType(message) + assert.Equal(t, int32(common.TMQ_RES_TABLE_META), messageType) + pointer := TMQGetJsonMeta(message) + assert.NotNil(t, pointer) + data := ParseJsonMeta(pointer) + var meta tmqcommon.Meta + err = jsoniter.Unmarshal(data, &meta) + assert.NoError(t, err) + + defer TaosFreeResult(message) + + TMQCommitAsync(tmq, nil, h2) + timer := time.NewTimer(time.Minute) + select { + case d := <-c2: + assert.Equal(t, int32(0), d.ErrCode) + PutTMQCommitCallbackResult(d) + timer.Stop() + break + case <-timer.C: + timer.Stop() + t.Error("wait tmq commit callback timeout") + cb(nil, nil) + return + } + errCode, rawMeta := TMQGetRaw(message) + if errCode != errors.SUCCESS { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.NewError(int(errCode), errStr)) + return + } + cb(&meta, rawMeta) + TMQFreeRaw(rawMeta) + } + + pool(func(meta *tmqcommon.Meta, rawMeta unsafe.Pointer) { + assert.Equal(t, "create", meta.Type) + assert.Equal(t, "stb", meta.TableName) + assert.Equal(t, "super", meta.TableType) + assert.NoError(t, err) + length, metaType, data := ParseRawMeta(rawMeta) + r2 := BuildRawMeta(length, metaType, data) + errCode = TMQWriteRaw(targetConn, r2) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } + d, err := query(targetConn, "describe stb") + assert.NoError(t, err) + expect := [][]driver.Value{ + {"ts", "TIMESTAMP", int32(8), ""}, + {"c1", "BOOL", int32(1), ""}, + {"c2", "TINYINT", int32(1), ""}, + {"c3", "SMALLINT", int32(2), ""}, + {"c4", "INT", int32(4), ""}, + {"c5", "BIGINT", int32(8), ""}, + {"c6", "TINYINT UNSIGNED", int32(1), ""}, + {"c7", "SMALLINT UNSIGNED", int32(2), ""}, + {"c8", "INT UNSIGNED", int32(4), ""}, + {"c9", "BIGINT UNSIGNED", int32(8), ""}, + {"c10", "FLOAT", int32(4), ""}, + {"c11", "DOUBLE", int32(8), ""}, + {"c12", "VARCHAR", int32(20), ""}, + {"c13", "NCHAR", int32(20), ""}, + {"tts", "TIMESTAMP", int32(8), "TAG"}, + {"tc1", "BOOL", int32(1), "TAG"}, + {"tc2", "TINYINT", int32(1), "TAG"}, + {"tc3", "SMALLINT", int32(2), "TAG"}, + {"tc4", "INT", int32(4), "TAG"}, + {"tc5", "BIGINT", int32(8), "TAG"}, + {"tc6", "TINYINT UNSIGNED", int32(1), "TAG"}, + {"tc7", "SMALLINT UNSIGNED", int32(2), "TAG"}, + {"tc8", "INT UNSIGNED", int32(4), "TAG"}, + {"tc9", "BIGINT UNSIGNED", int32(8), "TAG"}, + {"tc10", "FLOAT", int32(4), "TAG"}, + {"tc11", "DOUBLE", int32(8), "TAG"}, + {"tc12", "VARCHAR", int32(20), "TAG"}, + {"tc13", "NCHAR", int32(20), "TAG"}, + } + for rowIndex, values := range d { + for i := 0; i < 4; i++ { + assert.Equal(t, expect[rowIndex][i], values[i]) + } + } + }) + + TMQUnsubscribe(tmq) + errCode = TMQConsumerClose(tmq) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } +} + +// @author: xftan +// @date: 2023/10/13 11:34 +// @description: test tmq subscribe with auto create table +func TestTMQAutoCreateTable(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Error(err) + return + } + defer TaosClose(conn) + defer func() { + result := TaosQuery(conn, "drop database if exists tmq_test_auto_create") + code := TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + }() + result := TaosQuery(conn, "create database if not exists tmq_test_auto_create vgroups 2 WAL_RETENTION_PERIOD 86400") + code := TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "use tmq_test_auto_create") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + result = TaosQuery(conn, "create stable if not exists st1 (ts timestamp, c1 int, c2 float, c3 binary(10)) tags(t1 int)") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + + //create topic + result = TaosQuery(conn, "create topic if not exists test_tmq_auto_topic with meta as DATABASE tmq_test_auto_create") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + defer func() { + result = TaosQuery(conn, "drop topic if exists test_tmq_auto_topic") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + }() + result = TaosQuery(conn, "insert into ct1 using st1 tags(2000) values(now,1,2,'1')") + code = TaosError(result) + if code != 0 { + errStr := TaosErrorStr(result) + TaosFreeResult(result) + t.Error(errors.TaosError{Code: int32(code), ErrStr: errStr}) + return + } + TaosFreeResult(result) + //build consumer + conf := TMQConfNew() + // auto commit default is true then the commitCallback function will be called after 5 seconds + TMQConfSet(conf, "enable.auto.commit", "true") + TMQConfSet(conf, "group.id", "tg2") + TMQConfSet(conf, "msg.with.table.name", "true") + TMQConfSet(conf, "auto.offset.reset", "earliest") + c := make(chan *TMQCommitCallbackResult, 1) + h := cgo.NewHandle(c) + TMQConfSetAutoCommitCB(conf, h) + go func() { + for r := range c { + t.Log("auto commit", r) + PutTMQCommitCallbackResult(r) + } + }() + tmq, err := TMQConsumerNew(conf) + if err != nil { + t.Error(err) + } + TMQConfDestroy(conf) + //build_topic_list + topicList := TMQListNew() + TMQListAppend(topicList, "test_tmq_auto_topic") + + //sync_consume_loop + errCode := TMQSubscribe(tmq, topicList) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } + errCode, list := TMQSubscription(tmq) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } + size := TMQListGetSize(list) + r := TMQListToCArray(list, int(size)) + assert.Equal(t, []string{"test_tmq_auto_topic"}, r) + totalCount := 0 + c2 := make(chan *TMQCommitCallbackResult, 1) + h2 := cgo.NewHandle(c2) + for i := 0; i < 5; i++ { + message := TMQConsumerPoll(tmq, 500) + if message != nil { + t.Log(message) + topic := TMQGetTopicName(message) + assert.Equal(t, "test_tmq_auto_topic", topic) + messageType := TMQGetResType(message) + if messageType != common.TMQ_RES_METADATA { + continue + } + pointer := TMQGetJsonMeta(message) + data := ParseJsonMeta(pointer) + t.Log(string(data)) + var meta tmqcommon.Meta + err = jsoniter.Unmarshal(data, &meta) + assert.NoError(t, err) + assert.Equal(t, "create", meta.Type) + for { + blockSize, errCode, block := TaosFetchRawBlock(message) + if errCode != int(errors.SUCCESS) { + errStr := TaosErrorStr(message) + err := errors.NewError(errCode, errStr) + t.Error(err) + TaosFreeResult(message) + return + } + if blockSize == 0 { + break + } + tableName := TMQGetTableName(message) + assert.Equal(t, "ct1", tableName) + filedCount := TaosNumFields(message) + rh, err := ReadColumn(message, filedCount) + if err != nil { + t.Error(err) + return + } + precision := TaosResultPrecision(message) + totalCount += blockSize + data := parser.ReadBlock(block, blockSize, rh.ColTypes, precision) + t.Log(data) + } + TaosFreeResult(message) + + TMQCommitAsync(tmq, nil, h2) + timer := time.NewTimer(time.Minute) + select { + case d := <-c2: + assert.Nil(t, d.GetError()) + assert.Equal(t, int32(0), d.ErrCode) + PutTMQCommitCallbackResult(d) + timer.Stop() + break + case <-timer.C: + timer.Stop() + t.Error("wait tmq commit callback timeout") + return + } + } + } + + errCode = TMQConsumerClose(tmq) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } + assert.GreaterOrEqual(t, totalCount, 1) +} + +// @author: xftan +// @date: 2023/10/13 11:35 +// @description: test tmq get assignment +func TestTMQGetTopicAssignment(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Fatal(err) + return + } + defer TaosClose(conn) + + defer func() { + if err = taosOperation(conn, "drop database if exists test_tmq_get_topic_assignment"); err != nil { + t.Error(err) + } + }() + + if err = taosOperation(conn, "create database if not exists test_tmq_get_topic_assignment vgroups 1 WAL_RETENTION_PERIOD 86400"); err != nil { + t.Fatal(err) + return + } + if err = taosOperation(conn, "use test_tmq_get_topic_assignment"); err != nil { + t.Fatal(err) + return + } + if err = taosOperation(conn, "create table if not exists t (ts timestamp,v int)"); err != nil { + t.Fatal(err) + return + } + + // create topic + if err = taosOperation(conn, "create topic if not exists test_tmq_assignment as select * from t"); err != nil { + t.Fatal(err) + return + } + + defer func() { + if err = taosOperation(conn, "drop topic if exists test_tmq_assignment"); err != nil { + t.Error(err) + } + }() + + conf := TMQConfNew() + defer TMQConfDestroy(conf) + TMQConfSet(conf, "group.id", "tg2") + TMQConfSet(conf, "auto.offset.reset", "earliest") + tmq, err := TMQConsumerNew(conf) + if err != nil { + t.Fatal(err) + } + defer TMQConsumerClose(tmq) + + topicList := TMQListNew() + TMQListAppend(topicList, "test_tmq_assignment") + + errCode := TMQSubscribe(tmq, topicList) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Fatal(errors.NewError(int(errCode), errStr)) + return + } + + code, assignment := TMQGetTopicAssignment(tmq, "test_tmq_assignment") + if code != 0 { + t.Fatal(errors.NewError(int(code), TMQErr2Str(code))) + } + assert.Equal(t, 1, len(assignment)) + assert.Equal(t, int64(0), assignment[0].Begin) + assert.Equal(t, int64(0), assignment[0].Offset) + assert.GreaterOrEqual(t, assignment[0].End, assignment[0].Offset) + end := assignment[0].End + vgID, vgCode := TaosGetTableVgID(conn, "test_tmq_get_topic_assignment", "t") + if vgCode != 0 { + t.Fatal(errors.NewError(int(vgCode), TMQErr2Str(code))) + } + assert.Equal(t, int32(vgID), assignment[0].VGroupID) + + _ = taosOperation(conn, "insert into t values(now,1)") + haveMessage := false + for i := 0; i < 3; i++ { + message := TMQConsumerPoll(tmq, 500) + if message != nil { + haveMessage = true + TMQCommitSync(tmq, message) + TaosFreeResult(message) + break + } + } + assert.True(t, haveMessage, "expect have message") + code, assignment = TMQGetTopicAssignment(tmq, "test_tmq_assignment") + if code != 0 { + t.Fatal(errors.NewError(int(code), TMQErr2Str(code))) + } + assert.Equal(t, 1, len(assignment)) + assert.Equal(t, int64(0), assignment[0].Begin) + assert.GreaterOrEqual(t, assignment[0].End, end) + end = assignment[0].End + assert.Equal(t, int32(vgID), assignment[0].VGroupID) + + //seek + code = TMQOffsetSeek(tmq, "test_tmq_assignment", int32(vgID), 0) + if code != 0 { + t.Fatal(errors.NewError(int(code), TMQErr2Str(code))) + } + code, assignment = TMQGetTopicAssignment(tmq, "test_tmq_assignment") + if code != 0 { + t.Fatal(errors.NewError(int(code), TMQErr2Str(code))) + } + assert.Equal(t, 1, len(assignment)) + assert.Equal(t, int64(0), assignment[0].Begin) + assert.Equal(t, int64(0), assignment[0].Offset) + assert.GreaterOrEqual(t, assignment[0].End, end) + end = assignment[0].End + assert.Equal(t, int32(vgID), assignment[0].VGroupID) + + haveMessage = false + for i := 0; i < 3; i++ { + message := TMQConsumerPoll(tmq, 500) + if message != nil { + haveMessage = true + TMQCommitSync(tmq, message) + TaosFreeResult(message) + break + } + } + assert.True(t, haveMessage, "expect have message") + code, assignment = TMQGetTopicAssignment(tmq, "test_tmq_assignment") + if code != 0 { + t.Fatal(errors.NewError(int(code), TMQErr2Str(code))) + } + assert.Equal(t, 1, len(assignment)) + assert.Equal(t, int64(0), assignment[0].Begin) + assert.GreaterOrEqual(t, assignment[0].End, end) + end = assignment[0].End + assert.Equal(t, int32(vgID), assignment[0].VGroupID) + + // seek twice + code = TMQOffsetSeek(tmq, "test_tmq_assignment", int32(vgID), 1) + if code != 0 { + t.Fatal(errors.NewError(int(code), TMQErr2Str(code))) + } + code, assignment = TMQGetTopicAssignment(tmq, "test_tmq_assignment") + if code != 0 { + t.Fatal(errors.NewError(int(code), TMQErr2Str(code))) + } + assert.Equal(t, 1, len(assignment)) + assert.Equal(t, int64(0), assignment[0].Begin) + assert.GreaterOrEqual(t, assignment[0].End, end) + end = assignment[0].End + assert.Equal(t, int32(vgID), assignment[0].VGroupID) + + haveMessage = false + for i := 0; i < 3; i++ { + message := TMQConsumerPoll(tmq, 500) + if message != nil { + haveMessage = true + offset := TMQGetVgroupOffset(message) + assert.Greater(t, offset, int64(0)) + TMQCommitSync(tmq, message) + TaosFreeResult(message) + break + } + } + assert.True(t, haveMessage, "expect have message") + code, assignment = TMQGetTopicAssignment(tmq, "test_tmq_assignment") + if code != 0 { + t.Fatal(errors.NewError(int(code), TMQErr2Str(code))) + } + assert.Equal(t, 1, len(assignment)) + assert.Equal(t, int64(0), assignment[0].Begin) + assert.GreaterOrEqual(t, assignment[0].End, end) + assert.Equal(t, int32(vgID), assignment[0].VGroupID) +} + +func TestTMQPosition(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Fatal(err) + return + } + defer TaosClose(conn) + + defer func() { + if err = taosOperation(conn, "drop database if exists test_tmq_position"); err != nil { + t.Error(err) + } + }() + + if err = taosOperation(conn, "create database if not exists test_tmq_position vgroups 1 WAL_RETENTION_PERIOD 86400"); err != nil { + t.Fatal(err) + return + } + if err = taosOperation(conn, "use test_tmq_position"); err != nil { + t.Fatal(err) + return + } + if err = taosOperation(conn, "create table if not exists t (ts timestamp,v int)"); err != nil { + t.Fatal(err) + return + } + + // create topic + if err = taosOperation(conn, "create topic if not exists test_tmq_position_topic as select * from t"); err != nil { + t.Fatal(err) + return + } + + defer func() { + if err = taosOperation(conn, "drop topic if exists test_tmq_position_topic"); err != nil { + t.Error(err) + } + }() + + conf := TMQConfNew() + defer TMQConfDestroy(conf) + TMQConfSet(conf, "group.id", "position") + TMQConfSet(conf, "auto.offset.reset", "earliest") + + tmq, err := TMQConsumerNew(conf) + if err != nil { + t.Fatal(err) + } + defer TMQConsumerClose(tmq) + + topicList := TMQListNew() + TMQListAppend(topicList, "test_tmq_position_topic") + + errCode := TMQSubscribe(tmq, topicList) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Fatal(errors.NewError(int(errCode), errStr)) + return + } + _ = taosOperation(conn, "insert into t values(now,1)") + code, assignment := TMQGetTopicAssignment(tmq, "test_tmq_position_topic") + if code != 0 { + t.Fatal(errors.NewError(int(code), TMQErr2Str(code))) + } + vgID := assignment[0].VGroupID + position := TMQPosition(tmq, "test_tmq_position_topic", vgID) + assert.Equal(t, position, int64(0)) + haveMessage := false + for i := 0; i < 3; i++ { + message := TMQConsumerPoll(tmq, 500) + if message != nil { + haveMessage = true + position := TMQPosition(tmq, "test_tmq_position_topic", vgID) + assert.Greater(t, position, int64(0)) + committed := TMQCommitted(tmq, "test_tmq_position_topic", vgID) + assert.Less(t, committed, int64(0)) + TMQCommitSync(tmq, message) + position = TMQPosition(tmq, "test_tmq_position_topic", vgID) + committed = TMQCommitted(tmq, "test_tmq_position_topic", vgID) + assert.Equal(t, position, committed) + TaosFreeResult(message) + break + } + } + assert.True(t, haveMessage, "expect have message") + errCode = TMQUnsubscribe(tmq) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } +} + +func TestTMQCommitOffset(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Fatal(err) + return + } + defer TaosClose(conn) + + defer func() { + if err = taosOperation(conn, "drop database if exists test_tmq_commit_offset"); err != nil { + t.Error(err) + } + }() + + if err = taosOperation(conn, "create database if not exists test_tmq_commit_offset vgroups 1 WAL_RETENTION_PERIOD 86400"); err != nil { + t.Fatal(err) + return + } + if err = taosOperation(conn, "use test_tmq_commit_offset"); err != nil { + t.Fatal(err) + return + } + if err = taosOperation(conn, "create table if not exists t (ts timestamp,v int)"); err != nil { + t.Fatal(err) + return + } + + // create topic + if err = taosOperation(conn, "create topic if not exists test_tmq_commit_offset_topic as select * from t"); err != nil { + t.Fatal(err) + return + } + + defer func() { + if err = taosOperation(conn, "drop topic if exists test_tmq_commit_offset_topic"); err != nil { + t.Error(err) + } + }() + + conf := TMQConfNew() + defer TMQConfDestroy(conf) + TMQConfSet(conf, "group.id", "commit") + TMQConfSet(conf, "auto.offset.reset", "earliest") + + tmq, err := TMQConsumerNew(conf) + if err != nil { + t.Fatal(err) + } + defer TMQConsumerClose(tmq) + + topicList := TMQListNew() + TMQListAppend(topicList, "test_tmq_commit_offset_topic") + + errCode := TMQSubscribe(tmq, topicList) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Fatal(errors.NewError(int(errCode), errStr)) + return + } + _ = taosOperation(conn, "insert into t values(now,1)") + code, assignment := TMQGetTopicAssignment(tmq, "test_tmq_commit_offset_topic") + if code != 0 { + t.Fatal(errors.NewError(int(code), TMQErr2Str(code))) + } + vgID := assignment[0].VGroupID + haveMessage := false + for i := 0; i < 3; i++ { + message := TMQConsumerPoll(tmq, 500) + if message != nil { + haveMessage = true + position := TMQPosition(tmq, "test_tmq_commit_offset_topic", vgID) + assert.Greater(t, position, int64(0)) + committed := TMQCommitted(tmq, "test_tmq_commit_offset_topic", vgID) + assert.Less(t, committed, int64(0)) + offset := TMQGetVgroupOffset(message) + code = TMQCommitOffsetSync(tmq, "test_tmq_commit_offset_topic", vgID, offset) + if code != 0 { + t.Fatal(errors.NewError(int(code), TMQErr2Str(code))) + } + committed = TMQCommitted(tmq, "test_tmq_commit_offset_topic", vgID) + assert.Equal(t, int64(offset), committed) + TaosFreeResult(message) + break + } + } + assert.True(t, haveMessage, "expect have message") + errCode = TMQUnsubscribe(tmq) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } +} + +func TestTMQCommitOffsetAsync(t *testing.T) { + topic := "test_tmq_commit_offset_a_topic" + tableName := "test_tmq_commit_offset_a" + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Fatal(err) + return + } + defer TaosClose(conn) + + defer func() { + if err = taosOperation(conn, "drop database if exists "+tableName); err != nil { + t.Error(err) + } + }() + + if err = taosOperation(conn, "create database if not exists "+tableName+" vgroups 1 WAL_RETENTION_PERIOD 86400"); err != nil { + t.Fatal(err) + return + } + if err = taosOperation(conn, "use "+tableName); err != nil { + t.Fatal(err) + return + } + if err = taosOperation(conn, "create table if not exists t (ts timestamp,v int)"); err != nil { + t.Fatal(err) + return + } + + // create topic + + if err = taosOperation(conn, "create topic if not exists "+topic+" as select * from t"); err != nil { + t.Fatal(err) + return + } + + defer func() { + if err = taosOperation(conn, "drop topic if exists "+topic); err != nil { + t.Error(err) + } + }() + + conf := TMQConfNew() + defer TMQConfDestroy(conf) + TMQConfSet(conf, "group.id", "commit_a") + TMQConfSet(conf, "auto.offset.reset", "earliest") + + tmq, err := TMQConsumerNew(conf) + if err != nil { + t.Fatal(err) + } + defer TMQConsumerClose(tmq) + + topicList := TMQListNew() + TMQListAppend(topicList, topic) + + errCode := TMQSubscribe(tmq, topicList) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Fatal(errors.NewError(int(errCode), errStr)) + return + } + _ = taosOperation(conn, "insert into t values(now,1)") + code, assignment := TMQGetTopicAssignment(tmq, topic) + if code != 0 { + t.Fatal(errors.NewError(int(code), TMQErr2Str(code))) + } + vgID := assignment[0].VGroupID + haveMessage := false + for i := 0; i < 3; i++ { + message := TMQConsumerPoll(tmq, 500) + if message != nil { + haveMessage = true + position := TMQPosition(tmq, topic, vgID) + assert.Greater(t, position, int64(0)) + committed := TMQCommitted(tmq, topic, vgID) + assert.Less(t, committed, int64(0)) + offset := TMQGetVgroupOffset(message) + c := make(chan *TMQCommitCallbackResult, 1) + handler := cgo.NewHandle(c) + TMQCommitOffsetAsync(tmq, topic, vgID, offset, handler) + timer := time.NewTimer(time.Second * 5) + select { + case r := <-c: + code = r.ErrCode + if code != 0 { + t.Fatal(errors.NewError(int(code), TMQErr2Str(code))) + } + timer.Stop() + case <-timer.C: + t.Fatal("commit async timeout") + timer.Stop() + } + committed = TMQCommitted(tmq, topic, vgID) + assert.Equal(t, int64(offset), committed) + TaosFreeResult(message) + break + } + } + assert.True(t, haveMessage, "expect have message") + errCode = TMQUnsubscribe(tmq) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } +} + +func TestTMQCommitAsyncCallback(t *testing.T) { + topic := "test_tmq_commit_a_cb_topic" + tableName := "test_tmq_commit_a_cb" + conn, err := TaosConnect("", "root", "taosdata", "", 0) + if err != nil { + t.Fatal(err) + return + } + defer TaosClose(conn) + + defer func() { + if err = taosOperation(conn, "drop database if exists "+tableName); err != nil { + t.Error(err) + } + }() + + if err = taosOperation(conn, "create database if not exists "+tableName+" vgroups 1 WAL_RETENTION_PERIOD 86400"); err != nil { + t.Fatal(err) + return + } + if err = taosOperation(conn, "use "+tableName); err != nil { + t.Fatal(err) + return + } + if err = taosOperation(conn, "create table if not exists t (ts timestamp,v int)"); err != nil { + t.Fatal(err) + return + } + + // create topic + + if err = taosOperation(conn, "create topic if not exists "+topic+" as select * from t"); err != nil { + t.Fatal(err) + return + } + + defer func() { + if err = taosOperation(conn, "drop topic if exists "+topic); err != nil { + t.Error(err) + } + }() + + conf := TMQConfNew() + defer TMQConfDestroy(conf) + TMQConfSet(conf, "group.id", "commit_a") + TMQConfSet(conf, "enable.auto.commit", "false") + TMQConfSet(conf, "auto.offset.reset", "earliest") + TMQConfSet(conf, "auto.commit.interval.ms", "100") + c := make(chan *TMQCommitCallbackResult, 1) + h := cgo.NewHandle(c) + TMQConfSetAutoCommitCB(conf, h) + go func() { + for r := range c { + t.Log("auto commit", r) + PutTMQCommitCallbackResult(r) + } + }() + + tmq, err := TMQConsumerNew(conf) + if err != nil { + t.Fatal(err) + } + defer TMQConsumerClose(tmq) + + topicList := TMQListNew() + TMQListAppend(topicList, topic) + + errCode := TMQSubscribe(tmq, topicList) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Fatal(errors.NewError(int(errCode), errStr)) + return + } + _ = taosOperation(conn, "insert into t values(now,1)") + code, assignment := TMQGetTopicAssignment(tmq, topic) + if code != 0 { + t.Fatal(errors.NewError(int(code), TMQErr2Str(code))) + } + vgID := assignment[0].VGroupID + haveMessage := false + for i := 0; i < 3; i++ { + message := TMQConsumerPoll(tmq, 500) + if message != nil { + haveMessage = true + position := TMQPosition(tmq, topic, vgID) + assert.Greater(t, position, int64(0)) + committed := TMQCommitted(tmq, topic, vgID) + assert.Less(t, committed, int64(0)) + offset := TMQGetVgroupOffset(message) + TMQCommitOffsetSync(tmq, topic, vgID, offset) + committed = TMQCommitted(tmq, topic, vgID) + assert.Equal(t, offset, committed) + TaosFreeResult(message) + } + } + assert.True(t, haveMessage, "expect have message") + committed := TMQCommitted(tmq, topic, vgID) + t.Log(committed) + code, assignment = TMQGetTopicAssignment(tmq, topic) + if code != 0 { + t.Fatal(errors.NewError(int(code), TMQErr2Str(code))) + } + t.Log(assignment[0].Offset) + TMQCommitOffsetSync(tmq, topic, vgID, 1) + committed = TMQCommitted(tmq, topic, vgID) + assert.Equal(t, int64(1), committed) + code, assignment = TMQGetTopicAssignment(tmq, topic) + if code != 0 { + t.Fatal(errors.NewError(int(code), TMQErr2Str(code))) + } + t.Log(assignment[0].Offset) + position := TMQPosition(tmq, topic, vgID) + t.Log(position) + errCode = TMQUnsubscribe(tmq) + if errCode != 0 { + errStr := TMQErr2Str(errCode) + t.Error(errors.NewError(int(errCode), errStr)) + return + } +} + +func taosOperation(conn unsafe.Pointer, sql string) (err error) { + res := TaosQuery(conn, sql) + defer TaosFreeResult(res) + code := TaosError(res) + if code != 0 { + err = errors.NewError(code, TaosErrorStr(res)) + } + return +} diff --git a/driver/wrapper/tmqcb.go b/driver/wrapper/tmqcb.go new file mode 100644 index 00000000..94ca7c37 --- /dev/null +++ b/driver/wrapper/tmqcb.go @@ -0,0 +1,49 @@ +package wrapper + +/* +#include +#include +#include +#include +*/ +import "C" +import ( + "unsafe" + + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" +) + +//typedef void(tmq_commit_cb(tmq_t *, int32_t code, void *param)); + +//export TMQCommitCB +func TMQCommitCB(consumer unsafe.Pointer, resp C.int32_t, param unsafe.Pointer) { + c := (*(*cgo.Handle)(param)).Value().(chan *TMQCommitCallbackResult) + r := GetTMQCommitCallbackResult(int32(resp), consumer) + defer func() { + // Avoid panic due to channel closed + _ = recover() + }() + c <- r +} + +//export TMQAutoCommitCB +func TMQAutoCommitCB(consumer unsafe.Pointer, resp C.int32_t, param unsafe.Pointer) { + c := (*(*cgo.Handle)(param)).Value().(chan *TMQCommitCallbackResult) + r := GetTMQCommitCallbackResult(int32(resp), consumer) + defer func() { + // Avoid panic due to channel closed + _ = recover() + }() + c <- r +} + +//export TMQCommitOffsetCB +func TMQCommitOffsetCB(consumer unsafe.Pointer, resp C.int32_t, param unsafe.Pointer) { + c := (*(*cgo.Handle)(param)).Value().(chan *TMQCommitCallbackResult) + r := GetTMQCommitCallbackResult(int32(resp), consumer) + defer func() { + // Avoid panic due to channel closed + _ = recover() + }() + c <- r +} diff --git a/driver/wrapper/whitelist.go b/driver/wrapper/whitelist.go new file mode 100644 index 00000000..65788e1f --- /dev/null +++ b/driver/wrapper/whitelist.go @@ -0,0 +1,29 @@ +package wrapper + +/* +#cgo CFLAGS: -IC:/TDengine/include -I/usr/include +#cgo linux LDFLAGS: -L/usr/lib -ltaos +#cgo windows LDFLAGS: -LC:/TDengine/driver -ltaos +#cgo darwin LDFLAGS: -L/usr/local/lib -ltaos +#include +#include +#include +#include +extern void WhitelistCallback(void *param, int code, TAOS *taos, int numOfWhiteLists, uint64_t* pWhiteLists); +void taos_fetch_whitelist_a_wrapper(TAOS *taos, void *param){ + return taos_fetch_whitelist_a(taos, WhitelistCallback, param); +}; +*/ +import "C" +import ( + "unsafe" + + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" +) + +// typedef void (*__taos_async_whitelist_fn_t)(void *param, int code, TAOS *taos, int numOfWhiteLists, uint64_t* pWhiteLists); + +// TaosFetchWhitelistA DLL_EXPORT void taos_fetch_whitelist_a(TAOS *taos, __taos_async_whitelist_fn_t fp, void *param); +func TaosFetchWhitelistA(taosConnect unsafe.Pointer, caller cgo.Handle) { + C.taos_fetch_whitelist_a_wrapper(taosConnect, caller.Pointer()) +} diff --git a/driver/wrapper/whitelist_test.go b/driver/wrapper/whitelist_test.go new file mode 100644 index 00000000..d985ff06 --- /dev/null +++ b/driver/wrapper/whitelist_test.go @@ -0,0 +1,21 @@ +package wrapper + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" +) + +func TestGetWhiteList(t *testing.T) { + conn, err := TaosConnect("", "root", "taosdata", "", 0) + assert.NoError(t, err) + defer TaosClose(conn) + c := make(chan *WhitelistResult, 1) + handler := cgo.NewHandle(c) + TaosFetchWhitelistA(conn, handler) + data := <-c + assert.Equal(t, int32(0), data.ErrCode) + assert.Equal(t, 1, len(data.IPNets)) + assert.Equal(t, "0.0.0.0/0", data.IPNets[0].String()) +} diff --git a/driver/wrapper/whitelistcb.go b/driver/wrapper/whitelistcb.go new file mode 100644 index 00000000..aab5471f --- /dev/null +++ b/driver/wrapper/whitelistcb.go @@ -0,0 +1,35 @@ +package wrapper + +import "C" +import ( + "net" + "unsafe" + + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" +) + +type WhitelistResult struct { + ErrCode int32 + IPNets []*net.IPNet +} + +//export WhitelistCallback +func WhitelistCallback(param unsafe.Pointer, code int, taosConnect unsafe.Pointer, numOfWhiteLists int, pWhiteLists unsafe.Pointer) { + c := (*(*cgo.Handle)(param)).Value().(chan *WhitelistResult) + if code != 0 { + c <- &WhitelistResult{ErrCode: int32(code)} + return + } + ips := make([]*net.IPNet, 0, numOfWhiteLists) + for i := 0; i < numOfWhiteLists; i++ { + ipNet := make([]byte, 8) + for j := 0; j < 8; j++ { + ipNet[j] = *(*byte)(unsafe.Pointer(uintptr(pWhiteLists) + uintptr(i*8) + uintptr(j))) + } + ip := net.IP{ipNet[0], ipNet[1], ipNet[2], ipNet[3]} + ones := int(ipNet[4]) + ipMask := net.CIDRMask(ones, 32) + ips = append(ips, &net.IPNet{IP: ip, Mask: ipMask}) + } + c <- &WhitelistResult{IPNets: ips} +} diff --git a/driver/wrapper/whitelistcb_test.go b/driver/wrapper/whitelistcb_test.go new file mode 100644 index 00000000..9cc62a4f --- /dev/null +++ b/driver/wrapper/whitelistcb_test.go @@ -0,0 +1,57 @@ +package wrapper + +import ( + "net" + "testing" + "unsafe" + + "github.com/stretchr/testify/assert" + "github.com/taosdata/taosadapter/v3/driver/wrapper/cgo" +) + +func TestWhitelistCallback_ErrorCode(t *testing.T) { + // Create a channel to receive the result + resultChan := make(chan *WhitelistResult, 1) + handle := cgo.NewHandle(resultChan) + // Simulate an error (code != 0) + go WhitelistCallback(handle.Pointer(), 1, nil, 0, nil) + + // Expect the result to have an error code + result := <-resultChan + assert.Equal(t, int32(1), result.ErrCode) + assert.Nil(t, result.IPNets) // No IPs should be returned +} + +func TestWhitelistCallback_Success(t *testing.T) { + // Prepare the test data: a list of byte slices representing IPs and masks + ipList := []byte{ + 192, 168, 1, 1, 24, // 192.168.1.1/24 + 0, 0, 0, + 10, 0, 0, 1, 16, // 10.0.0.1/16 + } + + // Create a channel to receive the result + resultChan := make(chan *WhitelistResult, 1) + + // Cast the byte slice to an unsafe pointer + pWhiteLists := unsafe.Pointer(&ipList[0]) + handle := cgo.NewHandle(resultChan) + // Simulate a successful callback (code == 0) + go WhitelistCallback(handle.Pointer(), 0, nil, 2, pWhiteLists) + + // Expect the result to have two IPNets + result := <-resultChan + assert.Equal(t, int32(0), result.ErrCode) + assert.Len(t, result.IPNets, 2) + + // Validate the first IPNet (192.168.1.1/24) + assert.Equal(t, net.IPv4(192, 168, 1, 1).To4(), result.IPNets[0].IP) + + ones, _ := result.IPNets[0].Mask.Size() + assert.Equal(t, 24, ones) + + // Validate the second IPNet (10.0.0.1/16) + assert.Equal(t, net.IPv4(10, 0, 0, 1).To4(), result.IPNets[1].IP) + ones, _ = result.IPNets[1].Mask.Size() + assert.Equal(t, 16, ones) +} diff --git a/go.mod b/go.mod index 1cecbbc9..07d23e92 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,6 @@ require ( github.com/spf13/viper v1.14.0 github.com/stretchr/testify v1.9.0 github.com/swaggo/swag v1.8.8 - github.com/taosdata/driver-go/v3 v3.5.1-0.20241101015534-8fb37f82db51 github.com/taosdata/file-rotatelogs/v2 v2.5.2 go.uber.org/automaxprocs v1.5.1 golang.org/x/sync v0.1.0 diff --git a/go.sum b/go.sum index ba7dcb3b..5e9b5f10 100644 --- a/go.sum +++ b/go.sum @@ -2609,8 +2609,6 @@ github.com/swaggo/swag v1.8.8/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9e github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/taosdata/driver-go/v3 v3.5.1-0.20241101015534-8fb37f82db51 h1:diWG8X6vBAERTPk9DnsMfcFH/gN5s9v/WZ2C+gJMcs4= -github.com/taosdata/driver-go/v3 v3.5.1-0.20241101015534-8fb37f82db51/go.mod h1:H2vo/At+rOPY1aMzUV9P49SVX7NlXb3LAbKw+MCLrmU= github.com/taosdata/file-rotatelogs/v2 v2.5.2 h1:6ryjwDdKqQtWrkVq9OKj4gvMING/f+fDluMAAe2DIXQ= github.com/taosdata/file-rotatelogs/v2 v2.5.2/go.mod h1:Qm99Lh0iMZouGgyy++JgTqKvP5FQw1ruR5jkWF7e1n0= github.com/tbrandon/mbserver v0.0.0-20170611213546-993e1772cc62/go.mod h1:qUzPVlSj2UgxJkVbH0ZwuuiR46U8RBMDT5KLY78Ifpw= diff --git a/plugin/collectd/config.go b/plugin/collectd/config.go index 2f96ab49..388727af 100644 --- a/plugin/collectd/config.go +++ b/plugin/collectd/config.go @@ -3,7 +3,7 @@ package collectd import ( "github.com/spf13/pflag" "github.com/spf13/viper" - "github.com/taosdata/driver-go/v3/common" + "github.com/taosdata/taosadapter/v3/driver/common" ) type Config struct { diff --git a/plugin/collectd/plugin_test.go b/plugin/collectd/plugin_test.go index ec64395d..e69501f8 100644 --- a/plugin/collectd/plugin_test.go +++ b/plugin/collectd/plugin_test.go @@ -7,16 +7,17 @@ import ( "net" "testing" "time" + "unsafe" "collectd.org/api" "collectd.org/network" "github.com/spf13/viper" "github.com/stretchr/testify/assert" - "github.com/taosdata/driver-go/v3/af" - "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/db" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" ) // @author: xftan @@ -28,15 +29,12 @@ func TestCollectd(t *testing.T) { t.Error(err) return } - afC, err := af.NewConnector(conn) - assert.NoError(t, err) defer func() { - err = afC.Close() - assert.NoError(t, err) + wrapper.TaosClose(conn) }() - _, err = afC.Exec("drop database if exists collectd") + err = exec(conn, "drop database if exists collectd") assert.NoError(t, err) - _, err = afC.Exec("create database if not exists collectd") + err = exec(conn, "create database if not exists collectd") assert.NoError(t, err) //nolint:staticcheck rand.Seed(time.Now().UnixNano()) @@ -99,36 +97,60 @@ func TestCollectd(t *testing.T) { } wrapper.TaosFreeResult(r) }() - r, err := afC.Query("select last(`value`) from collectd.`cpu_value`") - if err != nil { - t.Error(err) - return - } - defer func() { - err = r.Close() - assert.NoError(t, err) - }() - values := make([]driver.Value, 1) - err = r.Next(values) + values, err := query(conn, "select last(`value`) from collectd.`cpu_value`") assert.NoError(t, err) - if int32(values[0].(float64)) != number { + if int32(values[0][0].(float64)) != number { t.Errorf("got %f expect %d", values[0], number) } - r, err = afC.Query("select `ttl` from information_schema.ins_tables " + + values, err = query(conn, "select `ttl` from information_schema.ins_tables "+ " where db_name='collectd' and stable_name='cpu_value'") if err != nil { t.Error(err) return } - defer func() { - err = r.Close() - assert.NoError(t, err) - }() - values = make([]driver.Value, 1) - err = r.Next(values) - assert.NoError(t, err) - if values[0].(int32) != 1000 { + if values[0][0].(int32) != 1000 { t.Fatal("ttl miss") } } + +func exec(conn unsafe.Pointer, sql string) error { + res := wrapper.TaosQuery(conn, sql) + defer wrapper.TaosFreeResult(res) + code := wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + return errors.NewError(code, errStr) + } + return nil +} + +func query(conn unsafe.Pointer, sql string) ([][]driver.Value, error) { + res := wrapper.TaosQuery(conn, sql) + defer wrapper.TaosFreeResult(res) + code := wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + return nil, errors.NewError(code, errStr) + } + fileCount := wrapper.TaosNumFields(res) + rh, err := wrapper.ReadColumn(res, fileCount) + if err != nil { + return nil, err + } + precision := wrapper.TaosResultPrecision(res) + var result [][]driver.Value + for { + columns, errCode, block := wrapper.TaosFetchRawBlock(res) + if errCode != 0 { + errStr := wrapper.TaosErrorStr(res) + return nil, errors.NewError(errCode, errStr) + } + if columns == 0 { + break + } + r := parser.ReadBlock(block, columns, rh.ColTypes, precision) + result = append(result, r...) + } + return result, nil +} diff --git a/plugin/influxdb/plugin.go b/plugin/influxdb/plugin.go index 642f58ce..3c1696ac 100644 --- a/plugin/influxdb/plugin.go +++ b/plugin/influxdb/plugin.go @@ -7,9 +7,9 @@ import ( "strings" "github.com/gin-gonic/gin" - tErrors "github.com/taosdata/driver-go/v3/errors" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/db/commonpool" + tErrors "github.com/taosdata/taosadapter/v3/driver/errors" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/monitor" "github.com/taosdata/taosadapter/v3/plugin" diff --git a/plugin/influxdb/plugin_test.go b/plugin/influxdb/plugin_test.go index 2c19cddc..552f2468 100644 --- a/plugin/influxdb/plugin_test.go +++ b/plugin/influxdb/plugin_test.go @@ -9,15 +9,16 @@ import ( "strings" "testing" "time" + "unsafe" "github.com/gin-gonic/gin" "github.com/spf13/viper" "github.com/stretchr/testify/assert" - "github.com/taosdata/driver-go/v3/af" - "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/db" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/log" ) @@ -40,22 +41,14 @@ func TestInfluxdb(t *testing.T) { t.Error(err) return } - afC, err := af.NewConnector(conn) - assert.NoError(t, err) defer func() { - err = afC.Close() - assert.NoError(t, err) + wrapper.TaosClose(conn) }() - _, err = afC.Exec("create database if not exists test_plugin_influxdb") + err = exec(conn, "drop database if exists test_plugin_influxdb") assert.NoError(t, err) defer func() { - r := wrapper.TaosQuery(conn, "drop database if exists test_plugin_influxdb") - code := wrapper.TaosError(r) - if code != 0 { - errStr := wrapper.TaosErrorStr(r) - t.Error(errors.NewError(code, errStr)) - } - wrapper.TaosFreeResult(r) + err = exec(conn, "drop database if exists test_plugin_influxdb") + assert.NoError(t, err) }() err = p.Init(router) assert.NoError(t, err) @@ -85,29 +78,14 @@ func TestInfluxdb(t *testing.T) { router.ServeHTTP(w, req) assert.Equal(t, 400, w.Code) time.Sleep(time.Second) - r, err := afC.Query("select last(*) from test_plugin_influxdb.`measurement`") - if err != nil { - t.Error(err) - return - } - defer func() { - err = r.Close() - assert.NoError(t, err) - }() - fieldCount := len(r.Columns()) - values := make([]driver.Value, fieldCount) - err = r.Next(values) + values, err := query(conn, "select * from test_plugin_influxdb.`measurement`") assert.NoError(t, err) - keyMap := map[string]int{} - for i, s := range r.Columns() { - keyMap[s] = i - } - if values[3].(string) != "Launch 🚀" { - t.Errorf("got %s expect %s", values[3], "Launch 🚀") + if values[0][3].(string) != "Launch 🚀" { + t.Errorf("got %s expect %s", values[0][3], "Launch 🚀") return } - if int32(values[1].(int64)) != number { - t.Errorf("got %d expect %d", values[1].(int64), number) + if int32(values[0][1].(int64)) != number { + t.Errorf("got %d expect %d", values[0][1].(int64), number) return } @@ -117,21 +95,51 @@ func TestInfluxdb(t *testing.T) { req.RemoteAddr = "127.0.0.1:33333" router.ServeHTTP(w, req) time.Sleep(time.Second) - - r, err = afC.Query("select `ttl` from information_schema.ins_tables " + + values, err = query(conn, "select `ttl` from information_schema.ins_tables "+ " where db_name='test_plugin_influxdb_ttl' and stable_name='measurement_ttl'") - if err != nil { - t.Error(err) - return - } - defer func() { - err = r.Close() - assert.NoError(t, err) - }() - values = make([]driver.Value, 1) - err = r.Next(values) assert.NoError(t, err) - if values[0].(int32) != 1000 { + if values[0][0].(int32) != 1000 { t.Fatal("ttl miss") } } + +func exec(conn unsafe.Pointer, sql string) error { + res := wrapper.TaosQuery(conn, sql) + defer wrapper.TaosFreeResult(res) + code := wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + return errors.NewError(code, errStr) + } + return nil +} + +func query(conn unsafe.Pointer, sql string) ([][]driver.Value, error) { + res := wrapper.TaosQuery(conn, sql) + defer wrapper.TaosFreeResult(res) + code := wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + return nil, errors.NewError(code, errStr) + } + fileCount := wrapper.TaosNumFields(res) + rh, err := wrapper.ReadColumn(res, fileCount) + if err != nil { + return nil, err + } + precision := wrapper.TaosResultPrecision(res) + var result [][]driver.Value + for { + columns, errCode, block := wrapper.TaosFetchRawBlock(res) + if errCode != 0 { + errStr := wrapper.TaosErrorStr(res) + return nil, errors.NewError(errCode, errStr) + } + if columns == 0 { + break + } + r := parser.ReadBlock(block, columns, rh.ColTypes, precision) + result = append(result, r...) + } + return result, nil +} diff --git a/plugin/nodeexporter/config.go b/plugin/nodeexporter/config.go index 340db76c..684a3637 100644 --- a/plugin/nodeexporter/config.go +++ b/plugin/nodeexporter/config.go @@ -5,7 +5,7 @@ import ( "github.com/spf13/pflag" "github.com/spf13/viper" - "github.com/taosdata/driver-go/v3/common" + "github.com/taosdata/taosadapter/v3/driver/common" ) type Config struct { diff --git a/plugin/nodeexporter/plugin.go b/plugin/nodeexporter/plugin.go index 61e39cf0..7b0e0658 100644 --- a/plugin/nodeexporter/plugin.go +++ b/plugin/nodeexporter/plugin.go @@ -17,8 +17,8 @@ import ( "github.com/gin-gonic/gin" tmetric "github.com/influxdata/telegraf/metric" "github.com/influxdata/telegraf/plugins/serializers/influx" - "github.com/taosdata/driver-go/v3/common" "github.com/taosdata/taosadapter/v3/db/commonpool" + "github.com/taosdata/taosadapter/v3/driver/common" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/monitor" "github.com/taosdata/taosadapter/v3/plugin" diff --git a/plugin/nodeexporter/plugin_test.go b/plugin/nodeexporter/plugin_test.go index c6340d87..852af10b 100644 --- a/plugin/nodeexporter/plugin_test.go +++ b/plugin/nodeexporter/plugin_test.go @@ -6,12 +6,15 @@ import ( "net/http/httptest" "testing" "time" + "unsafe" "github.com/spf13/viper" "github.com/stretchr/testify/assert" - "github.com/taosdata/driver-go/v3/af" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/db" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/log" ) @@ -52,17 +55,14 @@ func TestNodeExporter_Gather(t *testing.T) { viper.Set("node_exporter.urls", []string{api}) viper.Set("node_exporter.gatherDuration", time.Second) viper.Set("node_exporter.ttl", 1000) - conn, err := af.Open("", "", "", "", 0) + conn, err := wrapper.TaosConnect("", "root", "taosdata", "", 0) assert.NoError(t, err) defer func() { - err = conn.Close() - if err != nil { - t.Error(err) - } + wrapper.TaosClose(conn) }() - _, err = conn.Exec("create database if not exists node_exporter precision 'ns'") + err = exec(conn, "drop database if exists node_exporter") assert.NoError(t, err) - err = conn.SelectDB("node_exporter") + err = exec(conn, "use node_exporter") assert.NoError(t, err) n := NodeExporter{} err = n.Init(nil) @@ -70,41 +70,58 @@ func TestNodeExporter_Gather(t *testing.T) { err = n.Start() assert.NoError(t, err) time.Sleep(time.Second * 2) - rows, err := conn.Query("select last(`value`) as `value` from node_exporter.test_metric;") - assert.NoError(t, err) - defer func() { - err = rows.Close() - if err != nil { - t.Error(err) - } - }() - assert.Equal(t, 1, len(rows.Columns())) - d := make([]driver.Value, 1) - err = rows.Next(d) + values, err := query(conn, "select last(`value`) as `value` from node_exporter.go_gc_duration_seconds;") assert.NoError(t, err) - assert.Equal(t, float64(1), d[0]) + assert.Equal(t, float64(1), values[0][0]) err = n.Stop() assert.NoError(t, err) - - rows, err = conn.Query("select `ttl` from information_schema.ins_tables " + + values, err = query(conn, "select `ttl` from information_schema.ins_tables "+ " where db_name='node_exporter' and stable_name='test_metric'") - if err != nil { - t.Error(err) - return - } - defer func() { - err = rows.Close() - if err != nil { - t.Error(err) - } - }() - values := make([]driver.Value, 1) - err = rows.Next(values) assert.NoError(t, err) - if values[0].(int32) != 1000 { + if values[0][0].(int32) != 1000 { t.Fatal("ttl miss") } - - _, err = conn.Exec("drop database if exists node_exporter") + err = exec(conn, "drop database if exists node_exporter") assert.NoError(t, err) } + +func exec(conn unsafe.Pointer, sql string) error { + res := wrapper.TaosQuery(conn, sql) + defer wrapper.TaosFreeResult(res) + code := wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + return errors.NewError(code, errStr) + } + return nil +} + +func query(conn unsafe.Pointer, sql string) ([][]driver.Value, error) { + res := wrapper.TaosQuery(conn, sql) + defer wrapper.TaosFreeResult(res) + code := wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + return nil, errors.NewError(code, errStr) + } + fileCount := wrapper.TaosNumFields(res) + rh, err := wrapper.ReadColumn(res, fileCount) + if err != nil { + return nil, err + } + precision := wrapper.TaosResultPrecision(res) + var result [][]driver.Value + for { + columns, errCode, block := wrapper.TaosFetchRawBlock(res) + if errCode != 0 { + errStr := wrapper.TaosErrorStr(res) + return nil, errors.NewError(errCode, errStr) + } + if columns == 0 { + break + } + r := parser.ReadBlock(block, columns, rh.ColTypes, precision) + result = append(result, r...) + } + return result, nil +} diff --git a/plugin/opentsdb/plugin.go b/plugin/opentsdb/plugin.go index 0ceb0477..f22cfc35 100644 --- a/plugin/opentsdb/plugin.go +++ b/plugin/opentsdb/plugin.go @@ -9,9 +9,9 @@ import ( "strconv" "github.com/gin-gonic/gin" - tErrors "github.com/taosdata/driver-go/v3/errors" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/db/commonpool" + tErrors "github.com/taosdata/taosadapter/v3/driver/errors" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/monitor" "github.com/taosdata/taosadapter/v3/plugin" diff --git a/plugin/opentsdb/plugin_test.go b/plugin/opentsdb/plugin_test.go index 22451901..a2101fa0 100644 --- a/plugin/opentsdb/plugin_test.go +++ b/plugin/opentsdb/plugin_test.go @@ -9,15 +9,16 @@ import ( "strings" "testing" "time" + "unsafe" "github.com/gin-gonic/gin" "github.com/spf13/viper" "github.com/stretchr/testify/assert" - "github.com/taosdata/driver-go/v3/af" - "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/db" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/log" ) @@ -41,13 +42,10 @@ func TestOpentsdb(t *testing.T) { t.Error(err) return } - afC, err := af.NewConnector(conn) - assert.NoError(t, err) defer func() { - err = afC.Close() - assert.NoError(t, err) + wrapper.TaosClose(conn) }() - _, err = afC.Exec("create database if not exists test_plugin_opentsdb_http_json") + err = exec(conn, "drop database if exists test_plugin_opentsdb_http_telnet") assert.NoError(t, err) err = p.Init(router) assert.NoError(t, err) @@ -82,87 +80,74 @@ func TestOpentsdb(t *testing.T) { assert.Equal(t, 204, w.Code) defer func() { - r := wrapper.TaosQuery(conn, "drop database if exists test_plugin_opentsdb_http_json") - code := wrapper.TaosError(r) - if code != 0 { - errStr := wrapper.TaosErrorStr(r) - t.Error(errors.NewError(code, errStr)) - } - wrapper.TaosFreeResult(r) - }() - defer func() { - r := wrapper.TaosQuery(conn, "drop database if exists test_plugin_opentsdb_http_telnet") - code := wrapper.TaosError(r) - if code != 0 { - errStr := wrapper.TaosErrorStr(r) - t.Error(errors.NewError(code, errStr)) - } - wrapper.TaosFreeResult(r) + err = exec(conn, "drop database if exists test_plugin_opentsdb_http_json") + assert.NoError(t, err) }() - - r, err := afC.Query("select last(_value) from test_plugin_opentsdb_http_json.`sys_cpu_nice`") - if err != nil { - t.Error(err) - return - } defer func() { - err = r.Close() + err = exec(conn, "drop database if exists test_plugin_opentsdb_http_telnet") assert.NoError(t, err) }() - values := make([]driver.Value, 1) - err = r.Next(values) + values, err := query(conn, "select last(_value) from test_plugin_opentsdb_http_json.`sys_cpu_nice`") assert.NoError(t, err) - if int32(values[0].(float64)) != number { + if int32(values[0][0].(float64)) != number { t.Errorf("got %f expect %d", values[0], number) } - - r2, err := afC.Query("select last(_value) from test_plugin_opentsdb_http_telnet.`metric`") - if err != nil { - t.Error(err) - return - } - defer func() { - err = r2.Close() - assert.NoError(t, err) - }() - values = make([]driver.Value, 1) - err = r2.Next(values) + values, err = query(conn, "select last(_value) from test_plugin_opentsdb_http_telnet.`metric`") assert.NoError(t, err) - if int32(values[0].(float64)) != number { + if int32(values[0][0].(float64)) != number { t.Errorf("got %f expect %d", values[0], number) } - - rows, err := afC.Query("select `ttl` from information_schema.ins_tables " + + values, err = query(conn, "select `ttl` from information_schema.ins_tables "+ " where db_name='test_plugin_opentsdb_http_json' and stable_name='sys_cpu_nice'") - if err != nil { - t.Error(err) - return + assert.NoError(t, err) + if values[0][0].(int32) != 1000 { + t.Fatal("ttl miss") } - defer func() { - err = rows.Close() - assert.NoError(t, err) - }() - values = make([]driver.Value, 1) - err = rows.Next(values) + values, err = query(conn, "select `ttl` from information_schema.ins_tables "+ + " where db_name='test_plugin_opentsdb_http_telnet' and stable_name='metric'") assert.NoError(t, err) - if values[0].(int32) != 1000 { + if values[0][0].(int32) != 1000 { t.Fatal("ttl miss") } +} - rows, err = afC.Query("select `ttl` from information_schema.ins_tables " + - " where db_name='test_plugin_opentsdb_http_telnet' and stable_name='metric'") +func exec(conn unsafe.Pointer, sql string) error { + res := wrapper.TaosQuery(conn, sql) + defer wrapper.TaosFreeResult(res) + code := wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + return errors.NewError(code, errStr) + } + return nil +} + +func query(conn unsafe.Pointer, sql string) ([][]driver.Value, error) { + res := wrapper.TaosQuery(conn, sql) + defer wrapper.TaosFreeResult(res) + code := wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + return nil, errors.NewError(code, errStr) + } + fileCount := wrapper.TaosNumFields(res) + rh, err := wrapper.ReadColumn(res, fileCount) if err != nil { - t.Error(err) - return + return nil, err } - defer func() { - err = rows.Close() - assert.NoError(t, err) - }() - values = make([]driver.Value, 1) - err = rows.Next(values) - assert.NoError(t, err) - if values[0].(int32) != 1000 { - t.Fatal("ttl miss") + precision := wrapper.TaosResultPrecision(res) + var result [][]driver.Value + for { + columns, errCode, block := wrapper.TaosFetchRawBlock(res) + if errCode != 0 { + errStr := wrapper.TaosErrorStr(res) + return nil, errors.NewError(errCode, errStr) + } + if columns == 0 { + break + } + r := parser.ReadBlock(block, columns, rh.ColTypes, precision) + result = append(result, r...) } + return result, nil } diff --git a/plugin/opentsdbtelnet/config.go b/plugin/opentsdbtelnet/config.go index 99aadb5b..9b79858b 100644 --- a/plugin/opentsdbtelnet/config.go +++ b/plugin/opentsdbtelnet/config.go @@ -5,7 +5,7 @@ import ( "github.com/spf13/pflag" "github.com/spf13/viper" - "github.com/taosdata/driver-go/v3/common" + "github.com/taosdata/taosadapter/v3/driver/common" ) type Config struct { diff --git a/plugin/opentsdbtelnet/plugin_test.go b/plugin/opentsdbtelnet/plugin_test.go index d2c8b3f4..a8b937ee 100644 --- a/plugin/opentsdbtelnet/plugin_test.go +++ b/plugin/opentsdbtelnet/plugin_test.go @@ -7,14 +7,15 @@ import ( "net" "testing" "time" + "unsafe" "github.com/spf13/viper" "github.com/stretchr/testify/assert" - "github.com/taosdata/driver-go/v3/af" - "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/db" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/plugin/opentsdbtelnet" ) @@ -37,13 +38,10 @@ func TestPlugin(t *testing.T) { t.Error(err) return } - afC, err := af.NewConnector(conn) - assert.NoError(t, err) defer func() { - err = afC.Close() - assert.NoError(t, err) + wrapper.TaosClose(conn) }() - _, err = afC.Exec("create database if not exists opentsdb_telnet") + err = exec(conn, "drop database if exists opentsdb_telnet") assert.NoError(t, err) err = p.Init(nil) assert.NoError(t, err) @@ -73,37 +71,56 @@ func TestPlugin(t *testing.T) { } wrapper.TaosFreeResult(r) }() - - r, err := afC.Query("select last(_value) from opentsdb_telnet.`sys_if_bytes_out`") - if err != nil { - t.Error(err) - return - } - defer func() { - err = r.Close() - assert.NoError(t, err) - }() - values := make([]driver.Value, 1) - err = r.Next(values) + values, err := query(conn, "select last(_value) from opentsdb_telnet.`sys_if_bytes_out`") assert.NoError(t, err) - if int32(values[0].(float64)) != number { + if int32(values[0][0].(float64)) != number { t.Errorf("got %f expect %d", values[0], number) } - - rows, err := afC.Query("select `ttl` from information_schema.ins_tables " + + values, err = query(conn, "select `ttl` from information_schema.ins_tables "+ " where db_name='opentsdb_telnet' and stable_name='sys_if_bytes_out'") - if err != nil { - t.Error(err) - return - } - defer func() { - err = rows.Close() - assert.NoError(t, err) - }() - values = make([]driver.Value, 1) - err = rows.Next(values) assert.NoError(t, err) - if values[0].(int32) != 1000 { + if values[0][0].(int32) != 1000 { t.Fatal("ttl miss") } } + +func exec(conn unsafe.Pointer, sql string) error { + res := wrapper.TaosQuery(conn, sql) + defer wrapper.TaosFreeResult(res) + code := wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + return errors.NewError(code, errStr) + } + return nil +} + +func query(conn unsafe.Pointer, sql string) ([][]driver.Value, error) { + res := wrapper.TaosQuery(conn, sql) + defer wrapper.TaosFreeResult(res) + code := wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + return nil, errors.NewError(code, errStr) + } + fileCount := wrapper.TaosNumFields(res) + rh, err := wrapper.ReadColumn(res, fileCount) + if err != nil { + return nil, err + } + precision := wrapper.TaosResultPrecision(res) + var result [][]driver.Value + for { + columns, errCode, block := wrapper.TaosFetchRawBlock(res) + if errCode != 0 { + errStr := wrapper.TaosErrorStr(res) + return nil, errors.NewError(errCode, errStr) + } + if columns == 0 { + break + } + r := parser.ReadBlock(block, columns, rh.ColTypes, precision) + result = append(result, r...) + } + return result, nil +} diff --git a/plugin/prometheus/plugin.go b/plugin/prometheus/plugin.go index 5b462a1f..13744925 100644 --- a/plugin/prometheus/plugin.go +++ b/plugin/prometheus/plugin.go @@ -10,8 +10,8 @@ import ( "github.com/gogo/protobuf/proto" "github.com/golang/snappy" "github.com/prometheus/prometheus/prompb" - tErrors "github.com/taosdata/driver-go/v3/errors" "github.com/taosdata/taosadapter/v3/db/commonpool" + tErrors "github.com/taosdata/taosadapter/v3/driver/errors" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/monitor" "github.com/taosdata/taosadapter/v3/plugin" diff --git a/plugin/prometheus/plugin_test.go b/plugin/prometheus/plugin_test.go index 76409f49..dfcca1ea 100644 --- a/plugin/prometheus/plugin_test.go +++ b/plugin/prometheus/plugin_test.go @@ -14,9 +14,9 @@ import ( "github.com/prometheus/prometheus/prompb" "github.com/spf13/viper" "github.com/stretchr/testify/assert" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/db" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/log" ) diff --git a/plugin/prometheus/process.go b/plugin/prometheus/process.go index f1880fb7..10b9973b 100644 --- a/plugin/prometheus/process.go +++ b/plugin/prometheus/process.go @@ -16,13 +16,13 @@ import ( jsoniter "github.com/json-iterator/go" "github.com/prometheus/prometheus/prompb" - "github.com/taosdata/driver-go/v3/common" - tErrors "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/db/async" "github.com/taosdata/taosadapter/v3/db/syncinterface" "github.com/taosdata/taosadapter/v3/db/tool" + "github.com/taosdata/taosadapter/v3/driver/common" + tErrors "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/httperror" "github.com/taosdata/taosadapter/v3/log" prompbWrite "github.com/taosdata/taosadapter/v3/plugin/prometheus/proto/write" diff --git a/plugin/statsd/config.go b/plugin/statsd/config.go index 66e49f6f..98139851 100644 --- a/plugin/statsd/config.go +++ b/plugin/statsd/config.go @@ -5,7 +5,7 @@ import ( "github.com/spf13/pflag" "github.com/spf13/viper" - "github.com/taosdata/driver-go/v3/common" + "github.com/taosdata/taosadapter/v3/driver/common" ) type Config struct { diff --git a/plugin/statsd/plugin_test.go b/plugin/statsd/plugin_test.go index 14b3aee5..2bcb2bd6 100644 --- a/plugin/statsd/plugin_test.go +++ b/plugin/statsd/plugin_test.go @@ -7,14 +7,15 @@ import ( "net" "testing" "time" + "unsafe" "github.com/spf13/viper" "github.com/stretchr/testify/assert" - "github.com/taosdata/driver-go/v3/af" - "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/db" + "github.com/taosdata/taosadapter/v3/driver/common/parser" + "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/log" ) @@ -36,13 +37,10 @@ func TestStatsd(t *testing.T) { t.Error(err) return } - afC, err := af.NewConnector(conn) - assert.NoError(t, err) defer func() { - err = afC.Close() - assert.NoError(t, err) + wrapper.TaosClose(conn) }() - _, err = afC.Exec("create database if not exists statsd") + err = exec(conn, "drop database if exists statsd") assert.NoError(t, err) err = p.Init(nil) assert.NoError(t, err) @@ -68,37 +66,56 @@ func TestStatsd(t *testing.T) { } wrapper.TaosFreeResult(r) }() - - r, err := afC.Query("select last(`value`) from statsd.`foo`") - if err != nil { - t.Error(err) - return - } - defer func() { - err = r.Close() - assert.NoError(t, err) - }() - values := make([]driver.Value, 1) - err = r.Next(values) + values, err := query(conn, "select last(`value`) from statsd.`foo`") assert.NoError(t, err) - if int32(values[0].(int64)) != number { + if int32(values[0][0].(int64)) != number { t.Errorf("got %f expect %d", values[0], number) } - - rows, err := afC.Query("select `ttl` from information_schema.ins_tables " + + values, err = query(conn, "select `ttl` from information_schema.ins_tables "+ " where db_name='statsd' and stable_name='foo'") - if err != nil { - t.Error(err) - return - } - defer func() { - err = rows.Close() - assert.NoError(t, err) - }() - values = make([]driver.Value, 1) - err = rows.Next(values) assert.NoError(t, err) - if values[0].(int32) != 1000 { + if values[0][0].(int32) != 1000 { t.Fatal("ttl miss") } } + +func exec(conn unsafe.Pointer, sql string) error { + res := wrapper.TaosQuery(conn, sql) + defer wrapper.TaosFreeResult(res) + code := wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + return errors.NewError(code, errStr) + } + return nil +} + +func query(conn unsafe.Pointer, sql string) ([][]driver.Value, error) { + res := wrapper.TaosQuery(conn, sql) + defer wrapper.TaosFreeResult(res) + code := wrapper.TaosError(res) + if code != 0 { + errStr := wrapper.TaosErrorStr(res) + return nil, errors.NewError(code, errStr) + } + fileCount := wrapper.TaosNumFields(res) + rh, err := wrapper.ReadColumn(res, fileCount) + if err != nil { + return nil, err + } + precision := wrapper.TaosResultPrecision(res) + var result [][]driver.Value + for { + columns, errCode, block := wrapper.TaosFetchRawBlock(res) + if errCode != 0 { + errStr := wrapper.TaosErrorStr(res) + return nil, errors.NewError(errCode, errStr) + } + if columns == 0 { + break + } + r := parser.ReadBlock(block, columns, rh.ColTypes, precision) + result = append(result, r...) + } + return result, nil +} diff --git a/schemaless/capi/influxdb.go b/schemaless/capi/influxdb.go index 2cc4b1ed..3e90064c 100644 --- a/schemaless/capi/influxdb.go +++ b/schemaless/capi/influxdb.go @@ -5,10 +5,10 @@ import ( "unsafe" "github.com/sirupsen/logrus" - tErrors "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/db/syncinterface" "github.com/taosdata/taosadapter/v3/db/tool" + tErrors "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/tools/generator" ) diff --git a/schemaless/capi/influxdb_test.go b/schemaless/capi/influxdb_test.go index 45527e6f..755ca8f4 100644 --- a/schemaless/capi/influxdb_test.go +++ b/schemaless/capi/influxdb_test.go @@ -4,8 +4,8 @@ import ( "testing" "unsafe" - "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" + "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/schemaless/capi" ) diff --git a/schemaless/capi/opentsdb.go b/schemaless/capi/opentsdb.go index 67725e03..20aea201 100644 --- a/schemaless/capi/opentsdb.go +++ b/schemaless/capi/opentsdb.go @@ -5,10 +5,10 @@ import ( "unsafe" "github.com/sirupsen/logrus" - tErrors "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/db/syncinterface" "github.com/taosdata/taosadapter/v3/db/tool" + tErrors "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/tools/generator" ) diff --git a/schemaless/capi/opentsdb_test.go b/schemaless/capi/opentsdb_test.go index c6e52e92..8207471b 100644 --- a/schemaless/capi/opentsdb_test.go +++ b/schemaless/capi/opentsdb_test.go @@ -7,10 +7,10 @@ import ( "unsafe" "github.com/spf13/viper" - "github.com/taosdata/driver-go/v3/errors" - "github.com/taosdata/driver-go/v3/wrapper" "github.com/taosdata/taosadapter/v3/config" "github.com/taosdata/taosadapter/v3/db" + "github.com/taosdata/taosadapter/v3/driver/errors" + "github.com/taosdata/taosadapter/v3/driver/wrapper" "github.com/taosdata/taosadapter/v3/log" "github.com/taosdata/taosadapter/v3/schemaless/capi" ) diff --git a/tools/ctools/block.go b/tools/ctools/block.go index 0f73900e..72f0f08f 100644 --- a/tools/ctools/block.go +++ b/tools/ctools/block.go @@ -5,8 +5,8 @@ import ( "strconv" "unsafe" - "github.com/taosdata/driver-go/v3/common" - "github.com/taosdata/driver-go/v3/common/parser" + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/common/parser" "github.com/taosdata/taosadapter/v3/tools" "github.com/taosdata/taosadapter/v3/tools/jsonbuilder" ) diff --git a/tools/ctools/block_test.go b/tools/ctools/block_test.go index 79e7e1ca..03390fba 100644 --- a/tools/ctools/block_test.go +++ b/tools/ctools/block_test.go @@ -7,8 +7,8 @@ import ( "unsafe" "github.com/stretchr/testify/assert" - "github.com/taosdata/driver-go/v3/common" - "github.com/taosdata/driver-go/v3/common/parser" + "github.com/taosdata/taosadapter/v3/driver/common" + "github.com/taosdata/taosadapter/v3/driver/common/parser" "github.com/taosdata/taosadapter/v3/tools" "github.com/taosdata/taosadapter/v3/tools/jsonbuilder" ) diff --git a/tools/parseblock/parse.go b/tools/parseblock/parse.go index a7abbc7e..cf98fe45 100644 --- a/tools/parseblock/parse.go +++ b/tools/parseblock/parse.go @@ -4,7 +4,7 @@ import ( "database/sql/driver" "unsafe" - "github.com/taosdata/driver-go/v3/common/parser" + "github.com/taosdata/taosadapter/v3/driver/common/parser" "github.com/taosdata/taosadapter/v3/tools" ) diff --git a/tools/parseblock/parse_test.go b/tools/parseblock/parse_test.go index 611b1404..877353af 100644 --- a/tools/parseblock/parse_test.go +++ b/tools/parseblock/parse_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/taosdata/driver-go/v3/common" + "github.com/taosdata/taosadapter/v3/driver/common" ) func TestParseBlock(t *testing.T) { diff --git a/version/version.go b/version/version.go index 235bc0d8..4baa673d 100644 --- a/version/version.go +++ b/version/version.go @@ -1,6 +1,6 @@ package version -import "github.com/taosdata/driver-go/v3/wrapper" +import "github.com/taosdata/taosadapter/v3/driver/wrapper" var Version = "0.1.0"