monetdb 0.1.3 → 0.2.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.
- checksums.yaml +5 -13
- data/.travis.yml +5 -0
- data/CHANGELOG.rdoc +4 -0
- data/Gemfile +0 -15
- data/README.rdoc +2 -2
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/monetdb.rb +6 -255
- data/lib/monetdb/connection.rb +94 -412
- data/lib/monetdb/connection/messages.rb +16 -0
- data/lib/monetdb/connection/query.rb +136 -0
- data/lib/monetdb/connection/setup.rb +125 -0
- data/lib/monetdb/error.rb +6 -12
- data/lib/monetdb/version.rb +3 -3
- data/monetdb.gemspec +6 -3
- data/script/console +16 -1
- data/test/test_helper.rb +3 -0
- data/test/test_helper/minitest.rb +7 -0
- data/test/test_helper/simple_connection.rb +13 -0
- data/test/unit/connection/test_messages.rb +48 -0
- data/test/unit/connection/test_query.rb +276 -0
- data/test/unit/connection/test_setup.rb +364 -0
- data/test/unit/test_connection.rb +178 -0
- data/test/unit/test_monetdb.rb +77 -1
- metadata +62 -24
- data/lib/monetdb/core_ext.rb +0 -1
- data/lib/monetdb/core_ext/string.rb +0 -67
- data/lib/monetdb/data.rb +0 -300
- data/lib/monetdb/hasher.rb +0 -40
- data/lib/monetdb/transaction.rb +0 -36
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
NjJiZWNiZDM2MjA5Zjg2ZmNkNGU1YTllNmU2YjA5MjkwYjNjNWEzMw==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b124803dcab2b1006f95c5bfd252d1982350ae6c
|
4
|
+
data.tar.gz: 7472c8d1410fa5cd5e39bb79dea8059c43d5ba7a
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
ZGVjNzM1OWQ1YjFiNGZkNDVkYzUyYTlkOTZkODNlMDhhNjQyMjk4NTJlMDUx
|
11
|
-
YWM1ZGYzMTdiMTlhNThmNGYzMjU0OThlY2MzYzYwZjAwZjFjZWU=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
ZjY3NTY2ZDMzYzAzMGRhNTU4MTM2YzEwZTg2OWFiZGY1MDU1ZGZjMzUwMWVi
|
14
|
-
MTMwNThmMTI1N2FlYWQ2OGNhZjI4ZmQ5MWYzZjNiYTMxMjEzYTk3YWE2NDQx
|
15
|
-
ZGNiOTNhN2JmOGUzOTRjODY1NGVhY2EyYzNjZDBjOTc4NzQ3ZTM=
|
6
|
+
metadata.gz: b894e704798f71cd4791a4c019fde839a69cb75c89d30073210c8032e0e8e042da347cf786bdb8615dd86a26fe946994f6513e0d9b4ed238811da96f8ef44f71
|
7
|
+
data.tar.gz: 7bc0cd53a34cf88e7c85c5ede1343fe35e3a63ab9659b9c427a7a582f6a2df33f93f167703bd3d434cff036914faea273ce6d450ca282846428fb9ccfe29d69e
|
data/.travis.yml
ADDED
data/CHANGELOG.rdoc
CHANGED
data/Gemfile
CHANGED
@@ -1,18 +1,3 @@
|
|
1
1
|
source "https://rubygems.org"
|
2
2
|
|
3
3
|
gemspec
|
4
|
-
|
5
|
-
group :development do
|
6
|
-
gem "yard"
|
7
|
-
end
|
8
|
-
|
9
|
-
group :development, :test do
|
10
|
-
gem "monetdb", :path => "."
|
11
|
-
gem "pry"
|
12
|
-
end
|
13
|
-
|
14
|
-
group :test do
|
15
|
-
gem "simplecov", :require => false
|
16
|
-
gem "minitest"
|
17
|
-
gem "mocha"
|
18
|
-
end
|
data/README.rdoc
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
== MonetDB
|
1
|
+
== MonetDB {<img src="https://secure.travis-ci.org/archan937/monetdb.png"/>}[http://travis-ci.org/archan937/monetdb] {<img src="https://codeclimate.com/github/archan937/monetdb/badges/gpa.svg"/>}[https://codeclimate.com/github/archan937/monetdb]
|
2
2
|
|
3
|
-
A pure Ruby database driver for MonetDB (monetdb5).
|
3
|
+
A pure Ruby database driver for MonetDB (monetdb5-sql).
|
4
4
|
|
5
5
|
=== Installation
|
6
6
|
|
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/monetdb.rb
CHANGED
@@ -1,118 +1,10 @@
|
|
1
|
-
require "
|
2
|
-
|
1
|
+
require "active_support/core_ext/hash/reverse_merge"
|
2
|
+
|
3
3
|
require "monetdb/connection"
|
4
|
-
require "monetdb/transaction"
|
5
|
-
require "monetdb/data"
|
6
4
|
require "monetdb/error"
|
7
5
|
require "monetdb/version"
|
8
6
|
|
9
|
-
|
10
|
-
#
|
11
|
-
# A result set object is an instance of the MonetDB::Data class and has methods for fetching rows, moving around in the result set, obtaining column metadata, and releasing the result set.
|
12
|
-
#
|
13
|
-
# Records can be returned as arrays and iterators over the set.
|
14
|
-
#
|
15
|
-
# A database handler (dbh) is an instance of the MonetDB class.
|
16
|
-
#
|
17
|
-
#
|
18
|
-
# = Connection management
|
19
|
-
#
|
20
|
-
# connect - establish a new connection
|
21
|
-
# * user : username (default is monetdb)
|
22
|
-
# * passwd : password (default is monetdb)
|
23
|
-
# * lang : language (default is sql)
|
24
|
-
# * host : server hostanme or ip (default is localhost)
|
25
|
-
# * port : server port (default is 50000)
|
26
|
-
# * db_name : name of the database to connect to
|
27
|
-
# * auth_type : hashing function to use during authentication (default is SHA1)
|
28
|
-
# connected? - returns true if there is an active connection to a server, false otherwise
|
29
|
-
# reconnect - reconnect to a server
|
30
|
-
# close - terminate a connection
|
31
|
-
# auto_commit? - returns ture if the session is running in auto commit mode, false otherwise
|
32
|
-
# auto_commit - enable/disable auto commit mode
|
33
|
-
# query - fire a query
|
34
|
-
#
|
35
|
-
# Currently MAPI protocols 8 and 9 are supported.
|
36
|
-
#
|
37
|
-
#
|
38
|
-
# = Managing record sets
|
39
|
-
#
|
40
|
-
# A record set is represented as an instance of the MonetDB::Data class; the class provides methods to manage retrieved data.
|
41
|
-
#
|
42
|
-
# The following methods allow to iterate over data:
|
43
|
-
#
|
44
|
-
# fetch - iterates over the record set and retrieves on row at a time. Each row is returned as an array
|
45
|
-
# fetch_hash - iterates over columns (on cell at a time)
|
46
|
-
# fetch_all_hash - returns record set entries hashed by column name orderd by column position
|
47
|
-
#
|
48
|
-
# To return the record set as an array (with each tuple stored as array of fields) the following method can be used:
|
49
|
-
#
|
50
|
-
# fetch_all - fetch all rows and store them
|
51
|
-
#
|
52
|
-
# Information about the retrieved record set can be obtained via the following methods:
|
53
|
-
#
|
54
|
-
# num_rows - returns the number of rows present in the record set
|
55
|
-
# num_fields - returns the number of fields (columns) that compose the schema
|
56
|
-
# name_fields - returns the (ordered) name of the schema's columns
|
57
|
-
# type_fields - returns the (ordered) types list of the schema's columns
|
58
|
-
#
|
59
|
-
# To release a record set MonetDB::Data#free can be used.
|
60
|
-
#
|
61
|
-
#
|
62
|
-
# = Type conversion
|
63
|
-
#
|
64
|
-
# A mapping between SQL and ruby type is supported. Each retrieved field can be converted to a ruby datatype via
|
65
|
-
# a getTYPE method.
|
66
|
-
#
|
67
|
-
# The currently supported cast methods are:
|
68
|
-
#
|
69
|
-
# getInt - convert to an integer value
|
70
|
-
# getFloat - convert to a floating point value
|
71
|
-
# getString - return a string representation of the value, with trailing and leading " characters removed
|
72
|
-
# getBlob - convert an SQL stored HEX string to its binary representation
|
73
|
-
# getTime - return a string representation of a TIME field
|
74
|
-
# getDate - return a string representation of a DATE field
|
75
|
-
# getDateTime - convert a TIMESTAMP field to a ruby Time object
|
76
|
-
# getChar - on Ruby >= 1.9, convert a CHAR field to char
|
77
|
-
# getBool - convert a BOOLEAN field to a ruby bool object. If the value of the field is unknown, nil is returned
|
78
|
-
# getNull - convert a NULL value to a nil object
|
79
|
-
#
|
80
|
-
#
|
81
|
-
# = Transactions
|
82
|
-
#
|
83
|
-
# By default MonetDB works in auto_commit mode. To turn this feature off MonetDB#auto_commit(flag = false) can be used.
|
84
|
-
#
|
85
|
-
# Once auto_commit has been disabled it is possible to start transactions, create/delete savepoints, rollback and commit with
|
86
|
-
# the usual SQL statements.
|
87
|
-
#
|
88
|
-
# Savepoints IDs can be generated using the MonetDB#save method. To release a savepoint ID use MonetDB#release.
|
89
|
-
#
|
90
|
-
# You can access savepoints (as a stack) with the MonetDB#transactions method.
|
91
|
-
#
|
92
|
-
|
93
|
-
class MonetDB
|
94
|
-
|
95
|
-
Q_TABLE = "1" # SELECT operation
|
96
|
-
Q_UPDATE = "2" # INSERT/UPDATE operations
|
97
|
-
Q_CREATE = "3" # CREATE/DROP TABLE operations
|
98
|
-
Q_TRANSACTION = "4" # TRANSACTION
|
99
|
-
Q_PREPARE = "5" # QPREPARE message
|
100
|
-
Q_BLOCK = "6" # QBLOCK message
|
101
|
-
|
102
|
-
MSG_REDIRECT = "^" # auth redirection through merovingian
|
103
|
-
MSG_QUERY = "&"
|
104
|
-
MSG_SCHEMA_HEADER = "%"
|
105
|
-
MSG_INFO = "!" # info response from mserver
|
106
|
-
MSG_TUPLE = "["
|
107
|
-
MSG_PROMPT = ""
|
108
|
-
|
109
|
-
REPLY_SIZE = "-1"
|
110
|
-
MAX_AUTH_ITERATION = 10 # maximum number of auth iterations (through merovingian) allowed
|
111
|
-
MONET_ERROR = -1
|
112
|
-
|
113
|
-
LANG_SQL = "sql"
|
114
|
-
LANG_XQUERY = "xquery"
|
115
|
-
XQUERY_OUTPUT_SEQ = true # use MonetDB XQuery's output seq
|
7
|
+
module MonetDB
|
116
8
|
|
117
9
|
def self.logger=(logger)
|
118
10
|
@logger = logger
|
@@ -133,13 +25,10 @@ class MonetDB
|
|
133
25
|
def self.establish_connection(arg)
|
134
26
|
config = arg.is_a?(Hash) ? arg : (configurations || {})[arg.to_s]
|
135
27
|
if config
|
136
|
-
@connection =
|
137
|
-
|
138
|
-
connection.instance_variable_set(:@config, config)
|
139
|
-
connection.connect *config.values_at(:username, :password, :language, :host, :port, :database, :encryption)
|
140
|
-
end
|
28
|
+
@connection = Connection.new(config)
|
29
|
+
@connection.connect
|
141
30
|
else
|
142
|
-
raise
|
31
|
+
raise ConnectionError, "Unable to establish connection for #{arg.inspect}"
|
143
32
|
end
|
144
33
|
end
|
145
34
|
|
@@ -147,142 +36,4 @@ class MonetDB
|
|
147
36
|
@connection
|
148
37
|
end
|
149
38
|
|
150
|
-
# Establish a new connection.
|
151
|
-
# * user : username (default is monetdb)
|
152
|
-
# * passwd : password (default is monetdb)
|
153
|
-
# * lang : language (default is sql)
|
154
|
-
# * host : server hostanme or ip (default is localhost)
|
155
|
-
# * port : server port (default is 50000)
|
156
|
-
# * db_name : name of the database to connect to
|
157
|
-
# * auth_type : hashing function to use during authentication (default is SHA1)
|
158
|
-
def connect(username = "monetdb", password = "monetdb", lang = "sql", host = "127.0.0.1", port = "50000", db_name = "test", auth_type = "SHA1")
|
159
|
-
# TODO: Handle pools of connections
|
160
|
-
@username = username
|
161
|
-
@password = password
|
162
|
-
@lang = lang
|
163
|
-
@host = host
|
164
|
-
@port = port
|
165
|
-
@db_name = db_name
|
166
|
-
@auth_type = auth_type
|
167
|
-
@connection = MonetDB::Connection.new(user = @username, passwd = @password, lang = @lang, host = @host, port = @port)
|
168
|
-
@connection.connect(@db_name, @auth_type)
|
169
|
-
end
|
170
|
-
|
171
|
-
# Send a <b>user submitted</b> query to the server and store the response.
|
172
|
-
#
|
173
|
-
# Returns an instance of MonetDB::Data.
|
174
|
-
def query(q = "")
|
175
|
-
unless @connection.nil?
|
176
|
-
@data = MonetDB::Data.new(@connection)
|
177
|
-
@data.execute(q)
|
178
|
-
end
|
179
|
-
@data
|
180
|
-
end
|
181
|
-
|
182
|
-
# Send a <b>user submitted select</b> query to the server and store the response.
|
183
|
-
#
|
184
|
-
# Returns an array of arrays with casted values.
|
185
|
-
def select_rows(qry)
|
186
|
-
return if @connection.nil?
|
187
|
-
data = MonetDB::Data.new @connection
|
188
|
-
|
189
|
-
start = Time.now
|
190
|
-
@connection.send(data.send(:format_query, qry))
|
191
|
-
response = @connection.receive.split("\n")
|
192
|
-
|
193
|
-
if (row = response[0]).chr == MSG_INFO
|
194
|
-
raise QueryError, row
|
195
|
-
end
|
196
|
-
|
197
|
-
headers, rows = response.partition{|x| [MSG_SCHEMA_HEADER, MSG_QUERY].include? x[0]}
|
198
|
-
data.send :receive_record_set, headers.join("\n")
|
199
|
-
query_header = data.send :parse_header_query, headers.shift
|
200
|
-
table_header = data.send :parse_header_table, headers
|
201
|
-
rows = rows.join("\n")
|
202
|
-
|
203
|
-
row_count = rows.split("\t]\n").size
|
204
|
-
while row_count < query_header["rows"].to_i
|
205
|
-
received = @connection.receive
|
206
|
-
row_count += received.scan("\t]\n").size
|
207
|
-
rows << received
|
208
|
-
end
|
209
|
-
rows = rows.split("\t]\n")
|
210
|
-
|
211
|
-
log :info, "\n [1m[35mSQL (#{((Time.now - start) * 1000).round(1)}ms)[0m #{qry}[0m"
|
212
|
-
|
213
|
-
column_types = table_header["columns_type"]
|
214
|
-
types = table_header["columns_name"].collect{|x| column_types[x]}
|
215
|
-
|
216
|
-
rows.collect do |row|
|
217
|
-
parsed, values = [], row.gsub(/^\[\s*/, "").split(",\t")
|
218
|
-
values.each_with_index do |value, index|
|
219
|
-
parsed << begin
|
220
|
-
unless value.strip == "NULL"
|
221
|
-
case types[index]
|
222
|
-
when "bigint", "int"
|
223
|
-
value.to_i
|
224
|
-
when "double"
|
225
|
-
value.to_f
|
226
|
-
else
|
227
|
-
value.strip.gsub(/(^"|"$|\\|\")/, "").force_encoding("UTF-8")
|
228
|
-
end
|
229
|
-
end
|
230
|
-
end
|
231
|
-
end
|
232
|
-
parsed
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
|
-
# Returns whether a "connection" object exists.
|
237
|
-
def connected?
|
238
|
-
!@connection.nil?
|
239
|
-
end
|
240
|
-
|
241
|
-
# Reconnect to the server.
|
242
|
-
def reconnect
|
243
|
-
if @connection != nil
|
244
|
-
self.close
|
245
|
-
@connection = MonetDB::Connection.new(user = @username, passwd = @password, lang = @lang, host = @host, port = @port)
|
246
|
-
@connection.connect(db_name = @db_name, auth_type = @auth_type)
|
247
|
-
end
|
248
|
-
end
|
249
|
-
|
250
|
-
# Turn auto commit on/off.
|
251
|
-
def auto_commit(flag = true)
|
252
|
-
@connection.set_auto_commit(flag)
|
253
|
-
end
|
254
|
-
|
255
|
-
# Returns the current auto commit (on/off) setting.
|
256
|
-
def auto_commit?
|
257
|
-
@connection.auto_commit?
|
258
|
-
end
|
259
|
-
|
260
|
-
# Returns the name of the last savepoint in a transactions pool.
|
261
|
-
def transactions
|
262
|
-
@connection.savepoint
|
263
|
-
end
|
264
|
-
|
265
|
-
# Create a new savepoint ID.
|
266
|
-
def save
|
267
|
-
@connection.transactions.save
|
268
|
-
end
|
269
|
-
|
270
|
-
# Release a savepoint ID.
|
271
|
-
def release
|
272
|
-
@connection.transactions.release
|
273
|
-
end
|
274
|
-
|
275
|
-
# Close an active connection.
|
276
|
-
def close
|
277
|
-
@connection.disconnect
|
278
|
-
@connection = nil
|
279
|
-
end
|
280
|
-
|
281
|
-
private
|
282
|
-
|
283
|
-
# Log message.
|
284
|
-
def log(type, msg)
|
285
|
-
MonetDB.logger.send type, msg if MonetDB.logger
|
286
|
-
end
|
287
|
-
|
288
39
|
end
|
data/lib/monetdb/connection.rb
CHANGED
@@ -1,442 +1,124 @@
|
|
1
1
|
require "socket"
|
2
|
-
require "
|
3
|
-
require "
|
2
|
+
require "monetdb/connection/messages"
|
3
|
+
require "monetdb/connection/setup"
|
4
|
+
require "monetdb/connection/query"
|
4
5
|
|
5
|
-
|
6
|
-
# Implements the MAPI communication protocol
|
7
|
-
#
|
8
|
-
|
9
|
-
class MonetDB
|
6
|
+
module MonetDB
|
10
7
|
class Connection
|
11
8
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
# Perform a real connection; retrieve challenge, proxy through merovinginan, build challenge and set the timezone.
|
69
|
-
def real_connect
|
70
|
-
server_challenge = retrieve_server_challenge()
|
71
|
-
|
72
|
-
if server_challenge != nil
|
73
|
-
salt = server_challenge.split(':')[0]
|
74
|
-
@server_name = server_challenge.split(':')[1]
|
75
|
-
@protocol = server_challenge.split(':')[2].to_i
|
76
|
-
@supported_auth_types = server_challenge.split(':')[3].split(',')
|
77
|
-
@server_endianness = server_challenge.split(':')[4]
|
78
|
-
if @protocol == 9
|
79
|
-
@pwhash = server_challenge.split(':')[5]
|
80
|
-
end
|
81
|
-
else
|
82
|
-
raise MonetDB::ConnectionError, "Error: server returned an empty challenge string."
|
83
|
-
end
|
84
|
-
|
85
|
-
# The server supports only RIPMED168 or crypt as an authentication hash function, but the driver does not.
|
86
|
-
if @supported_auth_types.length == 1
|
87
|
-
auth = @supported_auth_types[0]
|
88
|
-
if auth.upcase == "RIPEMD160" or auth.upcase == "CRYPT"
|
89
|
-
raise MonetDB::ConnectionError, auth.upcase + " " + ": algorithm not supported by ruby-monetdb."
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
# If the server protocol version is not 8: abort and notify the user.
|
94
|
-
if @@SUPPORTED_PROTOCOLS.include?(@protocol) == false
|
95
|
-
raise MonetDB::ProtocolError, "Protocol not supported. The current implementation of ruby-monetdb works with MAPI protocols #{@@SUPPORTED_PROTOCOLS} only."
|
96
|
-
|
97
|
-
elsif mapi_proto_v8?
|
98
|
-
reply = build_auth_string_v8(@auth_type, salt, @database)
|
99
|
-
elsif mapi_proto_v9?
|
100
|
-
reply = build_auth_string_v9(@auth_type, salt, @database)
|
101
|
-
end
|
102
|
-
|
103
|
-
if @socket != nil
|
104
|
-
@connection_established = true
|
105
|
-
|
106
|
-
send(reply)
|
107
|
-
monetdb_auth = receive
|
108
|
-
|
109
|
-
if monetdb_auth.length == 0
|
110
|
-
# auth succedeed
|
111
|
-
true
|
112
|
-
else
|
113
|
-
if monetdb_auth[0].chr == MSG_REDIRECT
|
114
|
-
#redirection
|
115
|
-
|
116
|
-
redirects = [] # store a list of possible redirects
|
117
|
-
|
118
|
-
monetdb_auth.split('\n').each do |m|
|
119
|
-
# strip the trailing ^mapi:
|
120
|
-
# if the redirect string start with something != "^mapi:" or is empty, the redirect is invalid and shall not be included.
|
121
|
-
if m[0..5] == "^mapi:"
|
122
|
-
redir = m[6..m.length]
|
123
|
-
# url parse redir
|
124
|
-
redirects.push(redir)
|
125
|
-
else
|
126
|
-
$stderr.print "Warning: Invalid Redirect #{m}"
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
if redirects.size == 0
|
131
|
-
raise MonetDB::ConnectionError, "No valid redirect received"
|
132
|
-
else
|
133
|
-
begin
|
134
|
-
uri = URI.split(redirects[0])
|
135
|
-
# Splits the string on following parts and returns array with result:
|
136
|
-
#
|
137
|
-
# * Scheme
|
138
|
-
# * Userinfo
|
139
|
-
# * Host
|
140
|
-
# * Port
|
141
|
-
# * Registry
|
142
|
-
# * Path
|
143
|
-
# * Opaque
|
144
|
-
# * Query
|
145
|
-
# * Fragment
|
146
|
-
server_name = uri[0]
|
147
|
-
host = uri[2]
|
148
|
-
port = uri[3]
|
149
|
-
database = uri[5].gsub(/^\//, '') if uri[5] != nil
|
150
|
-
rescue URI::InvalidURIError
|
151
|
-
raise MonetDB::ConnectionError, "Invalid redirect: #{redirects[0]}"
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
if server_name == "merovingian"
|
156
|
-
if @auth_iteration <= 10
|
157
|
-
@auth_iteration += 1
|
158
|
-
real_connect
|
159
|
-
else
|
160
|
-
raise MonetDB::ConnectionError, "Merovingian: too many iterations while proxying."
|
161
|
-
end
|
162
|
-
elsif server_name == "monetdb"
|
163
|
-
begin
|
164
|
-
@socket.close
|
165
|
-
rescue
|
166
|
-
raise MonetDB::ConnectionError, "I/O error while closing connection to #{@socket}"
|
167
|
-
end
|
168
|
-
# reinitialize a connection
|
169
|
-
@host = host
|
170
|
-
@port = port
|
171
|
-
|
172
|
-
connect(database, @auth_type)
|
173
|
-
else
|
174
|
-
@connection_established = false
|
175
|
-
raise MonetDB::ConnectionError, monetdb_auth
|
176
|
-
end
|
177
|
-
elsif monetdb_auth[0].chr == MSG_INFO
|
178
|
-
raise MonetDB::ConnectionError, monetdb_auth
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
def savepoint
|
185
|
-
@transactions.savepoint
|
186
|
-
end
|
187
|
-
|
188
|
-
# Formats a <i>command</i> string so that it can be parsed by the server.
|
189
|
-
def format_command(x)
|
190
|
-
"X" + x + "\n"
|
191
|
-
end
|
192
|
-
|
193
|
-
# Send an 'export' command to the server.
|
194
|
-
def set_export(id, idx, offset)
|
195
|
-
send(format_command("export " + id.to_s + " " + idx.to_s + " " + offset.to_s ))
|
9
|
+
include Messages
|
10
|
+
include Setup
|
11
|
+
include Query
|
12
|
+
|
13
|
+
Q_TABLE = "1" # SELECT statement
|
14
|
+
Q_UPDATE = "2" # INSERT/UPDATE statement
|
15
|
+
Q_CREATE = "3" # CREATE/DROP TABLE statement
|
16
|
+
Q_TRANSACTION = "4" # TRANSACTION
|
17
|
+
Q_PREPARE = "5" # QPREPARE message
|
18
|
+
Q_BLOCK = "6" # QBLOCK message
|
19
|
+
|
20
|
+
MSG_PROMPT = ""
|
21
|
+
MSG_ERROR = "!"
|
22
|
+
MSG_REDIRECT = "^"
|
23
|
+
MSG_QUERY = "&"
|
24
|
+
MSG_SCHEME = "%"
|
25
|
+
MSG_TUPLE = "["
|
26
|
+
|
27
|
+
ENDIANNESS = "BIG"
|
28
|
+
LANG = "sql"
|
29
|
+
REPLY_SIZE = "-1"
|
30
|
+
MAX_MSG_SIZE = 32766
|
31
|
+
|
32
|
+
MAPI_V8 = "8"
|
33
|
+
MAPI_V9 = "9"
|
34
|
+
PROTOCOLS = [MAPI_V8, MAPI_V9]
|
35
|
+
|
36
|
+
AUTH_MD5 = "MD5"
|
37
|
+
AUTH_SHA512 = "SHA512"
|
38
|
+
AUTH_SHA384 = "SHA384"
|
39
|
+
AUTH_SHA256 = "SHA256"
|
40
|
+
AUTH_SHA1 = "SHA1"
|
41
|
+
AUTH_PLAIN = "PLAIN"
|
42
|
+
AUTH_TYPES = [AUTH_MD5, AUTH_SHA512, AUTH_SHA384, AUTH_SHA256, AUTH_SHA1, AUTH_PLAIN]
|
43
|
+
|
44
|
+
def initialize(config = {})
|
45
|
+
@config = {
|
46
|
+
:host => "localhost",
|
47
|
+
:port => 50000,
|
48
|
+
:username => "monetdb",
|
49
|
+
:password => "monetdb"
|
50
|
+
}.merge(
|
51
|
+
config.inject({}){|h, (k, v)| h[k.to_sym] = v; h}
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
def connect
|
56
|
+
disconnect if connected?
|
57
|
+
@socket = TCPSocket.new config[:host], config[:port].to_i
|
58
|
+
setup
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
def connected?
|
63
|
+
!socket.nil?
|
196
64
|
end
|
197
65
|
|
198
|
-
# Send a 'reply_size' command to the server.
|
199
|
-
def set_reply_size
|
200
|
-
send(format_command(("reply_size " + REPLY_SIZE)))
|
201
|
-
response = receive
|
202
|
-
|
203
|
-
if response == MSG_PROMPT
|
204
|
-
true
|
205
|
-
elsif response[0] == MSG_INFO
|
206
|
-
raise MonetDB::CommandError, "Unable to set reply_size: #{response}"
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
def set_output_seq
|
211
|
-
send(format_command("output seq"))
|
212
|
-
end
|
213
|
-
|
214
|
-
# Disconnect from server.
|
215
66
|
def disconnect
|
216
|
-
if
|
217
|
-
|
218
|
-
@socket.close
|
219
|
-
rescue => e
|
220
|
-
$stderr.print e
|
221
|
-
end
|
222
|
-
else
|
223
|
-
raise MonetDB::ConnectionError, "No connection established."
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
# Send data to a monetdb5 server instance and returns server response.
|
228
|
-
def send(data)
|
229
|
-
encode_message(data).each do |m|
|
230
|
-
@socket.write(m)
|
231
|
-
end
|
67
|
+
socket.disconnect if connected?
|
68
|
+
@socket = nil
|
232
69
|
end
|
233
70
|
|
234
|
-
|
235
|
-
def receive
|
236
|
-
is_final, chunk_size = recv_decode_hdr
|
237
|
-
|
238
|
-
if chunk_size == 0
|
239
|
-
return "" # needed on ruby-1.8.6 linux/64bit; recv(0) hangs on this configuration.
|
240
|
-
end
|
241
|
-
|
242
|
-
data = @socket.recv(chunk_size)
|
71
|
+
private
|
243
72
|
|
244
|
-
|
245
|
-
|
246
|
-
is_final, chunk_size = recv_decode_hdr
|
247
|
-
data += @socket.recv(chunk_size)
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
data
|
73
|
+
def config
|
74
|
+
@config
|
252
75
|
end
|
253
76
|
|
254
|
-
|
255
|
-
|
256
|
-
# seed = password + salt
|
257
|
-
if (auth_type.upcase == "MD5" or auth_type.upcase == "SHA1") and @supported_auth_types.include?(auth_type.upcase)
|
258
|
-
auth_type = auth_type.upcase
|
259
|
-
digest = Hasher.new(auth_type, @passwd+salt)
|
260
|
-
hashsum = digest.hashsum
|
261
|
-
elsif auth_type.downcase == "plain" or not @supported_auth_types.include?(auth_type.upcase)
|
262
|
-
auth_type = 'plain'
|
263
|
-
hashsum = @passwd + salt
|
264
|
-
|
265
|
-
elsif auth_type.downcase == "crypt"
|
266
|
-
auth_type = @supported_auth_types[@supported_auth_types.index(auth_type)+1]
|
267
|
-
$stderr.print "The selected hashing algorithm is not supported by the Ruby driver. #{auth_type} will be used instead."
|
268
|
-
digest = Hasher.new(auth_type, @passwd+salt)
|
269
|
-
hashsum = digest.hashsum
|
270
|
-
else
|
271
|
-
# The user selected an auth type not supported by the server.
|
272
|
-
raise MonetDB::ConnectionError, "#{auth_type} not supported by the server. Please choose one from #{@supported_auth_types}"
|
273
|
-
|
274
|
-
end
|
275
|
-
# Build the reply message with header
|
276
|
-
reply = @client_endianness + ":" + @user + ":{" + auth_type + "}" + hashsum + ":" + @lang + ":" + db_name + ":"
|
77
|
+
def socket
|
78
|
+
@socket
|
277
79
|
end
|
278
80
|
|
279
|
-
|
280
|
-
|
281
|
-
if (auth_type.upcase == "MD5" or auth_type.upcase == "SHA1") and @supported_auth_types.include?(auth_type.upcase)
|
282
|
-
auth_type = auth_type.upcase
|
283
|
-
# Hash the password
|
284
|
-
pwhash = Hasher.new(@pwhash, @passwd)
|
285
|
-
|
286
|
-
digest = Hasher.new(auth_type, pwhash.hashsum + salt)
|
287
|
-
hashsum = digest.hashsum
|
288
|
-
|
289
|
-
elsif auth_type.downcase == "plain" # or not @supported_auth_types.include?(auth_type.upcase)
|
290
|
-
# Keep it for compatibility with merovingian
|
291
|
-
auth_type = 'plain'
|
292
|
-
hashsum = @passwd + salt
|
293
|
-
elsif @supported_auth_types.include?(auth_type.upcase)
|
294
|
-
if auth_type.upcase == "RIPEMD160"
|
295
|
-
auth_type = @supported_auth_types[@supported_auth_types.index(auth_type)+1]
|
296
|
-
$stderr.print "The selected hashing algorithm is not supported by the Ruby driver. #{auth_type} will be used instead."
|
297
|
-
end
|
298
|
-
# Hash the password
|
299
|
-
pwhash = Hasher.new(@pwhash, @passwd)
|
300
|
-
|
301
|
-
digest = Hasher.new(auth_type, pwhash.hashsum + salt)
|
302
|
-
hashsum = digest.hashsum
|
303
|
-
else
|
304
|
-
# The user selected an auth type not supported by the server.
|
305
|
-
raise MonetDB::ConnectionError, "#{auth_type} not supported by the server. Please choose one from #{@supported_auth_types}"
|
306
|
-
end
|
307
|
-
# Build the reply message with header
|
308
|
-
reply = @client_endianness + ":" + @user + ":{" + auth_type + "}" + hashsum + ":" + @lang + ":" + db_name + ":"
|
81
|
+
def log(type, msg)
|
82
|
+
MonetDB.logger.send(type, msg) if MonetDB.logger
|
309
83
|
end
|
310
84
|
|
311
|
-
|
312
|
-
|
313
|
-
message = Array.new
|
314
|
-
data = ""
|
315
|
-
|
316
|
-
hdr = 0 # package header
|
317
|
-
pos = 0
|
318
|
-
is_final = false # last package in the stream
|
319
|
-
|
320
|
-
while (! is_final)
|
321
|
-
data = msg[pos..pos+[@@MAX_MESSAGE_SIZE.to_i, (msg.length - pos).to_i].min]
|
322
|
-
pos += data.length
|
85
|
+
def read
|
86
|
+
raise ConnectionError, "Not connected to server" unless connected?
|
323
87
|
|
324
|
-
|
325
|
-
|
326
|
-
is_final = true
|
327
|
-
else
|
328
|
-
last_bit = 0
|
329
|
-
end
|
88
|
+
length, last_chunk = read_length
|
89
|
+
data, iterations = "", 0
|
330
90
|
|
331
|
-
|
332
|
-
|
333
|
-
|
91
|
+
while (length > 0) && (iterations < 1000) do
|
92
|
+
received = socket.recv(length)
|
93
|
+
data << received
|
94
|
+
length -= received.bytesize
|
95
|
+
iterations += 1
|
334
96
|
end
|
97
|
+
data << read unless last_chunk
|
335
98
|
|
336
|
-
|
337
|
-
end
|
338
|
-
|
339
|
-
# Used as the first step in the authentication phase; retrieves a challenge string from the server.
|
340
|
-
def retrieve_server_challenge
|
341
|
-
server_challenge = receive
|
342
|
-
end
|
343
|
-
|
344
|
-
# Reads and decodes the header of a server message.
|
345
|
-
def recv_decode_hdr
|
346
|
-
if @socket != nil
|
347
|
-
fb = @socket.recv(1)
|
348
|
-
sb = @socket.recv(1)
|
349
|
-
|
350
|
-
# Use execeptions handling to keep compatibility between different ruby
|
351
|
-
# versions.
|
352
|
-
#
|
353
|
-
# Chars are treated differently in ruby 1.8 and 1.9
|
354
|
-
# try do to ascii to int conversion using ord (ruby 1.9)
|
355
|
-
# and if it fail fallback to character.to_i (ruby 1.8)
|
356
|
-
begin
|
357
|
-
fb = fb[0].ord
|
358
|
-
sb = sb[0].ord
|
359
|
-
rescue NoMethodError => one_eight
|
360
|
-
fb = fb[0].to_i
|
361
|
-
sb = sb[0].to_i
|
362
|
-
end
|
363
|
-
|
364
|
-
chunk_size = (sb << 7) | (fb >> 1)
|
365
|
-
|
366
|
-
is_final = false
|
367
|
-
if ( (fb & 1) == 1 )
|
368
|
-
is_final = true
|
369
|
-
|
370
|
-
end
|
371
|
-
# return the size of the chunk (in bytes)
|
372
|
-
return is_final, chunk_size
|
373
|
-
else
|
374
|
-
raise MonetDB::SocketError, "Error while receiving data\n"
|
375
|
-
end
|
99
|
+
data
|
376
100
|
end
|
377
101
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
tz_offset = tz.gmt_offset / @@HOUR
|
382
|
-
|
383
|
-
if tz_offset <= 9 # verify minute count!
|
384
|
-
tz_offset = "'+0" + tz_offset.to_s + ":00'"
|
385
|
-
else
|
386
|
-
tz_offset = "'+" + tz_offset.to_s + ":00'"
|
387
|
-
end
|
388
|
-
query_tz = "sSET TIME ZONE INTERVAL " + tz_offset + " HOUR TO MINUTE;"
|
389
|
-
|
390
|
-
# Perform the query directly within the method
|
391
|
-
send(query_tz)
|
392
|
-
response = receive
|
393
|
-
|
394
|
-
if response == MSG_PROMPT
|
395
|
-
true
|
396
|
-
elsif response[0].chr == MSG_INFO
|
397
|
-
raise MonetDB::QueryError, response
|
398
|
-
end
|
102
|
+
def read_length
|
103
|
+
bytes = socket.recv(2).unpack("v")[0]
|
104
|
+
[(bytes >> 1), (bytes & 1) == 1]
|
399
105
|
end
|
400
106
|
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
else
|
406
|
-
ac = " 1"
|
107
|
+
def write(message)
|
108
|
+
raise ConnectionError, "Not connected to server" unless connected?
|
109
|
+
pack(message).each do |chunk|
|
110
|
+
socket.write(chunk)
|
407
111
|
end
|
408
|
-
|
409
|
-
send(format_command("auto_commit " + ac))
|
410
|
-
response = receive
|
411
|
-
|
412
|
-
if response == MSG_PROMPT
|
413
|
-
@auto_commit = flag
|
414
|
-
elsif response[0].chr == MSG_INFO
|
415
|
-
raise MonetDB::CommandError, response
|
416
|
-
end
|
417
|
-
end
|
418
|
-
|
419
|
-
# Check the auto commit status (on/off).
|
420
|
-
def auto_commit?
|
421
|
-
!!@auto_commit
|
422
|
-
end
|
423
|
-
|
424
|
-
# Check if monetdb is running behind the merovingian proxy and forward the connection in case.
|
425
|
-
def merovingian?
|
426
|
-
@server_name.downcase == "merovingian"
|
427
|
-
end
|
428
|
-
|
429
|
-
def mserver?
|
430
|
-
@server_name.downcase == "monetdb"
|
431
|
-
end
|
432
|
-
|
433
|
-
# Check which protocol is spoken by the server.
|
434
|
-
def mapi_proto_v8?
|
435
|
-
@protocol == 8
|
112
|
+
true
|
436
113
|
end
|
437
114
|
|
438
|
-
def
|
439
|
-
|
115
|
+
def pack(message)
|
116
|
+
chunks = message.scan(/.{1,#{MAX_MSG_SIZE}}/m)
|
117
|
+
chunks.each_with_index.to_a.collect do |chunk, index|
|
118
|
+
last_bit = (index == chunks.size - 1) ? 1 : 0
|
119
|
+
length = [(chunk.size << 1) | last_bit].pack("v")
|
120
|
+
"#{length}#{chunk}"
|
121
|
+
end.freeze
|
440
122
|
end
|
441
123
|
|
442
124
|
end
|