sqlanywhere-ffi 1.0.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.
@@ -0,0 +1,126 @@
1
+ require 'ffi'
2
+
3
+
4
+ class SQLAnywhere
5
+ API_VERSION_1 = 1
6
+ API_VERSION_2 = 2
7
+
8
+ ERROR_SIZE = 256
9
+
10
+ extend FFI::Library
11
+
12
+ if FFI.type_size(:size_t) == 4
13
+ SIZE_T = :uint
14
+ def self.write_size_t(location, value)
15
+ location.write_uint(value)
16
+ end
17
+ else
18
+ SIZE_T = :ulong_long
19
+ def self.write_size_t(location, value)
20
+ location.write_ulong_long(value)
21
+ end
22
+ end
23
+
24
+ # http://dcx.sybase.com/1200/en/dbprogramming/programming-sacpp-sacapi-h-fil-a-sqlany-data-direction-enu.html
25
+ DataDirection = enum [
26
+ :invalid, 0,
27
+ :input,
28
+ :output,
29
+ :input_output,
30
+ ]
31
+
32
+ # http://dcx.sybase.com/1200/en/dbprogramming/programming-sacpp-sacapi-h-fil-a-sqlany-data-type-enu.html
33
+ DataType = enum [
34
+ :invalid_type,
35
+ :binary,
36
+ :string,
37
+ :double,
38
+ :val64, # 64 bit integer
39
+ :unsigned_val64, # 64 bit unsigned integer
40
+ :val32,
41
+ :unsigned_val32,
42
+ :val16,
43
+ :unsigned_val16,
44
+ :val8,
45
+ :unsigned_val8,
46
+ ]
47
+
48
+ # http://dcx.sybase.com/1200/en/dbprogramming/programming-sacpp-sacapi-h-fil-a-sqlany-native-type-enu.html
49
+ # http://dcx.sybase.com/index.html#1201/en/dbprogramming/esqlvar.html
50
+ NativeType = enum [
51
+ # No data type.
52
+ :no_type,
53
+ # Null-terminated character string that is a valid date.
54
+ :date,
55
+ # Null-terminated character string that is a valid time.
56
+ :time,
57
+ # Null-terminated character string that is a valid timestamp.
58
+ :timestamp,
59
+ # Varying length character string, in the CHAR character set, with a two-byte length field.
60
+ # The maximum length is 32765 bytes. When sending data, you must set the length field.
61
+ # When fetching data, the database server sets the length field.
62
+ # The data is not null-terminated or blank-padded.
63
+ :var_char,
64
+ # Fixed-length blank-padded character string, in the CHAR character set.
65
+ # The maximum length, specified in bytes, is 32767.
66
+ # The data is not null-terminated.
67
+ :fix_char,
68
+ # Long varying length character string, in the CHAR character set.
69
+ :long_var_char,
70
+ # Null-terminated character string, in the CHAR character set.
71
+ # The string is blank-padded if the database is initialized with blank-padded strings.
72
+ :string,
73
+ # 8-byte floating-point number.
74
+ :double,
75
+ # 4-byte floating-point number.
76
+ :float,
77
+ # Packed decimal number (proprietary format).
78
+ :decimal,
79
+ # 32-bit signed integer.
80
+ :int,
81
+ # 16-bit signed integer.
82
+ :small_int,
83
+ # Varying length binary data with a two-byte length field.
84
+ # The maximum length is 32765 bytes.
85
+ # When supplying information to the database server, you must set the length field.
86
+ # When fetching information from the database server, the server sets the length field.
87
+ :binary,
88
+ # Long binary data.
89
+ :long_binary,
90
+ # 8-bit signed integer.
91
+ :tiny_int,
92
+ # 64-bit signed integer.
93
+ :big_int,
94
+ # 32-bit unsigned integer.
95
+ :unsigned_int,
96
+ # 16-bit unsigned integer.
97
+ :unsigned_small_int,
98
+ # 64-bit unsigned integer.
99
+ :unsigned_big_int,
100
+ # 8-bit signed integer.
101
+ :bit,
102
+ # Long varying length character string, in the NCHAR character set.
103
+ :long_n_varchar,
104
+ ]
105
+
106
+ end
107
+
108
+ # typedefs
109
+ require_relative 'bool.rb'
110
+
111
+ # structures
112
+ #
113
+ # http://dcx.sybase.com/1200/en/dbprogramming/programming-sacpp-a-sqlany-data-value-str.html
114
+ require_relative 'data_value.rb'
115
+ # http://dcx.sybase.com/1200/en/dbprogramming/programming-sacpp-a-sqlany-bind-param-str.html
116
+ require_relative 'bind_param.rb'
117
+ # http://dcx.sybase.com/1200/en/dbprogramming/programming-sacpp-a-sqlany-bind-param-info-str.html
118
+ require_relative 'bind_param_info.rb'
119
+ # http://dcx.sybase.com/1200/en/dbprogramming/programming-sacpp-a-sqlany-column-info-str.html
120
+ require_relative 'column_info.rb'
121
+ # http://dcx.sybase.com/1200/en/dbprogramming/programming-sacpp-a-sqlany-data-info-str.html
122
+ require_relative 'data_info.rb'
123
+
124
+
125
+ require_relative 'sql_anywhere_interface.rb'
126
+ require_relative 'api.rb'
@@ -0,0 +1,15 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "sqlanywhere-ffi"
3
+ s.summary = "A low-lever driver that allows Ruby code to interface with SQL Anywhere databases"
4
+ s.description = File.read(File.join(File.dirname(__FILE__), 'README.md'))
5
+ s.version = "1.0.0"
6
+ s.author = "Chris Couzens"
7
+ s.email = "ccouzens@gmail.com"
8
+ s.files = Dir['**/**']
9
+ s.executables = []
10
+ s.test_files = Dir["test/test*.rb"]
11
+ s.has_rdoc = false
12
+ s.add_dependency 'ffi', '>= 1.3.1'
13
+ s.homepage = 'https://github.com/in4systems/sqlanywhere'
14
+ end
15
+
@@ -0,0 +1,430 @@
1
+ #====================================================
2
+ #
3
+ # Copyright 2012 iAnywhere Solutions, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ #
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+ # While not a requirement of the license, if you do modify this file, we
20
+ # would appreciate hearing about it. Please email sqlany_interfaces@sybase.com
21
+ #
22
+ #
23
+ #====================================================
24
+ #
25
+ # This sample program contains a hard-coded userid and password
26
+ # to connect to the demo database. This is done to simplify the
27
+ # sample program. The use of hard-coded passwords is strongly
28
+ # discouraged in production code. A best practice for production
29
+ # code would be to prompt the user for the userid and password.
30
+ #
31
+ #====================================================
32
+
33
+ require 'test/unit'
34
+ require 'date'
35
+
36
+ begin
37
+
38
+ require_relative '../lib/sqlanywhere.rb'
39
+
40
+ end
41
+
42
+ class Types
43
+ A_INVALID_TYPE= 0
44
+ A_BINARY = 1
45
+ A_STRING = 2
46
+ A_DOUBLE = 3
47
+ A_VAL64 = 4
48
+ A_UVAL64 = 5
49
+ A_VAL32 = 6
50
+ A_UVAL32 = 7
51
+ A_VAL16 = 8
52
+ A_UVAL16 = 9
53
+ A_VAL8 = 10
54
+ A_UVAL8 = 11
55
+ end
56
+
57
+ class Direction
58
+ DD_INVALID = 0
59
+ DD_INPUT = 1
60
+ DD_OUTPUT = 2
61
+ DD_INPUT_OUTPUT = 3
62
+ end
63
+
64
+ class SQLAnywhere_Test < Test::Unit::TestCase
65
+ def setup
66
+ @api = SQLAnywhere::SQLAnywhereInterface.new()
67
+ assert_not_nil @api
68
+ assert_nothing_raised do
69
+ SQLAnywhere::API.sqlany_initialize_interface( @api )
70
+ end
71
+ assert_nothing_raised do
72
+ @api.sqlany_init()
73
+ end
74
+ @conn = @api.sqlany_new_connection()
75
+ assert_not_nil @conn
76
+ conn_str = "UID=dba;PWD=sql;ENG=syb11_in4_cc;DBF=E:\\test.db;CS=UTF-8;LINKS=ShMem,TCPIP(IP=PINE)"
77
+ assert_succeeded @api.sqlany_connect(@conn, conn_str)
78
+ end
79
+
80
+ def teardown
81
+ assert_succeeded @api.sqlany_execute_immediate(@conn, 'SELECT * FROM dummy')
82
+ assert_nil @api.sqlany_disconnect(@conn)
83
+ assert_failed @api.sqlany_execute_immediate(@conn, 'SELECT * FROM dummy')
84
+ assert_nil @api.sqlany_free_connection(@conn)
85
+ assert_nothing_raised do
86
+ @api.sqlany_fini()
87
+ end
88
+ assert_nothing_raised do
89
+ SQLAnywhere::API.sqlany_finalize_interface( @api )
90
+ end
91
+ end
92
+
93
+ def test_execute_immediate
94
+ assert_succeeded @api.sqlany_execute_immediate(@conn, 'SELECT * FROM dummy')
95
+ end
96
+
97
+ def test_errors
98
+ sql = "INSERT INTO test(\"id\") VALUES('test');"
99
+ assert_failed @api.sqlany_execute_immediate(@conn, sql)
100
+ code, msg = @api.sqlany_error(@conn)
101
+ assert_equal -157, code
102
+ assert_not_equal "", msg
103
+ assert_equal "53018\000", @api.sqlany_sqlstate(@conn)
104
+ assert_nil @api.sqlany_clear_error(@conn)
105
+ code, msg = @api.sqlany_error(@conn)
106
+ assert_equal 0, code
107
+ assert_equal "", msg
108
+ end
109
+
110
+ def test_rollback
111
+ id = setup_transaction
112
+ @api.sqlany_rollback(@conn)
113
+ sql = "SELECT * FROM test where \"id\" = " + id.to_s + ";"
114
+ rs = exec_direct_with_test(sql)
115
+ assert_failed @api.sqlany_fetch_next(rs)
116
+ end
117
+
118
+ def test_commit
119
+ id = setup_transaction
120
+ @api.sqlany_commit(@conn)
121
+ sql = "SELECT * FROM test where \"id\" = " + id.to_s + ";"
122
+ rs = exec_direct_with_test(sql)
123
+ assert_succeeded @api.sqlany_fetch_next(rs)
124
+ res, ret_id = @api.sqlany_get_column(rs, 0)
125
+ assert_succeeded res
126
+ assert_not_nil ret_id
127
+ assert_equal id, ret_id
128
+ assert_failed @api.sqlany_fetch_next(rs)
129
+ end
130
+
131
+ def test_column_info
132
+ is_iq = is_iq_table?("types")
133
+ rs = exec_direct_with_test("SELECT TOP 2 * FROM \"types\" ORDER BY \"id\"")
134
+ assert_equal 22, @api.sqlany_num_cols(rs)
135
+ assert_column_info(rs, 0, "id", Types::A_VAL32, 4)
136
+ assert_column_info(rs, 1, "_binary_", Types::A_BINARY, 8)
137
+ assert_column_info(rs, 2, "_numeric_", Types::A_STRING, 2)
138
+ assert_column_info(rs, 3, "_decimal_", Types::A_STRING, 2)
139
+ assert_column_info(rs, 4, "_bounded_string_", Types::A_STRING, 255)
140
+ assert_column_info(rs, 5, "_unbounded_string_", Types::A_STRING, (2 * (2**30)) - 1)
141
+ assert_column_info(rs, 6, "_signed_bigint_", Types::A_VAL64, 8)
142
+ assert_column_info(rs, 7, "_unsigned_bigint_", Types::A_UVAL64, 8)
143
+ assert_column_info(rs, 8, "_signed_int_", Types::A_VAL32, 4)
144
+ assert_column_info(rs, 9, "_unsigned_int_", Types::A_UVAL32, 4)
145
+ assert_column_info(rs, 10, "_signed_smallint_", Types::A_VAL16, 2)
146
+ assert_column_info(rs, 11, "_unsigned_smallint_", Types::A_UVAL16, 2) unless is_iq #IQ Does not have an unsigned small int datatype
147
+ assert_column_info(rs, 12, "_signed_tinyint_", Types::A_UVAL8, 1)
148
+ assert_column_info(rs, 13, "_unsigned_tinyint_", Types::A_UVAL8, 1)
149
+ assert_column_info(rs, 14, "_bit_", Types::A_VAL8, 1)
150
+ assert_column_info(rs, 15, "_date_", Types::A_STRING, 10)
151
+ assert_column_info(rs, 16, "_datetime_", Types::A_STRING, 23)
152
+ assert_column_info(rs, 17, "_smalldatetime_", Types::A_STRING, 23)
153
+ assert_column_info(rs, 18, "_timestamp_", Types::A_STRING, 23)
154
+ assert_column_info(rs, 19, "_double_", Types::A_DOUBLE, 8)
155
+ assert_column_info(rs, 20, "_float_", Types::A_DOUBLE, 4)
156
+ assert_column_info(rs, 21, "_real_", Types::A_DOUBLE, 4)
157
+ assert_nil @api.sqlany_free_stmt(rs)
158
+ end
159
+
160
+ def test_bounds_on_types
161
+ is_iq = is_iq_table?("types")
162
+ rs = exec_direct_with_test("SELECT TOP 2 * FROM \"types\" ORDER BY \"id\"")
163
+ assert_succeeded @api.sqlany_fetch_next(rs)
164
+ assert_class_and_value(rs, String, 1, "x")
165
+ assert_class_and_value(rs, String, 2, "1.1")
166
+ assert_class_and_value(rs, String, 3, "1.1")
167
+ assert_class_and_value(rs, String, 4, 'Bounded String Test')
168
+ assert_class_and_value(rs, String, 5, 'Unbounded String Test')
169
+ assert_class_and_value(rs, Bignum, 6, 9223372036854775807)
170
+ assert_class_and_value(rs, Bignum, 7, 18446744073709551615)
171
+ assert_class_and_value(rs, Bignum, 8, 2147483647)
172
+ assert_class_and_value(rs, Bignum, 9, 4294967295)
173
+ assert_class_and_value(rs, Fixnum, 10, 32767)
174
+ assert_class_and_value(rs, Fixnum, 11, 65535) unless is_iq #IQ Does not have an unsigned small int datatype
175
+ assert_class_and_value(rs, Fixnum, 12, 255)
176
+ assert_class_and_value(rs, Fixnum, 13, 255)
177
+ assert_class_and_value(rs, Fixnum, 14, 1)
178
+ assert_date_and_time(rs, Date, 15, Date.new(1999, 1, 2))
179
+ assert_date_and_time(rs, DateTime, 16, DateTime.new(1999, 1, 2, 21, 20, 53))
180
+ assert_date_and_time(rs, DateTime, 17, DateTime.new(1999, 1, 2, 21, 20, 53))
181
+ assert_date_and_time(rs, DateTime, 18, DateTime.new(1999, 1, 2, 21, 20, 53))
182
+ assert_class_and_float_value(rs, Float, 19, 1.79769313486231e+308, 1e+293 )
183
+ assert_class_and_float_value(rs, Float, 20, 3.402823e+38, 1e+32 )
184
+ assert_class_and_float_value(rs, Float, 21, 3.402823e+38, 1e+32 )
185
+
186
+ assert_succeeded @api.sqlany_fetch_next(rs)
187
+ assert_class_and_value(rs, String, 1, 255.chr)
188
+ assert_class_and_value(rs, String, 2, "-1.1")
189
+ assert_class_and_value(rs, String, 3, "-1.1")
190
+ assert_class_and_value(rs, String, 4, '')
191
+ assert_class_and_value(rs, String, 5, '')
192
+ assert_class_and_value(rs, Bignum, 6, -9223372036854775808)
193
+ assert_class_and_value(rs, Fixnum, 7, 0)
194
+ assert_class_and_value(rs, Bignum, 8, -2147483648)
195
+ assert_class_and_value(rs, Fixnum, 9, 0)
196
+ assert_class_and_value(rs, Fixnum, 10, -32768)
197
+ assert_class_and_value(rs, Fixnum, 11, 0) unless is_iq #IQ Does not have an unsigned small int datatype
198
+ assert_class_and_value(rs, Fixnum, 12, 0)
199
+ assert_class_and_value(rs, Fixnum, 13, 0)
200
+ assert_class_and_value(rs, Fixnum, 14, 0)
201
+ assert_class_and_value(rs, NilClass, 15, nil)
202
+ assert_class_and_value(rs, NilClass, 16, nil)
203
+ assert_class_and_value(rs, NilClass, 17, nil)
204
+ assert_class_and_value(rs, NilClass, 18, nil)
205
+ assert_class_and_float_value(rs, Float, 19, -1.79769313486231e+308, 1e+293 )
206
+ assert_class_and_float_value(rs, Float, 20, -3.402823e+38, 1e+32 )
207
+ assert_class_and_float_value(rs, Float, 21, -3.402823e+38, 1e+32 )
208
+ assert_nil @api.sqlany_free_stmt(rs)
209
+ end
210
+
211
+ def test_prepared_stmt
212
+ is_iq = is_iq_table?("types")
213
+ stmt = @api.sqlany_prepare(@conn, "SELECT * FROM \"types\" WHERE \"id\" = ?")
214
+ assert_not_nil stmt
215
+ assert_failed @api.sqlany_execute(stmt) unless is_iq #IQ does not throw an error here
216
+ assert_equal 1, @api.sqlany_num_params(stmt)
217
+ res, param = @api.sqlany_describe_bind_param(stmt, 0)
218
+ assert_not_equal 0, res
219
+ assert_equal "?", param.get_name()
220
+ assert_equal :input, param.get_direction()
221
+
222
+ assert_nil param.set_value(0);
223
+ @api.sqlany_bind_param(stmt, 0, param)
224
+ assert_succeeded @api.sqlany_execute(stmt)
225
+ assert_succeeded @api.sqlany_fetch_next(stmt)
226
+ assert_class_and_value(stmt, String, 4, "Bounded String Test")
227
+
228
+ assert_nil param.set_value(1);
229
+ @api.sqlany_bind_param(stmt, 0, param)
230
+ assert_succeeded @api.sqlany_execute(stmt)
231
+ assert_succeeded @api.sqlany_fetch_next(stmt)
232
+ assert_class_and_value(stmt, String, 4, "")
233
+
234
+ assert_nil @api.sqlany_free_stmt(stmt)
235
+ end
236
+
237
+ def test_insert_binary
238
+ assert_insert("_binary_", "x", String)
239
+ end
240
+
241
+ def test_insert_numeric
242
+ assert_insert("_numeric_", "1.1", String)
243
+ end
244
+
245
+ def test_insert_decimal
246
+ assert_insert("_decimal_", "1.1", String)
247
+ end
248
+
249
+ def test_insert_bounded_string
250
+ assert_insert("_bounded_string_", "Bounded String Test", String)
251
+ end
252
+
253
+ def test_insert_unbounded_string
254
+ assert_insert("_unbounded_string_", "Unbounded String Test", String)
255
+ end
256
+
257
+ def test_insert_int64
258
+ assert_insert("_signed_bigint_", 9223372036854775807, Bignum)
259
+ assert_insert("_signed_bigint_", -9223372036854775808, Bignum)
260
+ end
261
+
262
+ def test_insert_uint64
263
+ assert_insert("_unsigned_bigint_", 9223372036854775807, Bignum)
264
+ assert_insert("_unsigned_bigint_", 0, Fixnum)
265
+ end
266
+
267
+ def test_insert_int32
268
+ assert_insert("_signed_int_", 2147483647, Bignum)
269
+ assert_insert("_signed_int_", -2147483648, Bignum)
270
+ end
271
+
272
+ def test_insert_uint32
273
+ assert_insert("_unsigned_int_", 4294967295, Bignum)
274
+ assert_insert("_unsigned_int_", 0, Fixnum)
275
+ end
276
+
277
+ def test_insert_int16
278
+ assert_insert("_signed_smallint_", 32767, Fixnum)
279
+ assert_insert("_signed_smallint_", -32768, Fixnum)
280
+ end
281
+
282
+ def test_insert_uint16
283
+ is_iq = is_iq_table?("types") #IQ Does not have an unsigned small int datatype
284
+ assert_insert("_unsigned_smallint_", 65535, Fixnum) unless is_iq
285
+ assert_insert("_unsigned_smallint_", 0, Fixnum) unless is_iq
286
+ end
287
+
288
+ def test_insert_int8
289
+ assert_insert("_signed_smallint_", 255, Fixnum)
290
+ assert_insert("_signed_smallint_", 0, Fixnum)
291
+ end
292
+
293
+ def test_insert_uint8
294
+ is_iq = is_iq_table?("types") #IQ Does not have an unsigned small int datatype
295
+ assert_insert("_unsigned_smallint_", 255, Fixnum) unless is_iq
296
+ assert_insert("_unsigned_smallint_", 0, Fixnum) unless is_iq
297
+ end
298
+
299
+ def test_insert_date
300
+ assert_insert("_date_", Date.new(1999, 1, 2), Date)
301
+ end
302
+
303
+ def test_insert_datetime
304
+ assert_insert("_datetime_", DateTime.new(1999, 1, 2, 21, 20, 53), DateTime)
305
+ end
306
+
307
+ def test_insert_smalldate
308
+ assert_insert("_smalldatetime_", DateTime.new(1999, 1, 2, 21, 20, 53), DateTime)
309
+ end
310
+
311
+ def test_insert_timestamp
312
+ assert_insert("_timestamp_", DateTime.new(1999, 1, 2, 21, 20, 53), DateTime)
313
+ end
314
+
315
+ def test_insert_double
316
+ assert_insert("_double_", 1.79769313486231e+308, Float, 1e+293)
317
+ end
318
+
319
+ def test_insert_float
320
+ assert_insert("_float_", 3.402823e+38, Float, 1e+32)
321
+ end
322
+
323
+ def test_insert_real
324
+ assert_insert("_real_", 3.402823e+38, Float, 1e+32)
325
+ end
326
+
327
+ def is_iq_table?(table_name)
328
+ rs = @api.sqlany_execute_direct(@conn, "SELECT server_type FROM SYS.SYSTABLE WHERE table_name = '#{table_name}'")
329
+ @api.sqlany_fetch_next(rs)
330
+ return @api.sqlany_get_column(rs, 0)[1] == 'IQ'
331
+ end
332
+
333
+ def assert_insert(column_name, value, type, delta = nil)
334
+ stmt = @api.sqlany_prepare(@conn, 'INSERT INTO "types"("id", "' + column_name + '", "_bit_") VALUES(3, ?, 1)')
335
+ assert_not_nil stmt
336
+ res, param = @api.sqlany_describe_bind_param(stmt, 0)
337
+ if type == Date or type == DateTime then
338
+ assert_nil param.set_value(value.strftime("%F %T"));
339
+ else
340
+ assert_nil param.set_value(value);
341
+ end
342
+ @api.sqlany_bind_param(stmt, 0, param)
343
+ assert_succeeded @api.sqlany_execute(stmt)
344
+ assert_nil @api.sqlany_free_stmt(stmt)
345
+
346
+ rs = exec_direct_with_test('SELECT "' + column_name + '" FROM "types" WHERE "id" = 3')
347
+ assert_succeeded @api.sqlany_fetch_next(rs)
348
+ if type == Date or type == DateTime then
349
+ assert_date_and_time(rs, type, 0, value)
350
+ elsif type == Float
351
+ assert_class_and_float_value(rs, type, 0, value, delta)
352
+ else
353
+ assert_class_and_value(rs, type, 0, value)
354
+ end
355
+
356
+ assert_nil @api.sqlany_free_stmt(rs)
357
+
358
+ @api.sqlany_rollback(@conn)
359
+ end
360
+
361
+ def assert_column_info(rs, pos, expected_col_name, expected_col_type, expected_col_size)
362
+ res, col_num, col_name, col_type, col_native_type, col_precision, col_scale, col_size, col_nullable = @api.sqlany_get_column_info(rs, pos);
363
+ assert_succeeded res
364
+ assert_equal expected_col_name, col_name
365
+ assert_equal SQLAnywhere::DataType[expected_col_type], col_type
366
+ assert_equal expected_col_size, col_size
367
+ end
368
+
369
+ def assert_class_and_float_value(rs, cl, pos, expected_value, allowed_delta)
370
+ res, val = @api.sqlany_get_column(rs, pos)
371
+ assert_succeeded res
372
+ assert_not_nil val unless expected_value.nil?
373
+ assert_in_delta expected_value, val, allowed_delta
374
+ assert_instance_of cl, val
375
+ end
376
+
377
+ def assert_date_and_time(rs, cl, pos, expected_value)
378
+ res, val = @api.sqlany_get_column(rs, pos)
379
+ assert_succeeded res
380
+ assert_not_nil val unless expected_value.nil?
381
+ parsed = cl.parse(val)
382
+ assert_equal expected_value, parsed
383
+ assert_instance_of cl, parsed
384
+ end
385
+
386
+ def assert_class_and_value(rs, cl, pos, expected_value)
387
+ res, val = @api.sqlany_get_column(rs, pos)
388
+ assert_succeeded res
389
+ assert_not_nil val unless expected_value.nil?
390
+ assert_equal expected_value, val
391
+ assert_instance_of cl, val unless RUBY_PLATFORM =~ /java|64/ and cl == Bignum and Fixnum === val
392
+ end
393
+
394
+ def setup_transaction
395
+ sql = "INSERT INTO test VALUES( DEFAULT );"
396
+ assert_succeeded @api.sqlany_execute_immediate(@conn, sql)
397
+
398
+ rs = exec_direct_with_test("SELECT @@identity")
399
+ assert_succeeded @api.sqlany_fetch_next(rs)
400
+ res, id = @api.sqlany_get_column(rs, 0)
401
+ assert_succeeded res
402
+ assert_not_nil id
403
+
404
+ sql = "SELECT * FROM test where \"id\" = " + id.to_s + ";"
405
+ rs = @api.sqlany_execute_direct(@conn, sql)
406
+ assert_not_nil rs
407
+
408
+ assert_succeeded @api.sqlany_fetch_next(rs)
409
+ assert_failed @api.sqlany_fetch_next(rs)
410
+ assert_nil @api.sqlany_free_stmt(rs)
411
+ id
412
+ end
413
+
414
+ def exec_direct_with_test(sql)
415
+ rs = @api.sqlany_execute_direct(@conn, sql)
416
+ code, msg = @api.sqlany_error(@conn)
417
+ assert_not_nil rs, "SQL Code: #{code}; Message: #{msg}"
418
+ rs
419
+ end
420
+
421
+ def assert_succeeded(val)
422
+ assert_not_equal 0, val, @api.sqlany_error(@conn)
423
+ end
424
+
425
+ def assert_failed(val)
426
+ assert_equal 0, val, @api.sqlany_error(@conn)
427
+ end
428
+
429
+ end
430
+