ruby_snowflake_client 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df1c4a15dca0f71a8d675287c635eec979e7f082164ef0a46e38922d935c64c5
4
- data.tar.gz: 5aaeb005099a2726e2d18c38e863d24e0a5a13f605df29794a8e94797e6d88c2
3
+ metadata.gz: 75899b8f6ba98da01620d76f6d7077ba73ae8dd7fb1177815300b969d0dbf7b5
4
+ data.tar.gz: 2fdbd266149c69bf17d59c83ab09418ca8f46d23707c5c3d98cc8e6800bf84e2
5
5
  SHA512:
6
- metadata.gz: 5def488e978d41d6815d102f8b9e33cee3746720ab3ed60720d65ab8f285ab3801611481398ca7e4ab17b22ca8b0c4ccf8183e309d4f7468753065f14c75d5e6
7
- data.tar.gz: 462d4579b9cac2309f77e0aafcdf1dcb9d41b263ab44831166eebb5821e2989f1d37b4150126c917f6514ec86d6c93e5a1b4c65ec85464894fa37ecdcebd0a4f
6
+ metadata.gz: 180122aebc707aeda9e7cff2fad9ab6361b21d027b4dc23961bb64319ae192338091fff3b5600cbc15a86522c39bff2b4402723c7c0dfbd80570bdea3e3f96cb
7
+ data.tar.gz: 65ab2555c42a044224ee1f5e010f43f7ddc9491205d271bd4459459ee5ffb3f65112bd310742c1bcab797c66e8a590e96f732032142ba3866e2e91afb5feb40b
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby_snowflake_client (1.0.1)
4
+ ruby_snowflake_client (1.1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/ext/c-decl.go CHANGED
@@ -29,9 +29,9 @@ func goobj_log(obj unsafe.Pointer) {
29
29
  }
30
30
 
31
31
  //export goobj_retain
32
- func goobj_retain(obj unsafe.Pointer) {
32
+ func goobj_retain(obj unsafe.Pointer, x *C.char) {
33
33
  if LOG_LEVEL > 0 {
34
- fmt.Printf("retain obj %v - currently keeping %d\n", obj, len(objects))
34
+ fmt.Printf("retain obj [%v] %v - currently keeping %d\n", C.GoString(x), obj, len(objects))
35
35
  }
36
36
  objects[obj] = true
37
37
  marked[obj] = 0
data/ext/result.go CHANGED
@@ -11,28 +11,58 @@ VALUE funcall0param(VALUE obj, ID id);
11
11
  import "C"
12
12
 
13
13
  import (
14
- "errors"
15
14
  "fmt"
16
- "io"
17
15
  "math/big"
18
- "strings"
19
16
  "time"
20
17
 
21
18
  gopointer "github.com/mattn/go-pointer"
22
19
  )
23
20
 
21
+ func wrapRbRaise(err error) {
22
+ fmt.Printf("[ruby-snowflake-client] Error encountered: %s\n", err.Error())
23
+ fmt.Printf("[ruby-snowflake-client] Will call `rb_raise`\n")
24
+ rb_raise(C.rb_eArgError, "%s", err.Error())
25
+ }
26
+
24
27
  func getResultStruct(self C.VALUE) *SnowflakeResult {
25
- ivar := C.rb_ivar_get(self, RESULT_IDENTIFIER)
26
-
27
- str := GetGoStruct(ivar)
28
- ptr := gopointer.Restore(str)
29
- sr, ok := ptr.(*SnowflakeResult)
30
- if !ok || sr.rows == nil {
31
- rb_raise(C.rb_eArgError, "%s", errors.New("Empty result; please run a query via `client.fetch(\"SQL\")`"))
32
- return nil
28
+ return resultMap[self]
29
+ }
30
+
31
+ //export GetRowsNoEnum
32
+ func GetRowsNoEnum(self C.VALUE) C.VALUE {
33
+ res := getResultStruct(self)
34
+ rows := res.rows
35
+
36
+ i := 0
37
+ t1 := time.Now()
38
+ var arr []C.VALUE
39
+
40
+ for rows.Next() {
41
+ if i%5000 == 0 {
42
+ if LOG_LEVEL > 0 {
43
+ fmt.Println("scanning row: ", i)
44
+ }
45
+ }
46
+ x := res.ScanNextRow(false)
47
+ objects[x] = true
48
+ gopointer.Save(x)
49
+ if LOG_LEVEL > 1 {
50
+ // This is VERY noisy
51
+ fmt.Printf("alloced %v\n", &x)
52
+ }
53
+ arr = append(arr, x)
54
+ i = i + 1
55
+ }
56
+ if LOG_LEVEL > 0 {
57
+ fmt.Printf("done with rows.next: %s\n", time.Now().Sub(t1))
58
+ }
59
+
60
+ rbArr := C.rb_ary_new2(C.long(len(arr)))
61
+ for idx, elem := range arr {
62
+ C.rb_ary_store(rbArr, C.long(idx), elem)
33
63
  }
34
64
 
35
- return sr
65
+ return rbArr
36
66
  }
37
67
 
38
68
  //export GetRows
@@ -62,11 +92,6 @@ func GetRows(self C.VALUE) C.VALUE {
62
92
  fmt.Printf("done with rows.next: %s\n", time.Now().Sub(t1))
63
93
  }
64
94
 
65
- //empty for GC
66
- res.rows = nil
67
- res.keptHash = C.Qnil
68
- res.cols = []C.VALUE{}
69
-
70
95
  return self
71
96
  }
72
97
 
@@ -82,10 +107,6 @@ func ObjNextRow(self C.VALUE) C.VALUE {
82
107
  if rows.Next() {
83
108
  r := res.ScanNextRow(false)
84
109
  return r
85
- } else if rows.Err() == io.EOF {
86
- res.rows = nil // free up for gc
87
- res.keptHash = C.Qnil // free up for gc
88
- res.cols = []C.VALUE{}
89
110
  }
90
111
  return C.Qnil
91
112
  }
@@ -97,22 +118,28 @@ func (res SnowflakeResult) ScanNextRow(debug bool) C.VALUE {
97
118
  fmt.Printf("column types: %+v; %+v\n", cts[0], cts[0].ScanType())
98
119
  }
99
120
 
100
- rawResult := make([]any, len(res.cols))
101
- rawData := make([]any, len(res.cols))
121
+ rawResult := make([]any, len(res.columns))
122
+ rawData := make([]any, len(res.columns))
102
123
  for i := range rawResult {
103
124
  rawData[i] = &rawResult[i]
104
125
  }
105
126
 
106
127
  err := rows.Scan(rawData...)
107
128
  if err != nil {
108
- rb_raise(C.rb_eArgError, "Cannot scan row: '%s'", err)
129
+ err = fmt.Errorf("Cannot scan row: '%s'", err)
130
+ wrapRbRaise(err)
109
131
  }
110
132
 
111
133
  // trick from postgres; keep hash: pg_result.c:1088
112
- hash := C.rb_hash_dup(res.keptHash)
134
+ //hash := C.rb_hash_dup(res.keptHash)
135
+ hash := C.rb_hash_new()
136
+ if LOG_LEVEL > 1 {
137
+ // This is very noisy
138
+ fmt.Println("alloc'ed new hash", &hash)
139
+ }
140
+
113
141
  for idx, raw := range rawResult {
114
142
  raw := raw
115
- col_name := res.cols[idx]
116
143
 
117
144
  var rbVal C.VALUE
118
145
 
@@ -139,43 +166,16 @@ func (res SnowflakeResult) ScanNextRow(debug bool) C.VALUE {
139
166
  str := v
140
167
  rbVal = RbString(str)
141
168
  default:
142
- rb_raise(C.rb_eArgError, "Cannot parse type '%s'", fmt.Errorf("%T", v))
169
+ err := fmt.Errorf("Cannot parse type : '%T'", v)
170
+ wrapRbRaise(err)
143
171
  }
144
172
  }
145
- C.rb_hash_aset(hash, col_name, rbVal)
146
- }
147
- return hash
148
- }
149
-
150
- func SafeMakeHash(lenght int, cols []C.VALUE) C.VALUE {
151
- var hash C.VALUE
152
- hash = C.rb_hash_new()
153
-
154
- if LOG_LEVEL > 0 {
155
- fmt.Println("starting make hash")
156
- }
157
- for _, col := range cols {
158
- C.rb_hash_aset(hash, col, C.Qnil)
159
- }
160
- if LOG_LEVEL > 0 {
161
- fmt.Println("end make hash", hash)
173
+ colstr := C.rb_str_new2(C.CString(res.columns[idx]))
174
+ if LOG_LEVEL > 1 {
175
+ // This is very noisy
176
+ fmt.Printf("alloc string: %+v; rubyVal: %+v\n", &colstr, &rbVal)
177
+ }
178
+ C.rb_hash_aset(hash, colstr, rbVal)
162
179
  }
163
180
  return hash
164
181
  }
165
-
166
- func (res *SnowflakeResult) Initialize() {
167
- columns, _ := res.rows.Columns()
168
- rbArr := C.rb_ary_new2(C.long(len(columns)))
169
-
170
- cols := make([]C.VALUE, len(columns))
171
- for idx, colName := range columns {
172
- str := strings.ToLower(colName)
173
- sym := C.rb_str_new2(C.CString(str))
174
- sym = C.rb_str_freeze(sym)
175
- cols[idx] = sym
176
- C.rb_ary_store(rbArr, C.long(idx), sym)
177
- }
178
-
179
- res.cols = cols
180
- res.keptHash = SafeMakeHash(len(columns), cols)
181
- }
@@ -8,8 +8,9 @@ VALUE ObjFetch(VALUE,VALUE);
8
8
  VALUE ObjNextRow(VALUE);
9
9
  VALUE Inspect(VALUE);
10
10
  VALUE GetRows(VALUE);
11
+ VALUE GetRowsNoEnum(VALUE);
11
12
 
12
- VALUE NewGoStruct(VALUE klass, void *p);
13
+ VALUE NewGoStruct(VALUE klass, char* reason, void *p);
13
14
  VALUE GoRetEnum(VALUE,int,VALUE);
14
15
  void* GetGoStruct(VALUE obj);
15
16
  void RbGcGuard(VALUE ptr);
@@ -21,18 +22,18 @@ import "C"
21
22
  import (
22
23
  "context"
23
24
  "database/sql"
24
- "errors"
25
25
  "fmt"
26
+ "strings"
26
27
  "time"
27
28
 
28
- gopointer "github.com/mattn/go-pointer"
29
29
  sf "github.com/snowflakedb/gosnowflake"
30
30
  )
31
31
 
32
32
  type SnowflakeResult struct {
33
- rows *sql.Rows
34
- keptHash C.VALUE
35
- cols []C.VALUE
33
+ rows *sql.Rows
34
+ //keptHash C.VALUE
35
+ columns []string
36
+ //cols []C.VALUE
36
37
  }
37
38
  type SnowflakeClient struct {
38
39
  db *sql.DB
@@ -42,12 +43,13 @@ var rbSnowflakeClientClass C.VALUE
42
43
  var rbSnowflakeResultClass C.VALUE
43
44
  var rbSnowflakeModule C.VALUE
44
45
 
45
- var DB_IDENTIFIER = C.rb_intern(C.CString("db"))
46
46
  var RESULT_IDENTIFIER = C.rb_intern(C.CString("rows"))
47
47
  var RESULT_DURATION = C.rb_intern(C.CString("@query_duration"))
48
48
  var ERROR_IDENT = C.rb_intern(C.CString("@error"))
49
49
 
50
50
  var objects = make(map[interface{}]bool)
51
+ var resultMap = make(map[C.VALUE]*SnowflakeResult)
52
+ var clientRef = make(map[C.VALUE]*SnowflakeClient)
51
53
 
52
54
  var LOG_LEVEL = 0
53
55
  var empty C.VALUE = C.Qnil
@@ -78,13 +80,7 @@ func Connect(self C.VALUE, account C.VALUE, warehouse C.VALUE, database C.VALUE,
78
80
  C.rb_ivar_set(self, ERROR_IDENT, RbString(errStr))
79
81
  }
80
82
  rs := SnowflakeClient{db}
81
- ptr := gopointer.Save(&rs)
82
- rbStruct := C.NewGoStruct(
83
- rbSnowflakeClientClass,
84
- ptr,
85
- )
86
-
87
- C.rb_ivar_set(self, DB_IDENTIFIER, rbStruct)
83
+ clientRef[self] = &rs
88
84
  }
89
85
 
90
86
  func (x SnowflakeClient) Fetch(statement C.VALUE) C.VALUE {
@@ -113,46 +109,28 @@ func (x SnowflakeClient) Fetch(statement C.VALUE) C.VALUE {
113
109
  }
114
110
 
115
111
  result := C.rb_class_new_instance(0, &empty, rbSnowflakeResultClass)
116
- rs := SnowflakeResult{rows, C.Qnil, []C.VALUE{}}
117
- rs.Initialize()
118
- ptr := gopointer.Save(&rs)
119
- rbStruct := C.NewGoStruct(
120
- rbSnowflakeClientClass,
121
- ptr,
122
- )
123
- C.RbGcGuard(rbStruct)
124
- C.RbGcGuard(rbSnowflakeResultClass)
125
- C.rb_ivar_set(result, RESULT_IDENTIFIER, rbStruct)
112
+ cols, _ := rows.Columns()
113
+ for idx, col := range cols {
114
+ col := col
115
+ cols[idx] = strings.ToLower(col)
116
+ }
117
+ rs := SnowflakeResult{rows, cols}
118
+ resultMap[result] = &rs
126
119
  C.rb_ivar_set(result, RESULT_DURATION, RbNumFromDouble(C.double(duration)))
127
120
  return result
128
121
  }
129
122
 
130
123
  //export ObjFetch
131
124
  func ObjFetch(self C.VALUE, statement C.VALUE) C.VALUE {
132
- var q C.VALUE
133
- q = C.rb_ivar_get(self, DB_IDENTIFIER)
134
-
135
- req := C.GetGoStruct(q)
136
- f := gopointer.Restore(req)
137
- x, ok := f.(*SnowflakeClient)
138
- if !ok {
139
- rb_raise(C.rb_eArgError, "%s", errors.New("cannot convert x to pointer"))
140
- }
125
+ x, _ := clientRef[self]
141
126
 
142
127
  return x.Fetch(statement)
143
128
  }
144
129
 
145
130
  //export Inspect
146
131
  func Inspect(self C.VALUE) C.VALUE {
147
- q := C.rb_ivar_get(self, DB_IDENTIFIER)
148
- if q == C.Qnil {
149
- return RbString("Object is not instantiated")
150
- }
151
-
152
- req := C.GetGoStruct(q)
153
- f := gopointer.Restore(req)
154
- x := f.(*SnowflakeClient)
155
- return RbString(fmt.Sprintf("%+v", x))
132
+ x := clientRef[self]
133
+ return RbString(fmt.Sprintf("Snowflake::Client <%+v>", x))
156
134
  }
157
135
 
158
136
  //export Init_ruby_snowflake_client_ext
@@ -161,10 +139,20 @@ func Init_ruby_snowflake_client_ext() {
161
139
  rbSnowflakeClientClass = C.rb_define_class_under(rbSnowflakeModule, C.CString("Client"), C.rb_cObject)
162
140
  rbSnowflakeResultClass = C.rb_define_class_under(rbSnowflakeModule, C.CString("Result"), C.rb_cObject)
163
141
 
142
+ objects[rbSnowflakeResultClass] = true
143
+ objects[rbSnowflakeClientClass] = true
144
+ objects[rbSnowflakeModule] = true
145
+ objects[RESULT_DURATION] = true
146
+ objects[ERROR_IDENT] = true
147
+ C.RbGcGuard(RESULT_DURATION)
148
+ //C.RbGcGuard(RESULT_IDENTIFIER)
149
+ C.RbGcGuard(ERROR_IDENT)
150
+
164
151
  C.rb_define_method(rbSnowflakeResultClass, C.CString("next_row"), (*[0]byte)(C.ObjNextRow), 0)
165
152
  // `get_rows` is private as this can lead to SEGFAULT errors if not invoked
166
153
  // with GC.disable due to undetermined issues caused by the Ruby GC.
167
154
  C.rb_define_private_method(rbSnowflakeResultClass, C.CString("_get_rows"), (*[0]byte)(C.GetRows), 0)
155
+ C.rb_define_method(rbSnowflakeResultClass, C.CString("get_rows_no_enum"), (*[0]byte)(C.GetRowsNoEnum), 0)
168
156
 
169
157
  C.rb_define_private_method(rbSnowflakeClientClass, C.CString("_connect"), (*[0]byte)(C.Connect), 7)
170
158
  C.rb_define_method(rbSnowflakeClientClass, C.CString("inspect"), (*[0]byte)(C.Inspect), 0)
data/ext/wrapper.go CHANGED
@@ -24,7 +24,7 @@ VALUE RbNumFromLong(long v) {
24
24
  return LONG2NUM(v);
25
25
  }
26
26
 
27
- void goobj_retain(void *);
27
+ void goobj_retain(void *, char*);
28
28
  void goobj_free(void *);
29
29
  void goobj_log(void *);
30
30
  void goobj_mark(void *);
@@ -42,9 +42,9 @@ static const rb_data_type_t go_type = {
42
42
  };
43
43
 
44
44
  VALUE
45
- NewGoStruct(VALUE klass, void *p)
45
+ NewGoStruct(VALUE klass, char* reason, void *p)
46
46
  {
47
- goobj_retain(p);
47
+ goobj_retain(p, reason);
48
48
  return TypedData_Wrap_Struct(klass, &go_type, p);
49
49
  }
50
50
 
@@ -125,7 +125,8 @@ func RbString(str string) C.VALUE {
125
125
  if len(str) == 0 {
126
126
  return C.rb_utf8_str_new(nil, C.long(0))
127
127
  }
128
- cstr := (*C.char)(unsafe.Pointer(&(*(*[]byte)(unsafe.Pointer(&str)))[0]))
128
+ //cstr := (*C.char)(unsafe.Pointer(&(*(*[]byte)(unsafe.Pointer(&str)))[0]))
129
+ cstr := C.CString(str)
129
130
  return C.rb_utf8_str_new(cstr, C.long(len(str)))
130
131
  }
131
132
 
@@ -1,3 +1,3 @@
1
1
  module RubySnowflakeClient
2
- VERSION = '1.0.1'
2
+ VERSION = '1.1.0'
3
3
  end
@@ -2,6 +2,15 @@
2
2
 
3
3
  module Snowflake
4
4
  require "ruby_snowflake_client_ext" # build bundle of the go files
5
+ LOG_LEVEL = 0
6
+
7
+ class Error < StandardError
8
+ attr_reader :details
9
+
10
+ def initialize(details)
11
+ @details = details
12
+ end
13
+ end
5
14
 
6
15
  class Client
7
16
  attr_reader :error
@@ -9,9 +18,18 @@ module Snowflake
9
18
  # wrapping the function in C as the current CGO has a limitation on
10
19
  # accepting variadic arguments for functions.
11
20
  def connect(account:"", warehouse:"", database:"", schema: "", user: "", password: "", role: "")
12
- _connect(account, warehouse, database, schema, user, password, role)
21
+ @connection_details = {
22
+ account: account,
23
+ warehouse: warehouse,
24
+ database: database,
25
+ schema: schema,
26
+ user: user,
27
+ role: role
28
+ }
29
+
30
+ _connect(account.dup, warehouse.dup, database.dup, schema.dup, user.dup, password.dup, role.dup)
13
31
  if error != nil
14
- raise(error)
32
+ raise Error.new(@connection_details), error
15
33
  end
16
34
  true
17
35
  end
@@ -19,7 +37,7 @@ module Snowflake
19
37
  def fetch(sql)
20
38
  result = _fetch(sql)
21
39
  return result if result.valid?
22
- raise(result.error)
40
+ raise Error.new(@connection_details.merge(sql: sql)), result.error
23
41
  end
24
42
  end
25
43
 
@@ -34,13 +52,24 @@ module Snowflake
34
52
  def get_all_rows(&blk)
35
53
  GC.disable
36
54
  if blk
37
- _get_rows(&blk)
55
+ while r = next_row do
56
+ yield r
57
+ end
38
58
  else
39
- _get_rows.to_a
59
+ get_rows_array
40
60
  end
41
61
  ensure
42
62
  GC.enable
43
- GC.start
44
63
  end
64
+
65
+ private
66
+ def get_rows_array
67
+ arr = []
68
+ while r = next_row do
69
+ puts "at #{arr.length}" if arr.length % 15000 == 0 && LOG_LEVEL > 0
70
+ arr << r
71
+ end
72
+ arr
73
+ end
45
74
  end
46
75
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_snowflake_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rinsed
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-08 00:00:00.000000000 Z
11
+ date: 2023-06-09 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  Using the `Go` library for Snowflake to query and creating native Ruby objects,