Class: MonetDBConnection
- Inherits:
-
Object
- Object
- MonetDBConnection
- Defined in:
- lib/MonetDBConnection.rb
Constant Summary collapse
- @@DEBUG =
enable debug output
false
- @@HOUR =
hour in seconds, used for timezone calculation
3600
- @@MAX_MESSAGE_SIZE =
maximum size (in bytes) for a monetdb message to be sent
32766
- @@CLIENT_ENDIANNESS =
endianness of a message sent to the server
"BIG"
- @@SUPPORTED_PROTOCOLS =
MAPI protocols supported by the driver
[ 8, 9 ]
Instance Attribute Summary collapse
-
#auto_commit ⇒ Object
readonly
Returns the value of attribute auto_commit.
-
#lang ⇒ Object
readonly
Returns the value of attribute lang.
-
#socket ⇒ Object
readonly
Returns the value of attribute socket.
-
#transactions ⇒ Object
readonly
Returns the value of attribute transactions.
Instance Method Summary collapse
-
#auto_commit? ⇒ Boolean
Check the auto commit status (on/off).
-
#build_auth_string_v8(auth_type, salt, db_name) ⇒ Object
Builds and authentication string given the parameters submitted by the user (MAPI protocol v8).
-
#build_auth_string_v9(auth_type, salt, db_name) ⇒ Object
Builds and authentication string given the parameters submitted by the user (MAPI protocol v9).
-
#connect(db_name = 'demo', auth_type = 'SHA1') ⇒ Object
Connect to the database, creates a new socket.
-
#disconnect ⇒ Object
Disconnect from server.
-
#encode_message(msg = "") ⇒ Object
builds a message to be sent to the server.
-
#format_command(x) ⇒ Object
Formats a command string so that it can be parsed by the server.
-
#initialize(user = "monetdb", passwd = "monetdb", lang = "sql", host = "127.0.0.1", port = "50000") ⇒ MonetDBConnection
constructor
Instantiates a new MonetDBConnection object * user: username (default is monetdb) * passwd: password (default is monetdb) * lang: language (default is sql) * host: server hostanme or ip (default is localhost) * port: server port (default is 50000).
-
#mapi_proto_v8? ⇒ Boolean
Check which protocol is spoken by the server.
- #mapi_proto_v9? ⇒ Boolean
-
#merovingian? ⇒ Boolean
Check if monetdb is running behind the merovingian proxy and forward the connection in case.
- #mserver? ⇒ Boolean
-
#real_connect ⇒ Object
perform a real connection; retrieve challenge, proxy through merovinginan, build challenge and set the timezone.
-
#receive ⇒ Object
receive data from a monetdb5 server instance.
-
#recv_decode_hdr ⇒ Object
reads and decodes the header of a server message.
-
#retrieve_server_challenge ⇒ Object
Used as the first step in the authentication phase; retrives a challenge string from the server.
- #savepoint ⇒ Object
-
#send(data) ⇒ Object
send data to a monetdb5 server instance and returns server’s response.
-
#set_auto_commit(flag = true) ⇒ Object
Turns auto commit on/off.
-
#set_export(id, idx, offset) ⇒ Object
send an ‘export’ command to the server.
- #set_output_seq ⇒ Object
-
#set_reply_size ⇒ Object
send a ‘reply_size’ command to the server.
-
#set_timezone ⇒ Object
Sets the time zone according to the Operating System settings.
Constructor Details
#initialize(user = "monetdb", passwd = "monetdb", lang = "sql", host = "127.0.0.1", port = "50000") ⇒ MonetDBConnection
Instantiates a new MonetDBConnection object
-
user: username (default is monetdb)
-
passwd: password (default is monetdb)
-
lang: language (default is sql)
-
host: server hostanme or ip (default is localhost)
-
port: server port (default is 50000)
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/MonetDBConnection.rb', line 82 def initialize(user = "monetdb", passwd = "monetdb", lang = "sql", host="127.0.0.1", port = "50000") @user = user @passwd = passwd @lang = lang.downcase @host = host @port = port @client_endianness = @@CLIENT_ENDIANNESS @auth_iteration = 0 @connection_established = false @transactions = MonetDBTransaction.new # handles a pool of transactions (generates and keeps track of savepoints) if @@DEBUG == true require 'logger' end end |
Instance Attribute Details
#auto_commit ⇒ Object (readonly)
Returns the value of attribute auto_commit.
73 74 75 |
# File 'lib/MonetDBConnection.rb', line 73 def auto_commit @auto_commit end |
#lang ⇒ Object (readonly)
Returns the value of attribute lang.
73 74 75 |
# File 'lib/MonetDBConnection.rb', line 73 def lang @lang end |
#socket ⇒ Object (readonly)
Returns the value of attribute socket.
73 74 75 |
# File 'lib/MonetDBConnection.rb', line 73 def socket @socket end |
#transactions ⇒ Object (readonly)
Returns the value of attribute transactions.
73 74 75 |
# File 'lib/MonetDBConnection.rb', line 73 def transactions @transactions end |
Instance Method Details
#auto_commit? ⇒ Boolean
Check the auto commit status (on/off)
483 484 485 |
# File 'lib/MonetDBConnection.rb', line 483 def auto_commit? @auto_commit end |
#build_auth_string_v8(auth_type, salt, db_name) ⇒ Object
Builds and authentication string given the parameters submitted by the user (MAPI protocol v8).
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 |
# File 'lib/MonetDBConnection.rb', line 314 def build_auth_string_v8(auth_type, salt, db_name) # seed = password + salt if (auth_type.upcase == "MD5" or auth_type.upcase == "SHA1") and @supported_auth_types.include?(auth_type.upcase) auth_type = auth_type.upcase digest = Hasher.new(auth_type, @passwd+salt) hashsum = digest.hashsum elsif auth_type.downcase == "plain" or not @supported_auth_types.include?(auth_type.upcase) auth_type = 'plain' hashsum = @passwd + salt elsif auth_type.downcase == "crypt" auth_type = @supported_auth_types[@supported_auth_types.index(auth_type)+1] $stderr.print "The selected hashing algorithm is not supported by the Ruby driver. #{auth_type} will be used instead." digest = Hasher.new(auth_type, @passwd+salt) hashsum = digest.hashsum else # The user selected an auth type not supported by the server. raise MonetDBConnectionError, "#{auth_type} not supported by the server. Please choose one from #{@supported_auth_types}" end # Build the reply message with header reply = @client_endianness + ":" + @user + ":{" + auth_type + "}" + hashsum + ":" + @lang + ":" + db_name + ":" end |
#build_auth_string_v9(auth_type, salt, db_name) ⇒ Object
Builds and authentication string given the parameters submitted by the user (MAPI protocol v9).
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/MonetDBConnection.rb', line 341 def build_auth_string_v9(auth_type, salt, db_name) if (auth_type.upcase == "MD5" or auth_type.upcase == "SHA1") and @supported_auth_types.include?(auth_type.upcase) auth_type = auth_type.upcase # Hash the password pwhash = Hasher.new(@pwhash, @passwd) digest = Hasher.new(auth_type, pwhash.hashsum + salt) hashsum = digest.hashsum elsif auth_type.downcase == "plain" # or not @supported_auth_types.include?(auth_type.upcase) # Keep it for compatibility with merovingian auth_type = 'plain' hashsum = @passwd + salt elsif @supported_auth_types.include?(auth_type.upcase) if auth_type.upcase == "RIPEMD160" auth_type = @supported_auth_types[@supported_auth_types.index(auth_type)+1] $stderr.print "The selected hashing algorithm is not supported by the Ruby driver. #{auth_type} will be used instead." end # Hash the password pwhash = Hasher.new(@pwhash, @passwd) digest = Hasher.new(auth_type, pwhash.hashsum + salt) hashsum = digest.hashsum else # The user selected an auth type not supported by the server. raise MonetDBConnectionError, "#{auth_type} not supported by the server. Please choose one from #{@supported_auth_types}" end # Build the reply message with header reply = @client_endianness + ":" + @user + ":{" + auth_type + "}" + hashsum + ":" + @lang + ":" + db_name + ":" end |
#connect(db_name = 'demo', auth_type = 'SHA1') ⇒ Object
Connect to the database, creates a new socket
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/MonetDBConnection.rb', line 103 def connect(db_name = 'demo', auth_type = 'SHA1') @database = db_name @auth_type = auth_type @socket = TCPSocket.new(@host, @port.to_i) if real_connect if @lang == LANG_SQL set_timezone set_reply_size elsif (@lang == LANG_XQUERY) and XQUERY_OUTPUT_SEQ # require xquery output to be in seq format send(format_command("output seq")) end true end false end |
#disconnect ⇒ Object
Disconnect from server
273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/MonetDBConnection.rb', line 273 def disconnect() if @connection_established begin @socket.close rescue => e $stderr.print e end else raise MonetDBConnectionError, "No connection established." end end |
#encode_message(msg = "") ⇒ Object
builds a message to be sent to the server
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 |
# File 'lib/MonetDBConnection.rb', line 373 def (msg = "") = Array.new data = "" hdr = 0 # package header pos = 0 is_final = false # last package in the stream while (! is_final) data = msg[pos..pos+[@@MAX_MESSAGE_SIZE.to_i, (msg.length - pos).to_i].min] pos += data.length if (msg.length - pos) == 0 last_bit = 1 is_final = true else last_bit = 0 end hdr = [(data.length << 1) | last_bit].pack('v') << hdr + data.to_s # Short Little Endian Encoding end .freeze # freeze and return the encode message end |
#format_command(x) ⇒ Object
Formats a command string so that it can be parsed by the server
244 245 246 |
# File 'lib/MonetDBConnection.rb', line 244 def format_command(x) return "X" + x + "\n" end |
#mapi_proto_v8? ⇒ Boolean
Check which protocol is spoken by the server
505 506 507 508 509 510 511 |
# File 'lib/MonetDBConnection.rb', line 505 def mapi_proto_v8? if @protocol == 8 true else false end end |
#mapi_proto_v9? ⇒ Boolean
513 514 515 516 517 518 519 |
# File 'lib/MonetDBConnection.rb', line 513 def mapi_proto_v9? if @protocol == 9 true else false end end |
#merovingian? ⇒ Boolean
Check if monetdb is running behind the merovingian proxy and forward the connection in case
488 489 490 491 492 493 494 |
# File 'lib/MonetDBConnection.rb', line 488 def merovingian? if @server_name.downcase == 'merovingian' true else false end end |
#mserver? ⇒ Boolean
496 497 498 499 500 501 502 |
# File 'lib/MonetDBConnection.rb', line 496 def mserver? if @server_name.downcase == 'monetdb' true else false end end |
#real_connect ⇒ Object
perform a real connection; retrieve challenge, proxy through merovinginan, build challenge and set the timezone
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/MonetDBConnection.rb', line 123 def real_connect server_challenge = retrieve_server_challenge() print "CHALLENGE: " + server_challenge.to_s if server_challenge != nil salt = server_challenge.split(':')[0] @server_name = server_challenge.split(':')[1] @protocol = server_challenge.split(':')[2].to_i @supported_auth_types = server_challenge.split(':')[3].split(',') @server_endianness = server_challenge.split(':')[4] if @protocol == 9 @pwhash = server_challenge.split(':')[5] end else raise MonetDBConnectionError, "Error: server returned an empty challenge string." end # The server supports only RIPMED168 or crypt as an authentication hash function, but the driver does not. if @supported_auth_types.length == 1 auth = @supported_auth_types[0] if auth.upcase == "RIPEMD160" or auth.upcase == "CRYPT" raise MonetDBConnectionError, auth.upcase + " " + ": algorithm not supported by ruby-monetdb." end end # If the server protocol version is not 8: abort and notify the user. if @@SUPPORTED_PROTOCOLS.include?(@protocol) == false raise MonetDBProtocolError, "Protocol not supported. The current implementation of ruby-monetdb works with MAPI protocols #{@@SUPPORTED_PROTOCOLS} only." elsif mapi_proto_v8? reply = build_auth_string_v8(@auth_type, salt, @database) elsif mapi_proto_v9? reply = build_auth_string_v9(@auth_type, salt, @database) end if @socket != nil @connection_established = true send(reply) monetdb_auth = receive if monetdb_auth.length == 0 # auth succedeed true else if monetdb_auth[0].chr == MSG_REDIRECT #redirection redirects = [] # store a list of possible redirects monetdb_auth.split('\n').each do |m| # strip the trailing ^mapi: # if the redirect string start with something != "^mapi:" or is empty, the redirect is invalid and shall not be included. if m[0..5] == "^mapi:" redir = m[6..m.length] # url parse redir redirects.push(redir) else $stderr.print "Warning: Invalid Redirect #{m}" end end if redirects.size == 0 raise MonetDBConnectionError, "No valid redirect received" else begin uri = URI.split(redirects[0]) # Splits the string on following parts and returns array with result: # # * Scheme # * Userinfo # * Host # * Port # * Registry # * Path # * Opaque # * Query # * Fragment server_name = uri[0] host = uri[2] port = uri[3] database = uri[5].gsub(/^\//, '') if uri[5] != nil rescue URI::InvalidURIError raise MonetDBConnectionError, "Invalid redirect: #{redirects[0]}" end end if server_name == "merovingian" if @auth_iteration <= 10 @auth_iteration += 1 real_connect else raise MonetDBConnectionError, "Merovingian: too many iterations while proxying." end elsif server_name == "monetdb" begin @socket.close rescue raise MonetDBConnectionError, "I/O error while closing connection to #{@socket}" end # reinitialize a connection @host = host @port = port connect(database, @auth_type) else @connection_established = false raise MonetDBConnectionError, monetdb_auth end elsif monetdb_auth[0].chr == MSG_INFO raise MonetDBConnectionError, monetdb_auth end end end end |
#receive ⇒ Object
receive data from a monetdb5 server instance
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 |
# File 'lib/MonetDBConnection.rb', line 293 def receive is_final, chunk_size = recv_decode_hdr if chunk_size == 0 return "" # needed on ruby-1.8.6 linux/64bit; recv(0) hangs on this configuration. end data = @socket.recv(chunk_size) if is_final == false while is_final == false is_final, chunk_size = recv_decode_hdr data += @socket.recv(chunk_size) end end return data end |
#recv_decode_hdr ⇒ Object
reads and decodes the header of a server message
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 |
# File 'lib/MonetDBConnection.rb', line 406 def recv_decode_hdr() if @socket != nil fb = @socket.recv(1) sb = @socket.recv(1) # Use execeptions handling to keep compatibility between different ruby # versions. # # Chars are treated differently in ruby 1.8 and 1.9 # try do to ascii to int conversion using ord (ruby 1.9) # and if it fail fallback to character.to_i (ruby 1.8) begin fb = fb[0].ord sb = sb[0].ord rescue NoMethodError => one_eight fb = fb[0].to_i sb = sb[0].to_i end chunk_size = (sb << 7) | (fb >> 1) is_final = false if ( (fb & 1) == 1 ) is_final = true end # return the size of the chunk (in bytes) return is_final, chunk_size else raise MonetDBSocketError, "Error while receiving data\n" end end |
#retrieve_server_challenge ⇒ Object
Used as the first step in the authentication phase; retrives a challenge string from the server.
401 402 403 |
# File 'lib/MonetDBConnection.rb', line 401 def retrieve_server_challenge() server_challenge = receive end |
#savepoint ⇒ Object
239 240 241 |
# File 'lib/MonetDBConnection.rb', line 239 def savepoint @transactions.savepoint end |
#send(data) ⇒ Object
send data to a monetdb5 server instance and returns server’s response
286 287 288 289 290 |
# File 'lib/MonetDBConnection.rb', line 286 def send(data) (data).each do |m| @socket.write(m) end end |
#set_auto_commit(flag = true) ⇒ Object
Turns auto commit on/off
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 |
# File 'lib/MonetDBConnection.rb', line 463 def set_auto_commit(flag=true) if flag == false ac = " 0" else ac = " 1" end send(format_command("auto_commit " + ac)) response = receive if response == MSG_PROMPT @auto_commit = flag elsif response[0].chr == MSG_INFO raise MonetDBCommandError, response return end end |
#set_export(id, idx, offset) ⇒ Object
send an ‘export’ command to the server
250 251 252 |
# File 'lib/MonetDBConnection.rb', line 250 def set_export(id, idx, offset) send(format_command("export " + id.to_s + " " + idx.to_s + " " + offset.to_s )) end |
#set_output_seq ⇒ Object
268 269 270 |
# File 'lib/MonetDBConnection.rb', line 268 def set_output_seq send(format_command("output seq")) end |
#set_reply_size ⇒ Object
send a ‘reply_size’ command to the server
255 256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/MonetDBConnection.rb', line 255 def set_reply_size send(format_command(("reply_size " + REPLY_SIZE))) response = receive if response == MSG_PROMPT true elsif response[0] == MSG_INFO raise MonetDBCommandError, "Unable to set reply_size: #{response}" end end |
#set_timezone ⇒ Object
Sets the time zone according to the Operating System settings
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 |
# File 'lib/MonetDBConnection.rb', line 440 def set_timezone() tz = Time.new tz_offset = tz.gmt_offset / @@HOUR if tz_offset <= 9 # verify minute count! tz_offset = "'+0" + tz_offset.to_s + ":00'" else tz_offset = "'+" + tz_offset.to_s + ":00'" end query_tz = "sSET TIME ZONE INTERVAL " + tz_offset + " HOUR TO MINUTE;" # Perform the query directly within the method send(query_tz) response = receive if response == MSG_PROMPT true elsif response[0].chr == MSG_INFO raise MonetDBQueryError, response end end |