ffi-sybase 0.0.1

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.
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
+