ruby_snowflake_client 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,