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.
- data/CHANGELOG +34 -0
- data/Gemfile +3 -0
- data/LICENSE +23 -0
- data/README.md +102 -0
- data/lib/api.rb +8 -0
- data/lib/bind_param.rb +92 -0
- data/lib/bind_param_info.rb +12 -0
- data/lib/bool.rb +13 -0
- data/lib/column_info.rb +18 -0
- data/lib/data_info.rb +12 -0
- data/lib/data_value.rb +60 -0
- data/lib/sql_anywhere_interface.rb +113 -0
- data/lib/sqlanywhere.rb +126 -0
- data/sqlanywhere-ffi.gemspec +15 -0
- data/test/sqlanywhere_test.rb +430 -0
- data/test/test.sql +92 -0
- data/test/test_iq.sql +92 -0
- metadata +106 -0
data/lib/sqlanywhere.rb
ADDED
|
@@ -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
|
+
|