ffi-sybase 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in ffi-sybase.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Jari Bakken
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,25 @@
1
+ = ffi-sybase
2
+
3
+ Ruby/FFI bindings for Sybase's Open Client library.
4
+
5
+ = See also
6
+
7
+ * http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc35570.1550/html/clcprgde/title.htm
8
+
9
+ = Dependencies
10
+
11
+ * ffi
12
+
13
+ == Note on Patches/Pull Requests
14
+
15
+ * Fork the project.
16
+ * Make your feature addition or bug fix.
17
+ * Add tests for it. This is important so I don't break it in a
18
+ future version unintentionally.
19
+ * Commit, do not mess with rakefile, version, or history.
20
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
21
+ * Send me a pull request. Bonus points for topic branches.
22
+
23
+ == Copyright
24
+
25
+ Copyright (c) 2011 Jari Bakken. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
data/TODO ADDED
@@ -0,0 +1,10 @@
1
+ soon:
2
+
3
+ - Sybase::Client wraps Context/Connection
4
+ - simple specs
5
+
6
+ maybe:
7
+
8
+ - Async / EM
9
+
10
+
@@ -0,0 +1,20 @@
1
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
2
+ require 'pp'
3
+ require 'ffi-sybase'
4
+
5
+ unless ARGV.size == 3
6
+ abort("USAGE: #{$PROGRAM_NAME} <db> <user> <pass>")
7
+ end
8
+
9
+ db, user, pass = *ARGV
10
+
11
+ Sybase::Context.new do |ctx|
12
+ ctx.callbacks.library { |message| puts "library : #{message}" }
13
+ ctx.callbacks.client { |message| puts "client : #{message}" }
14
+ ctx.callbacks.server { |message| puts "server : #{message}" }
15
+
16
+ Sybase::Connection.new(ctx, :username => user, :password => pass) do |conn|
17
+ conn.connect db
18
+ pp Sybase::Command.new(conn, "sp_who").execute
19
+ end
20
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "sybase/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "ffi-sybase"
7
+ s.version = Sybase::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Jari Bakken"]
10
+ s.email = ["jari.bakken@gmail.com"]
11
+ s.homepage = "http://github.com/jarib/ffi-sybase"
12
+ s.summary = %q{Ruby/FFI bindings for Sybase OCS}
13
+ s.description = %q{Ruby/FFI bindings for Sybase's Open Client library.}
14
+
15
+ s.rubyforge_project = "ffi-sybase"
16
+
17
+ s.add_dependency "ffi", ">= 0.6.3"
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.require_paths = ["lib"]
23
+ end
data/lib/ffi-sybase.rb ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'socket'
4
+ require 'ffi'
5
+
6
+ module Sybase
7
+ class Error < StandardError; end
8
+ end
9
+
10
+ require "sybase/version"
11
+ require "sybase/constants"
12
+ require "sybase/structs/message"
13
+ require "sybase/structs/client_message"
14
+ require "sybase/structs/server_message"
15
+ require "sybase/structs/column_data"
16
+ require "sybase/structs/data_format"
17
+ require "sybase/lib"
18
+ require "sybase/context"
19
+ require "sybase/connection"
20
+ require "sybase/command"
@@ -0,0 +1,209 @@
1
+ module Sybase
2
+ class Command
3
+
4
+ def initialize(connection, str)
5
+ FFI::MemoryPointer.new(:pointer) do |ptr|
6
+ Lib.check Lib.ct_cmd_alloc(connection, ptr), "ct_cmd_alloc"
7
+ @ptr = FFI::AutoPointer.new(ptr.read_pointer, Lib.method(:ct_cmd_drop))
8
+ end
9
+
10
+ @str = str.to_s
11
+ end
12
+
13
+ def execute
14
+ set_command
15
+ send
16
+ results
17
+ ensure
18
+ finish
19
+ end
20
+
21
+ def finish
22
+ to_ptr.free
23
+ @ptr = nil
24
+ end
25
+
26
+ def to_ptr
27
+ @ptr or raise "command #{self} already ran or was not initialized"
28
+ end
29
+
30
+ private
31
+
32
+ def set_command
33
+ Lib.check Lib.ct_command(to_ptr, CS_LANG_CMD, @str, @str.bytesize, CS_UNUSED)
34
+ end
35
+
36
+ def send
37
+ Lib.check Lib.ct_send(to_ptr), "ct_send failed"
38
+ end
39
+
40
+ def cancel
41
+ Lib.ct_cancel(nil, to_ptr, CS_CANCEL_CURRENT)
42
+ end
43
+
44
+ COMMAND_RESULTS = {
45
+ CS_CMD_SUCCEED => :succeed,
46
+ CS_CMD_DONE => :done,
47
+ CS_CMD_FAIL => :fail,
48
+ CS_ROW_RESULT => :row,
49
+ CS_CURSOR_RESULT => :cursor,
50
+ CS_PARAM_RESULT => :param,
51
+ CS_COMPUTE_RESULT => :compute,
52
+ CS_STATUS_RESULT => :status
53
+ }
54
+
55
+ def results
56
+ intptr = FFI::MemoryPointer.new(:int)
57
+
58
+ state = :initial
59
+
60
+ returned = []
61
+
62
+ while successful? intptr
63
+ restype = intptr.read_int
64
+ restype = COMMAND_RESULTS[restype] || restype
65
+
66
+ case restype
67
+ when :succeed, # no row - e.g. insert/update
68
+ :done # results completely processed
69
+ returned << Result.new(restype, nil, result_info(CS_ROW_COUNT), result_info(CS_TRANS_STATE))
70
+ when :fail
71
+ returned << Result.new(restype, nil, result_info(CS_ROW_COUNT), result_info(CS_TRANS_STATE))
72
+ when :row,
73
+ :cursor,
74
+ :param,
75
+ :compute,
76
+ :status
77
+
78
+ columns, rows = fetch_data
79
+ returned << Result.new(restype, :columns => columns, :rows => rows)
80
+ else
81
+ returned << Result.new(restype, nil, result_info(CS_ROW_COUNT), result_info(CS_TRANS_STATE))
82
+ end
83
+
84
+ # check context timeout?
85
+ end
86
+
87
+ returned
88
+ end
89
+
90
+ class Result
91
+ def initialize(type, data, row_count = nil, transaction_state = nil)
92
+ @type = type
93
+ @data = data
94
+ @row_count = row_count
95
+ @transaction_state = transaction_state
96
+ end
97
+ end # Result
98
+
99
+ def successful?(intptr)
100
+ @return_code = Lib.ct_results(to_ptr, intptr)
101
+ @return_code == CS_SUCCEED
102
+ end
103
+
104
+ def fetch_data
105
+ num_cols = fetch_column_count
106
+
107
+ column_datas = Array.new(num_cols) { ColumnData.new }
108
+
109
+ num_cols.times do |i|
110
+ cd = column_datas[i]
111
+ df = cd.format
112
+
113
+ Lib.check Lib.ct_describe(to_ptr, i + 1, df)
114
+ type = df[:datatype]
115
+
116
+ case type
117
+ when CS_TINYINT_TYPE,
118
+ CS_SMALLINT_TYPE,
119
+ CS_INT_TYPE,
120
+ CS_BIT_TYPE,
121
+ CS_DECIMAL_TYPE,
122
+ CS_NUMERIC_TYPE
123
+ df[:maxlength] = FFI.type_size(:int)
124
+ df[:datatype] = CS_INT_TYPE
125
+ df[:format] = CS_FMT_UNUSED
126
+
127
+ cd.int_pointer!
128
+ when CS_REAL_TYPE, CS_FLOAT_TYPE
129
+ # not sure about this
130
+ df[:maxlength] = FFI.type_size(:double)
131
+ df[:datatype] = CS_FLOAT_TYPE
132
+ df[:format] = CS_FMT_UNUSED
133
+
134
+ cd.double_pointer!
135
+ else # treat as String
136
+ df[:maxlength] = Lib.display_length(df) + 1
137
+
138
+ if type == CS_IMAGE_TYPE
139
+ df[:format] = CS_FMT_UNUSED
140
+ else
141
+ df[:format] = CS_FMT_NULLTERM
142
+ df[:datatype] = CS_CHAR_TYPE
143
+ end
144
+
145
+ cd.char_pointer!(df[:maxlength])
146
+ end
147
+
148
+ bind i, df, cd
149
+ end
150
+
151
+ rows_read_ptr = FFI::MemoryPointer.new(:int)
152
+ row_count = 0
153
+
154
+ columns = column_datas.map { |cd| cd.format.name }
155
+ values = []
156
+
157
+ while (code = fetch_row(rows_read_ptr)) == CS_SUCCEED || code == CS_ROW_FAIL
158
+ # increment row count
159
+ row_count += rows_read_ptr.read_int
160
+
161
+ if code == CS_ROW_FAIL
162
+ raise Error, "error on row #{row_count}"
163
+ end
164
+
165
+ values << column_datas.map { |e| e.value }
166
+ end
167
+
168
+ # done processing rows, check final return code
169
+ case code
170
+ when CS_END_DATA
171
+ # all good
172
+ when CS_FAIL
173
+ raise Error, "ct_fetch() failed"
174
+ else
175
+ raise Error, "unexpected return code: #{code}"
176
+ end
177
+
178
+ [columns, values]
179
+ ensure
180
+ # ?
181
+ end
182
+
183
+ def fetch_row(rows_read_ptr)
184
+ Lib.ct_fetch(to_ptr, CS_UNUSED, CS_UNUSED, CS_UNUSED, rows_read_ptr)
185
+ end
186
+
187
+ def bind(index, data_format, column_data)
188
+ Lib.check Lib.ct_bind(to_ptr, index + 1, data_format, column_data.pointer, column_data.valuelen_pointer, column_data.indicator_pointer)
189
+ end
190
+
191
+ def fetch_column_count
192
+ num_cols = result_info(CS_NUMDATA)
193
+
194
+ if num_cols <= 0
195
+ cancel
196
+ raise Error, "bad column count (#{num_cols})"
197
+ end
198
+
199
+ num_cols
200
+ end
201
+
202
+ def result_info(operation)
203
+ int_ptr = FFI::MemoryPointer.new(:int)
204
+ Lib.check Lib.ct_res_info(to_ptr, operation, int_ptr, CS_UNUSED, nil), "ct_res_info failed"
205
+ num_cols = int_ptr.read_int
206
+ end
207
+
208
+ end # Command
209
+ end # Sybase
@@ -0,0 +1,132 @@
1
+ module Sybase
2
+ class Connection
3
+ PROPERTIES = {
4
+ :username => [ CS_USERNAME, :string ],
5
+ :password => [ CS_PASSWORD, :string ],
6
+ :appname => [ CS_APPNAME, :string ],
7
+ :tds_version => [ CS_TDS_VERSION, :int ],
8
+ :hostname => [ CS_HOSTNAME, :string ]
9
+ }
10
+
11
+
12
+ def initialize(context, opts ={})
13
+ @context = context
14
+
15
+ FFI::MemoryPointer.new(:pointer) { |ptr|
16
+ Lib.check Lib.ct_con_alloc(context, ptr), "ct_con_alloc"
17
+ @ptr = FFI::AutoPointer.new(ptr.read_pointer, Lib.method(:ct_con_drop))
18
+ }
19
+
20
+ opts.each do |key, value|
21
+ self[key] = value
22
+ end
23
+
24
+ unless opts.has_key?(:hostname)
25
+ self[:hostname] = Socket.gethostname
26
+ end
27
+
28
+ if block_given?
29
+ begin
30
+ yield self
31
+ ensure
32
+ close
33
+ end
34
+ end
35
+ end
36
+
37
+ def debug!
38
+ Lib.check Lib.ct_debug(@context, to_ptr, CS_SET_FLAG, CS_DBG_ALL, nil, CS_UNUSED)
39
+ end
40
+
41
+ def close
42
+ Lib.check Lib.ct_close(@ptr, CS_UNUSED), "ct_close"
43
+ end
44
+
45
+ def [](key)
46
+ property, type = property_type_for(key)
47
+ case type
48
+ when :string
49
+ get_string_property property
50
+ when :int
51
+ get_int_property property
52
+ end
53
+ end
54
+
55
+ def []=(key, value)
56
+ property, type = property_type_for(key)
57
+
58
+ case type
59
+ when :string
60
+ set_string_property property, value
61
+ when :int
62
+ set_int_property property, value
63
+ else
64
+ raise Error, "invalid type: #{type.inspect}"
65
+ end
66
+ end
67
+
68
+ def property_type_for(key)
69
+ PROPERTIES.fetch(key) { |key|
70
+ raise ArgumentError, "invalid option: #{key.inspect}, expected one of #{PROPERTIES.keys.inspect}"
71
+ }
72
+ end
73
+
74
+ def username=(user)
75
+ set_string CS_USERNAME, user
76
+ end
77
+
78
+ def username
79
+ get_string CS_USERNAME
80
+ end
81
+
82
+ def password=(password)
83
+ set_string CS_PASSWORD, password
84
+ end
85
+
86
+ def password
87
+ get_string CS_PASSWORD
88
+ end
89
+
90
+ def appname=(name)
91
+ set_string CS_APPNAME, name
92
+ end
93
+
94
+ def appname
95
+ get_string CS_APPNAME, name
96
+ end
97
+
98
+ def connect(server)
99
+ server = server.to_s
100
+ Lib.check Lib.ct_connect(@ptr, server, server.bytesize), "connect(#{server.inspect}) failed"
101
+ end
102
+
103
+ def to_ptr
104
+ @ptr
105
+ end
106
+
107
+ private
108
+
109
+ def set_string_property(property, string)
110
+ Lib.check Lib.ct_con_props(@ptr, CS_SET, property, string.to_s, CS_NULLTERM, nil), "ct_con_prop(#{property} => #{string.inspect}) failed"
111
+ end
112
+
113
+ def get_string_property(property)
114
+ FFI::MemoryPointer.new(:string) { |ptr| get_property(property, ptr) }.read_string
115
+ end
116
+
117
+ def get_int_property(property)
118
+ FFI::MemoryPointer.new(:int) { |ptr| get_property(property, ptr) }.read_int
119
+ end
120
+
121
+ def set_int_property(property, int)
122
+ ptr = FFI::MemoryPointer.new(:int)
123
+ ptr.write_int(int)
124
+
125
+ Lib.check Lib.ct_con_props(@ptr, CS_SET, property, ptr, CS_UNUSED, nil), "ct_con_prop(#{property} => #{int.inspect}) failed"
126
+ end
127
+
128
+ def get_property(property, ptr)
129
+ Lib.check Lib.ct_con_props(@ptr, CS_GET, property, ptr, CS_UNUSED, nil), "ct_con_prop(CS_GET, #{property}) failed"
130
+ end
131
+ end # Connection
132
+ end # Sybase
@@ -0,0 +1,165 @@
1
+ module Sybase
2
+ DEFAULT_CTLIB_VERSION = 15001
3
+
4
+ MAX_CHAR_BUF = 1024
5
+
6
+ CS_CONV_ERR = -100
7
+ CS_EXTERNAL_ERR = -200
8
+ CS_INTERNAL_ERR = -300
9
+
10
+ CS_TDS_40 = 7360
11
+ CS_TDS_42 = 7361
12
+ CS_TDS_46 = 7362
13
+ CS_TDS_495 = 7363
14
+ CS_TDS_50 = 7364
15
+
16
+ CS_SET_FLAG = 1700
17
+ CS_CLEAR_FLAG = 1701
18
+ CS_DBG_ALL = 1
19
+ CS_DBG_ASYNC = 2
20
+ CS_DBG_ERROR = 4
21
+
22
+ CS_CLIENTMSG_CB = 3
23
+ CS_GET = 33
24
+ CS_MAX_CHAR = 256
25
+ CS_MAX_NAME = 255
26
+ CS_MAX_MSG = 1024
27
+ CS_MESSAGE_CB = 9119
28
+ CS_NULLTERM = -9
29
+ CS_SERVERMSG_CB = 2
30
+ CS_SET = 34
31
+ CS_SQLSTATE_SIZE = 8
32
+ CS_SUCCEED = 1
33
+ CS_FAIL = 0
34
+ CS_UNUSED = -99999
35
+ CS_CANCEL_CURRENT = 6000
36
+ CS_FMT_UNUSED = 0
37
+ CS_FMT_NULLTERM = 1
38
+
39
+ CS_SYNC_IO = 8111
40
+ CS_ASYNC_IO = 8112
41
+ CS_DEFER_IO = 8113
42
+
43
+ # server options
44
+ CS_OPT_CHARSET = 5010
45
+ CS_OPT_PARSEONLY = 5018
46
+
47
+ # ct_command types
48
+ CS_LANG_CMD = 148
49
+ CS_RPC_CMD = 149
50
+ CS_MSG_CMD = 150
51
+ CS_SEND_DATA_CMD = 152
52
+
53
+ # connection properties
54
+ CS_USERNAME = 9100
55
+ CS_PASSWORD = 9101
56
+ CS_APPNAME = 9102
57
+ CS_HOSTNAME = 9103
58
+ CS_LOGIN_STATUS = 9104
59
+ CS_TDS_VERSION = 9105
60
+ CS_CHARSETCNV = 9106
61
+ CS_PACKETSIZE = 9107
62
+ CS_USERDATA = 9108
63
+ CS_COMMBLOCK = 9109
64
+ CS_NETIO = 9110
65
+ CS_NOINTERRUPT = 9111
66
+ CS_TEXTLIMIT = 9112
67
+ CS_HIDDEN_KEYS = 9113
68
+ CS_VERSION = 9114
69
+ CS_IFILE = 9115
70
+ CS_LOGIN_TIMEOUT = 9116
71
+ CS_TIMEOUT = 9117
72
+ CS_MAX_CONNECT = 9118
73
+ CS_EXPOSE_FMTS = 9120
74
+ CS_EXTRA_INF = 9121
75
+ CS_TRANSACTION_NAME = 9122
76
+ CS_ANSI_BINDS = 9123
77
+ CS_BULK_LOGIN = 9124
78
+ CS_LOC_PROP = 9125
79
+ CS_CUR_STATUS = 9126
80
+ CS_CUR_ID = 9127
81
+ CS_CUR_NAME = 9128
82
+ CS_CUR_ROWCOUNT = 9129
83
+ CS_PARENT_HANDLE = 9130
84
+ CS_EED_CMD = 9131
85
+ CS_DIAG_TIMEOUT = 9132
86
+ CS_DISABLE_POLL = 9133
87
+ CS_NOTIF_CMD = 9134
88
+ CS_SEC_ENCRYPTION = 9135
89
+ CS_SEC_CHALLENGE = 9136
90
+ CS_SEC_NEGOTIATE = 9137
91
+ CS_MEM_POOL = 9138
92
+ CS_USER_ALLOC = 9139
93
+ CS_USER_FREE = 9140
94
+ CS_ENDPOINT = 9141
95
+ CS_NO_TRUNCATE = 9142
96
+ CS_CON_STATUS = 9143
97
+ CS_VER_STRING = 9144
98
+ CS_ASYNC_NOTIFS = 9145
99
+ CS_SERVERNAME = 9146
100
+
101
+ CS_SEND_BULK_CMD = 153
102
+
103
+ # ct_results
104
+ CS_ROW_RESULT = 4040
105
+ CS_CURSOR_RESULT = 4041
106
+ CS_PARAM_RESULT = 4042
107
+ CS_STATUS_RESULT = 4043
108
+ CS_MSG_RESULT = 4044
109
+ CS_COMPUTE_RESULT = 4045
110
+ CS_CMD_DONE = 4046
111
+ CS_CMD_SUCCEED = 4047
112
+ CS_CMD_FAIL = 4048
113
+ CS_ROWFMT_RESULT = 4049
114
+ CS_COMPUTEFMT_RESULT = 4050
115
+ CS_DESCRIBE_RESULT = 4051
116
+ CS_END_RESULTS = CS_EXTERNAL_ERR - 5
117
+
118
+ # ct_res_info
119
+ CS_ROW_COUNT = 800
120
+ CS_NUMDATA = 803
121
+ CS_MSGTYPE = 806
122
+ CS_TRANS_STATE = 808
123
+
124
+ # ct_fetch
125
+ CS_ROW_FAIL = CS_EXTERNAL_ERR - 3
126
+ CS_END_DATA = CS_EXTERNAL_ERR - 4
127
+
128
+ # data types
129
+ CS_ILLEGAL_TYPE = -1
130
+ CS_CHAR_TYPE = 0
131
+ CS_BINARY_TYPE = 1
132
+ CS_LONGCHAR_TYPE = 2
133
+ CS_LONGBINARY_TYPE = 3
134
+ CS_TEXT_TYPE = 4
135
+ CS_IMAGE_TYPE = 5
136
+ CS_TINYINT_TYPE = 6
137
+ CS_SMALLINT_TYPE = 7
138
+ CS_INT_TYPE = 8
139
+ CS_REAL_TYPE = 9
140
+ CS_FLOAT_TYPE = 10
141
+ CS_BIT_TYPE = 11
142
+ CS_DATETIME_TYPE = 12
143
+ CS_DATETIME4_TYPE = 13
144
+ CS_MONEY_TYPE = 14
145
+ CS_MONEY4_TYPE = 15
146
+ CS_NUMERIC_TYPE = 16
147
+ CS_DECIMAL_TYPE = 17
148
+ CS_VARCHAR_TYPE = 18
149
+ CS_VARBINARY_TYPE = 19
150
+ CS_LONG_TYPE = 20
151
+ CS_SENSITIVITY_TYPE = 21
152
+ CS_BOUNDARY_TYPE = 22
153
+ CS_VOID_TYPE = 23
154
+ CS_USHORT_TYPE = 24
155
+ CS_UNICHAR_TYPE = 25
156
+ CS_BLOB_TYPE = 26
157
+ CS_DATE_TYPE = 27
158
+ CS_TIME_TYPE = 28
159
+ CS_UNITEXT_TYPE = 29
160
+ CS_BIGINT_TYPE = 30
161
+ CS_USMALLINT_TYPE = 31
162
+ CS_UINT_TYPE = 32
163
+ CS_UBIGINT_TYPE = 33
164
+ CS_XML_TYPE = 34
165
+ end
@@ -0,0 +1,71 @@
1
+ module Sybase
2
+ class Context
3
+ def initialize(version = DEFAULT_CTLIB_VERSION)
4
+ @version = Integer(version)
5
+
6
+ FFI::MemoryPointer.new(:pointer) do |ptr|
7
+ Lib.check Lib.cs_ctx_alloc(@version, ptr), "cs_ctx_alloc failed"
8
+ @ptr = FFI::AutoPointer.new(ptr.read_pointer, Lib.method(:cs_ctx_drop))
9
+ end
10
+
11
+ Lib.check Lib.ct_init(@ptr, @version), "ct_init failed"
12
+
13
+ if block_given?
14
+ begin
15
+ yield self
16
+ ensure
17
+ exit
18
+ end
19
+ end
20
+ end
21
+
22
+ def sync=(bool)
23
+ FFI::MemoryPointer.new(:int) do |ptr|
24
+ ptr.write_int(bool ? CS_SYNC_IO : CS_ASYNC_IO) # CS_DEFER_IO ?
25
+ Lib.check Lib.ct_config(@ptr, CS_SET, CS_NETIO, ptr, CS_UNUSED, nil)
26
+ end
27
+ end
28
+
29
+ def callbacks
30
+ @callbacks ||= Callbacks.new self
31
+ end
32
+
33
+ def to_ptr
34
+ @ptr
35
+ end
36
+
37
+ def exit
38
+ Lib.check Lib.ct_exit(@ptr, CS_UNUSED), "ct_exit failed"
39
+ end
40
+
41
+ private
42
+
43
+ class Callbacks
44
+ def initialize(context)
45
+ @context = context
46
+ end
47
+
48
+ def library(&cb)
49
+ actual_callback = FFI::Function.new(:int, [:pointer, :pointer]) { |context, message|
50
+ cb.call ClientMessage.new(message)
51
+ CS_SUCCEED
52
+ }
53
+ Lib.check Lib.cs_config(@context, CS_SET, CS_MESSAGE_CB, actual_callback, CS_UNUSED, nil)
54
+ end
55
+
56
+ def client(&cb)
57
+ Lib.check Lib.ct_callback(@context, nil, CS_SET, CS_CLIENTMSG_CB, lambda { |context, connection, message|
58
+ cb.call ClientMessage.new(message)
59
+ CS_SUCCEED
60
+ })
61
+ end
62
+
63
+ def server(&cb)
64
+ Lib.check Lib.ct_callback(@context, nil, CS_SET, CS_SERVERMSG_CB, lambda { |context, connection, message|
65
+ cb.call ServerMessage.new(message)
66
+ CS_SUCCEED
67
+ })
68
+ end
69
+ end # Callbacks
70
+ end # Context
71
+ end # Sybase
data/lib/sybase/lib.rb ADDED
@@ -0,0 +1,264 @@
1
+ module Sybase
2
+ module Lib
3
+ extend FFI::Library
4
+
5
+ suffix = RUBY_VERSION < '1.9' ? '' : '_r'
6
+
7
+ if FFI.type_size(:pointer) == 8
8
+ ffi_lib "sybct64#{suffix}"
9
+ else
10
+ ffi_lib "sybct#{suffix}"
11
+ end
12
+
13
+ # extern CS_RETCODE CS_PUBLIC cs_ctx_alloc PROTOTYPE((
14
+ # CS_INT version,
15
+ # CS_CONTEXT **outptr
16
+ # ));
17
+
18
+ attach_function :cs_ctx_alloc, [:int, :pointer], :int
19
+
20
+ # extern CS_RETCODE CS_PUBLIC cs_ctx_drop PROTOTYPE((
21
+ # CS_CONTEXT *context
22
+ # ));
23
+
24
+ attach_function :cs_ctx_drop, [:pointer], :int
25
+
26
+ # extern CS_RETCODE CS_PUBLIC ct_init PROTOTYPE((
27
+ # CS_CONTEXT *context,
28
+ # CS_INT version
29
+ # ));
30
+
31
+ attach_function :ct_init, [:pointer, :int], :int
32
+
33
+ # extern CS_RETCODE CS_PUBLIC ct_config PROTOTYPE((
34
+ # CS_CONTEXT *context,
35
+ # CS_INT action,
36
+ # CS_INT property,
37
+ # CS_VOID *buf,
38
+ # CS_INT buflen,
39
+ # CS_INT *outlen
40
+ # ));
41
+
42
+ attach_function :ct_config, [:pointer, :int, :int, :pointer, :int, :pointer], :int
43
+
44
+ # extern CS_RETCODE CS_PUBLIC cs_config PROTOTYPE((
45
+ # CS_CONTEXT *context,
46
+ # CS_INT action,
47
+ # CS_INT property,
48
+ # CS_VOID *buf,
49
+ # CS_INT buflen,
50
+ # CS_INT *outlen
51
+ # ));
52
+
53
+ attach_function :cs_config, [:pointer, :int, :int, :pointer, :int, :pointer], :int
54
+
55
+
56
+
57
+ callback :cs_clientmsg_cb, [:pointer, :pointer, :pointer], :int
58
+
59
+ # extern CS_RETCODE CS_PUBLIC ct_callback PROTOTYPE((
60
+ # CS_CONTEXT *context,
61
+ # CS_CONNECTION *connection,
62
+ # CS_INT action,
63
+ # CS_INT type,
64
+ # CS_VOID *func
65
+ # ));
66
+
67
+ attach_function :ct_callback, [:pointer, :pointer, :int, :int, :cs_clientmsg_cb], :int
68
+
69
+ # extern CS_RETCODE CS_PUBLIC ct_con_alloc PROTOTYPE((
70
+ # CS_CONTEXT *context,
71
+ # CS_CONNECTION **connection
72
+ # ));
73
+
74
+ attach_function :ct_con_alloc, [:pointer, :pointer], :int
75
+
76
+ # extern CS_RETCODE CS_PUBLIC ct_con_props PROTOTYPE((
77
+ # CS_CONNECTION *connection,
78
+ # CS_INT action,
79
+ # CS_INT property,
80
+ # CS_VOID *buf,
81
+ # CS_INT buflen,
82
+ # CS_INT *outlen
83
+ # ));
84
+
85
+ attach_function :ct_con_props, [:pointer, :int, :int, :pointer, :int, :pointer], :int
86
+
87
+ # extern CS_RETCODE CS_PUBLIC ct_connect PROTOTYPE((
88
+ # CS_CONNECTION *connection,
89
+ # CS_CHAR *server_name,
90
+ # CS_INT snamelen
91
+ # ));
92
+
93
+ attach_function :ct_connect, [:pointer, :pointer, :int], :int
94
+
95
+ # extern CS_RETCODE CS_PUBLIC ct_close PROTOTYPE((
96
+ # CS_CONNECTION *connection,
97
+ # CS_INT option
98
+ # ));
99
+
100
+ attach_function :ct_close, [:pointer, :int], :int
101
+
102
+ # extern CS_RETCODE CS_PUBLIC ct_cmd_alloc PROTOTYPE((
103
+ # CS_CONNECTION *connection,
104
+ # CS_COMMAND **cmdptr
105
+ # ));
106
+
107
+ attach_function :ct_cmd_alloc, [:pointer, :pointer], :int
108
+
109
+ # extern CS_RETCODE CS_PUBLIC ct_cmd_drop PROTOTYPE((
110
+ # CS_COMMAND *cmd
111
+ # ));
112
+
113
+ attach_function :ct_cmd_drop, [:pointer], :int
114
+
115
+ # extern CS_RETCODE CS_PUBLIC ct_cmd_props PROTOTYPE((
116
+ # CS_COMMAND *cmd,
117
+ # CS_INT action,
118
+ # CS_INT property,
119
+ # CS_VOID *buf,
120
+ # CS_INT buflen,
121
+ # CS_INT *outlen
122
+ # ));
123
+
124
+ attach_function :ct_cmd_props, [:pointer, :int, :int, :pointer, :int, :pointer], :int
125
+
126
+ # extern CS_RETCODE CS_PUBLIC ct_command PROTOTYPE((
127
+ # CS_COMMAND *cmd,
128
+ # CS_INT type,
129
+ # CS_CHAR *buf,
130
+ # CS_INT buflen,
131
+ # CS_INT option
132
+ # ));
133
+
134
+ attach_function :ct_command, [:pointer, :int, :string, :int, :int], :int
135
+
136
+ # extern CS_RETCODE CS_PUBLIC ct_send PROTOTYPE((
137
+ # CS_COMMAND *cmd
138
+ # ));
139
+
140
+ attach_function :ct_send, [:pointer], :int
141
+
142
+ # extern CS_RETCODE CS_PUBLIC ct_results PROTOTYPE((
143
+ # CS_COMMAND *cmd,
144
+ # CS_INT *result_type
145
+ # ));
146
+
147
+ attach_function :ct_results, [:pointer, :pointer], :int
148
+
149
+ # extern CS_RETCODE CS_PUBLIC ct_close PROTOTYPE((
150
+ # CS_CONNECTION *connection,
151
+ # CS_INT option
152
+ # ));
153
+
154
+ attach_function :close, [:pointer, :int], :int
155
+
156
+ # extern CS_RETCODE CS_PUBLIC ct_exit PROTOTYPE((
157
+ # CS_CONTEXT *context,
158
+ # CS_INT option
159
+ # ));
160
+
161
+ attach_function :ct_exit, [:pointer, :int], :int
162
+
163
+ # extern CS_RETCODE CS_PUBLIC ct_con_drop PROTOTYPE((
164
+ # CS_CONNECTION *connection
165
+ # ));
166
+
167
+ attach_function :ct_con_drop, [:pointer], :int
168
+
169
+ # extern CS_RETCODE CS_PUBLIC ct_cancel PROTOTYPE((
170
+ # CS_CONNECTION *connection,
171
+ # CS_COMMAND *cmd,
172
+ # CS_INT type
173
+ # ));
174
+
175
+ attach_function :ct_cancel, [:pointer, :pointer, :int], :int
176
+
177
+ # extern CS_RETCODE CS_PUBLIC ct_res_info PROTOTYPE((
178
+ # CS_COMMAND *cmd,
179
+ # CS_INT operation,
180
+ # CS_VOID *buf,
181
+ # CS_INT buflen,
182
+ # CS_INT *outlen
183
+ # ));
184
+
185
+ attach_function :ct_res_info, [:pointer, :int, :pointer, :int, :pointer], :int
186
+
187
+
188
+ # extern CS_RETCODE CS_PUBLIC ct_describe PROTOTYPE((
189
+ # CS_COMMAND *cmd,
190
+ # CS_INT item,
191
+ # CS_DATAFMT *datafmt
192
+ # ));
193
+
194
+ attach_function :ct_describe, [:pointer, :int, :pointer], :int
195
+
196
+ # extern CS_RETCODE CS_PUBLIC ct_bind PROTOTYPE((
197
+ # CS_COMMAND *cmd,
198
+ # CS_INT item,
199
+ # CS_DATAFMT *datafmt,
200
+ # CS_VOID *buf,
201
+ # CS_INT *outputlen,
202
+ # CS_SMALLINT *indicator
203
+ # ));
204
+
205
+ attach_function :ct_bind, [:pointer, :int, :pointer, :pointer, :pointer, :pointer], :int
206
+
207
+ # extern CS_RETCODE CS_PUBLIC ct_fetch PROTOTYPE((
208
+ # CS_COMMAND *cmd,
209
+ # CS_INT type,
210
+ # CS_INT offset,
211
+ # CS_INT option,
212
+ # CS_INT *count
213
+ # ));
214
+
215
+ attach_function :ct_fetch, [:pointer, :int, :int, :int, :pointer], :int
216
+
217
+ # extern CS_RETCODE CS_PUBLIC ct_debug PROTOTYPE((
218
+ # CS_CONTEXT *context,
219
+ # CS_CONNECTION *connection,
220
+ # CS_INT operation,
221
+ # CS_INT flag,
222
+ # CS_CHAR *filename,
223
+ # CS_INT fnamelen
224
+ # ));
225
+
226
+ attach_function :ct_debug, [:pointer, :pointer, :int, :int, :pointer, :int], :int
227
+
228
+ def self.check(code, msg = "error")
229
+ if code != CS_SUCCEED
230
+ raise Error, msg
231
+ end
232
+ end
233
+
234
+ def self.display_length(data_format)
235
+ len = case data_format[:datatype]
236
+ when CS_CHAR_TYPE, CS_LONGCHAR_TYPE, CS_VARCHAR_TYPE, CS_TEXT_TYPE, CS_IMAGE_TYPE
237
+ [data_format[:maxlength], MAX_CHAR_BUF].min
238
+ when CS_UNICHAR_TYPE
239
+ [data_format[:maxlength] / 2, MAX_CHAR_BUF].min
240
+ when CS_BINARY_TYPE, CS_VARBINARY_TYPE
241
+ [(2 * data_format[:maxlength]) + 2, MAX_CHAR_BUF].min
242
+ when CS_BIT_TYPE, CS_TINYINT_TYPE
243
+ 3
244
+ when CS_SMALLINT_TYPE
245
+ 6
246
+ when CS_INT_TYPE
247
+ 11
248
+ when CS_REAL_TYPE, CS_FLOAT_TYPE
249
+ 20
250
+ when CS_MONEY_TYPE, CS_MONEY4_TYPE
251
+ 24
252
+ when CS_DATETIME_TYPE, CS_DATETIME4_TYPE
253
+ 30
254
+ when CS_NUMERIC_TYPE, CS_DECIMAL_TYPE
255
+ CS_MAX_PREC + 2
256
+ else
257
+ 12
258
+ end
259
+
260
+
261
+ [data_format[:name].size + 1, len].max
262
+ end
263
+ end # Lib
264
+ end # Sybase
@@ -0,0 +1,33 @@
1
+ module Sybase
2
+ # typedef struct _cs_clientmsg
3
+ # {
4
+ # CS_INT severity;
5
+ # CS_MSGNUM msgnumber;
6
+ # CS_CHAR msgstring[CS_MAX_MSG];
7
+ # CS_INT msgstringlen;
8
+ # CS_INT osnumber;
9
+ # CS_CHAR osstring[CS_MAX_MSG];
10
+ # CS_INT osstringlen;
11
+ # CS_INT status;
12
+ # CS_BYTE sqlstate[CS_SQLSTATE_SIZE];
13
+ # CS_INT sqlstatelen;
14
+ # } CS_CLIENTMSG;
15
+
16
+ class ClientMessage < Message
17
+ layout :severity, :int,
18
+ :msgnumber, :uint,
19
+ :msgstring, [:char, CS_MAX_MSG],
20
+ :msgstringlen, :int,
21
+ :osnumber, :int,
22
+ :osstring, [:char, CS_MAX_MSG],
23
+ :osstringlen, :int,
24
+ :status, :int,
25
+ :sqlstate, [:uchar, CS_SQLSTATE_SIZE],
26
+ :sqlstatelen, :int
27
+
28
+ def text
29
+ self[:msgstring].to_s.chomp
30
+ end
31
+
32
+ end # ClientMessage
33
+ end
@@ -0,0 +1,60 @@
1
+ module Sybase
2
+ class ColumnData
3
+ attr_reader :pointer, :type, :format
4
+
5
+ def initialize
6
+ @pointer = nil
7
+ @type = nil
8
+ @format = DataFormat.new
9
+ end
10
+
11
+ def inspect
12
+ "#<#{self.class}:0x#{(hash*2).to_s(16)} type=#{type.inspect} valuelen=#{valuelen} indicator=#{indicator}>"
13
+ end
14
+
15
+ def char_pointer!(size = 256)
16
+ @type = :char
17
+ @pointer = FFI::MemoryPointer.new(@type, size)
18
+ end
19
+
20
+ def int_pointer!
21
+ @type = :int
22
+ @pointer = FFI::MemoryPointer.new(@type)
23
+ end
24
+
25
+ def double_pointer!
26
+ @type = :double
27
+ @pointer = FFI::MemoryPointer.new(@type)
28
+ end
29
+
30
+ def valuelen_pointer
31
+ @valuelen_ptr ||= FFI::MemoryPointer.new(:int)
32
+ end
33
+
34
+ def indicator_pointer
35
+ @indicator_ptr ||= FFI::MemoryPointer.new(:int)
36
+ end
37
+
38
+ def valuelen
39
+ valuelen_pointer.read_int
40
+ end
41
+
42
+ def indicator
43
+ indicator_pointer.read_int
44
+ end
45
+
46
+ def value
47
+ case @type
48
+ when :int
49
+ @pointer.read_int
50
+ when :char
51
+ @pointer.get_bytes(0, valuelen - 1)
52
+ when :double
53
+ @pointer.read_double
54
+ else
55
+ raise Error, "uknown type #{type}"
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,63 @@
1
+ module Sybase
2
+
3
+ # typedef struct _cs_datafmt
4
+ # {
5
+ # CS_CHAR name[CS_MAX_NAME]; // CS_MAX_CHAR if >= Sybase 15
6
+ # CS_INT namelen;
7
+ # CS_INT datatype;
8
+ # CS_INT format;
9
+ # CS_INT maxlength;
10
+ # CS_INT scale;
11
+ # CS_INT precision;
12
+ # CS_INT status;
13
+ # CS_INT count;
14
+ # CS_INT usertype;
15
+ # CS_LOCALE *locale;
16
+ # } CS_DATAFMT;
17
+
18
+
19
+ class DataFormat < FFI::Struct
20
+ attr_accessor :ruby_type
21
+
22
+ layout :name, [:char, CS_MAX_CHAR],
23
+ :namelen, :int,
24
+ :datatype, :int,
25
+ :format, :int,
26
+ :maxlength, :int,
27
+ :scale, :int,
28
+ :precision, :int,
29
+ :status, :int,
30
+ :count, :int,
31
+ :usertype, :int,
32
+ :locale, :pointer
33
+
34
+ INTS = [:namelen, :datatype, :format, :maxlength, :scale, :precision, :status, :count, :usertype]
35
+
36
+ def reset!
37
+ INTS.each { |key| self[key] = 0 }
38
+ end
39
+
40
+ def inspect
41
+ "#<%s name=%s namelen=%d datatype=%d format=%d maxlength=%d scale=%d precision=%d status=%d count=%d usertype=%d locale=%s address=%x>" % [
42
+ self.class.name,
43
+ name.inspect,
44
+ self[:namelen],
45
+ self[:datatype],
46
+ self[:format],
47
+ self[:maxlength],
48
+ self[:scale],
49
+ self[:precision],
50
+ self[:status],
51
+ self[:count],
52
+ self[:usertype],
53
+ self[:locale].inspect,
54
+ to_ptr.address
55
+ ]
56
+ end
57
+
58
+ def name
59
+ self[:name].to_s
60
+ end
61
+ end
62
+
63
+ end
@@ -0,0 +1,27 @@
1
+ module Sybase
2
+ class Message < FFI::Struct
3
+ def severity
4
+ (self[:severity] >> 8) & 0xff
5
+ end
6
+
7
+ def number
8
+ self[:msgnumber] & 0xff
9
+ end
10
+
11
+ def origin
12
+ (self[:msgnumber]) >> 16 & 0xff
13
+ end
14
+
15
+ def layer
16
+ (self[:msgnumber] >> 24) & 0x44
17
+ end
18
+
19
+ def inspect
20
+ "#<%s text=%s severity=%d number=%d origin=%d layer=%d>" % [self.class.name, text.inspect, severity, number, origin, layer]
21
+ end
22
+
23
+ def to_s
24
+ "%s (severity=%d number=%d origin=%d layer=%d)" % [text, severity, number, origin, layer]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,38 @@
1
+ module Sybase
2
+ # typedef struct _cs_servermsg
3
+ # {
4
+ # CS_MSGNUM msgnumber;
5
+ # CS_INT state;
6
+ # CS_INT severity;
7
+ # CS_CHAR text[CS_MAX_MSG];
8
+ # CS_INT textlen;
9
+ # CS_CHAR svrname[CS_MAX_CHAR];
10
+ # CS_INT svrnlen;
11
+ # CS_CHAR proc[CS_MAX_CHAR];
12
+ # CS_INT proclen;
13
+ # CS_INT line;
14
+ # CS_INT status;
15
+ # CS_BYTE sqlstate[CS_SQLSTATE_SIZE];
16
+ # CS_INT sqlstatelen;
17
+ # } CS_SERVERMSG;
18
+
19
+ class ServerMessage < Message
20
+ layout :msgnumber, :uint,
21
+ :state, :int,
22
+ :severity, :int,
23
+ :text, [:char, CS_MAX_MSG],
24
+ :textlen, :int,
25
+ :svrname, [:char, CS_MAX_CHAR],
26
+ :svrnlen, :int,
27
+ :proc, [:char, CS_MAX_CHAR],
28
+ :proclen, :int,
29
+ :line, :int,
30
+ :status, :int,
31
+ :sqlstate, [:uchar, CS_SQLSTATE_SIZE],
32
+ :sqlstatelen, :int
33
+
34
+ def text
35
+ self[:text].to_s.chomp
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,3 @@
1
+ module Sybase
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ffi-sybase
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Jari Bakken
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-03-19 00:00:00 +01:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: ffi
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: 0.6.3
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ description: Ruby/FFI bindings for Sybase's Open Client library.
28
+ email:
29
+ - jari.bakken@gmail.com
30
+ executables: []
31
+
32
+ extensions: []
33
+
34
+ extra_rdoc_files: []
35
+
36
+ files:
37
+ - .gitignore
38
+ - Gemfile
39
+ - LICENSE
40
+ - README
41
+ - Rakefile
42
+ - TODO
43
+ - examples/sp_who.rb
44
+ - ffi-sybase.gemspec
45
+ - lib/ffi-sybase.rb
46
+ - lib/sybase/command.rb
47
+ - lib/sybase/connection.rb
48
+ - lib/sybase/constants.rb
49
+ - lib/sybase/context.rb
50
+ - lib/sybase/lib.rb
51
+ - lib/sybase/structs/client_message.rb
52
+ - lib/sybase/structs/column_data.rb
53
+ - lib/sybase/structs/data_format.rb
54
+ - lib/sybase/structs/message.rb
55
+ - lib/sybase/structs/server_message.rb
56
+ - lib/sybase/version.rb
57
+ has_rdoc: true
58
+ homepage: http://github.com/jarib/ffi-sybase
59
+ licenses: []
60
+
61
+ post_install_message:
62
+ rdoc_options: []
63
+
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ requirements: []
79
+
80
+ rubyforge_project: ffi-sybase
81
+ rubygems_version: 1.5.2
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: Ruby/FFI bindings for Sybase OCS
85
+ test_files: []
86
+