Mercurial > hg > monetdb-ruby
changeset 23:838f38eb7de4
Added .editorconfig + indented.
author | Sjoerd Mullender <sjoerd@acm.org> |
---|---|
date | Tue, 20 Oct 2020 16:24:05 +0200 (2020-10-20) |
parents | de7332f6911e |
children | a4a96e70ee13 |
files | .editorconfig lib/MonetDB.rb lib/MonetDBConnection.rb lib/MonetDBData.rb lib/MonetDBExceptions.rb lib/hasher.rb monetdb-sql.gemspec |
diffstat | 7 files changed, 926 insertions(+), 917 deletions(-) [+] |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 +max_line_length = 120 +indent_style = tab +tab_width = 4 +indent_size = tab
--- a/lib/MonetDB.rb +++ b/lib/MonetDB.rb @@ -5,205 +5,205 @@ # Copyright 1997 - July 2008 CWI, August 2008 - 2020 MonetDB B.V. # = Introduction - # - # A typical sequence of events is as follows: - # Create a database instance (handle), invoke query using the database handle to send the statement to the server and get back a result set object. - # - # A result set object has methods for fetching rows, moving around in the result set, obtaining column metadata, and releasing the result set. - # A result set object is an instance of the MonetDBData class. - # - # Records can be returned as arrays and iterators over the set. - # - # A database handler (dbh) is and instance of the MonetDB class. - # - # = Connection management - # - # connect - establish a new connection - # * user: username (default is monetdb) - # * passwd: password (default is monetdb) - # * lang: language (default is sql) - # * host: server hostname or ip (default is localhost) - # * port: server port (default is 50000) - # * db_name: name of the database to connect to - # * auth_type: hashing function to use during authentication (default is SHA1) - # - # is_connected? - returns true if there is an active connection to a server, false otherwise - # reconnect - reconnect to a server - # close - terminate a connection - # auto_commit? - returns true if the session is running in auto commit mode, false otherwise - # auto_commit - enable/disable auto commit mode. - # - # query - fire a query - # - # Currently MAPI protocols 8 and 9 are supported. - # - # = Managing record sets - # - # - # A record set is represented as an instance of the MonetDBData class; the class provides methods to manage retrieved data. - # - # - # The following methods allow to iterate over data: - # - # fetch - iterates over the record set and retrieves one row at a time. Each row is returned as an array. - # each_record - works as ruby each method. The method takes a block as parameter and yields each record to this block. - # fetch_hash - iterates over the record set and retrieves one row at a time. Each row is returned as a hash. - # each_record_as_hash - works as ruby each method. The method takes a block as parameter and yields each record, as hash, to this block - # fetch_all - returns all rows as a two dimensional array - # fetch_all_as_column_hash - returns all records as a hash with the column name as the keys and an array with all column values as values - # - # Information about the retrieved record set can be obtained via the following methods: - # - # num_rows - returns the number of rows present in the record set - # num_fields - returns the number of fields (columns) that compose the schema - # name_fields - returns the (ordered) name of the schema's columns - # type_fields - returns the (ordered) types list of the schema's columns - # - # To release a record set MonetDBData#free can be used. - # - # = Type conversion - # - # All values from the database are converted to the closest ruby type, i.e: INTEGER to int, TIME to time, CLOB to string - # Some of the more complex datatypes are not recognized, such as INTERVAL, these are converted to strings - # - # = Transactions - # - # By default monetdb works in auto_commit mode. To turn this feature off MonetDB#auto_commit(flag=false) can be used. - # - # Once auto_commit has been disable it is possible to start transactions, create/delete savepoints, rollback and commit with - # the usual SQL statements. - # - # Savepoints IDs can be generated using the MonetDB#save method. To release a savepoint ID use MonetDB#release. - # - # Savepoints can be accessed (as a stack) with the MonetDB#transactions method. - # - # demo.rb contains usage example of the above mentioned methods. +# +# A typical sequence of events is as follows: +# Create a database instance (handle), invoke query using the database handle to send the statement to the server and get back a result set object. +# +# A result set object has methods for fetching rows, moving around in the result set, obtaining column metadata, and releasing the result set. +# A result set object is an instance of the MonetDBData class. +# +# Records can be returned as arrays and iterators over the set. +# +# A database handler (dbh) is and instance of the MonetDB class. +# +# = Connection management +# +# connect - establish a new connection +# * user: username (default is monetdb) +# * passwd: password (default is monetdb) +# * lang: language (default is sql) +# * host: server hostname or ip (default is localhost) +# * port: server port (default is 50000) +# * db_name: name of the database to connect to +# * auth_type: hashing function to use during authentication (default is SHA1) +# +# is_connected? - returns true if there is an active connection to a server, false otherwise +# reconnect - reconnect to a server +# close - terminate a connection +# auto_commit? - returns true if the session is running in auto commit mode, false otherwise +# auto_commit - enable/disable auto commit mode. +# +# query - fire a query +# +# Currently MAPI protocols 8 and 9 are supported. +# +# = Managing record sets +# +# +# A record set is represented as an instance of the MonetDBData class; the class provides methods to manage retrieved data. +# +# +# The following methods allow to iterate over data: +# +# fetch - iterates over the record set and retrieves one row at a time. Each row is returned as an array. +# each_record - works as ruby each method. The method takes a block as parameter and yields each record to this block. +# fetch_hash - iterates over the record set and retrieves one row at a time. Each row is returned as a hash. +# each_record_as_hash - works as ruby each method. The method takes a block as parameter and yields each record, as hash, to this block +# fetch_all - returns all rows as a two dimensional array +# fetch_all_as_column_hash - returns all records as a hash with the column name as the keys and an array with all column values as values +# +# Information about the retrieved record set can be obtained via the following methods: +# +# num_rows - returns the number of rows present in the record set +# num_fields - returns the number of fields (columns) that compose the schema +# name_fields - returns the (ordered) name of the schema's columns +# type_fields - returns the (ordered) types list of the schema's columns +# +# To release a record set MonetDBData#free can be used. +# +# = Type conversion +# +# All values from the database are converted to the closest ruby type, i.e: INTEGER to int, TIME to time, CLOB to string +# Some of the more complex datatypes are not recognized, such as INTERVAL, these are converted to strings +# +# = Transactions +# +# By default monetdb works in auto_commit mode. To turn this feature off MonetDB#auto_commit(flag=false) can be used. +# +# Once auto_commit has been disable it is possible to start transactions, create/delete savepoints, rollback and commit with +# the usual SQL statements. +# +# Savepoints IDs can be generated using the MonetDB#save method. To release a savepoint ID use MonetDB#release. +# +# Savepoints can be accessed (as a stack) with the MonetDB#transactions method. +# +# demo.rb contains usage example of the above mentioned methods. require_relative 'MonetDBConnection' require_relative 'MonetDBData' require_relative 'MonetDBExceptions' class MonetDB - DEFAULT_USERNAME = "monetdb" - DEFAULT_PASSWORD = "monetdb" - DEFAULT_LANG = MonetDBConnection::LANG_SQL - DEFAULT_HOST = "127.0.0.1" - DEFAULT_PORT = 50000 - DEFAULT_DATABASE = "test" - DEFAULT_AUTHTYPE = "SHA1" + DEFAULT_USERNAME = "monetdb" + DEFAULT_PASSWORD = "monetdb" + DEFAULT_LANG = MonetDBConnection::LANG_SQL + DEFAULT_HOST = "127.0.0.1" + DEFAULT_PORT = 50000 + DEFAULT_DATABASE = "test" + DEFAULT_AUTHTYPE = "SHA1" - def initalize() - @connection = nil - end + def initalize() + @connection = nil + end - # Establish a new connection. - # * username: username (default is monetdb) - # * password: password (default is monetdb) - # * lang: language (default is sql) - # * host: server hostname or ip (default is localhost) - # * port: server port (default is 50000) - # * db_name: name of the database to connect to - # * auth_type: hashing function to use during authentication (default is SHA1) - def connect(username=DEFAULT_USERNAME, password=DEFAULT_PASSWORD, lang=DEFAULT_LANG, host=DEFAULT_HOST, port=DEFAULT_PORT, db_name=DEFAULT_DATABASE, auth_type=DEFAULT_AUTHTYPE) - # TODO: handle pools of connections + # Establish a new connection. + # * username: username (default is monetdb) + # * password: password (default is monetdb) + # * lang: language (default is sql) + # * host: server hostname or ip (default is localhost) + # * port: server port (default is 50000) + # * db_name: name of the database to connect to + # * auth_type: hashing function to use during authentication (default is SHA1) + def connect(username=DEFAULT_USERNAME, password=DEFAULT_PASSWORD, lang=DEFAULT_LANG, host=DEFAULT_HOST, port=DEFAULT_PORT, db_name=DEFAULT_DATABASE, auth_type=DEFAULT_AUTHTYPE) + # TODO: handle pools of connections - @username = username - @password = password - @lang = lang - @host = host - @port = port - @db_name = db_name - @auth_type = auth_type + @username = username + @password = password + @lang = lang + @host = host + @port = port + @db_name = db_name + @auth_type = auth_type - @connection = MonetDBConnection.new(user = @username, passwd = @password, lang = @lang, host = @host, port = @port) - @connection.connect(@db_name, @auth_type) - end + @connection = MonetDBConnection.new(user = @username, passwd = @password, lang = @lang, host = @host, port = @port) + @connection.connect(@db_name, @auth_type) + end - # Establish a new connection using named parameters. - # * user: username (default is monetdb) - # * passwd: password (default is monetdb) - # * language: lang (default is sql) - # * host: host to connect to (default is localhost) - # * port: port to connect to (default is 50000) - # * database: name of the database to connect to - # * auth_type: hashing function to use during authentication (default is SHA1) - # - # Conventionally named parameters are passed as an hash. - # - # Ruby 1.8: - # MonetDB::conn({ :user => "username", :passwd => "password", :database => "database"}) - # - # Ruby 1.9: - # MonetDB::conn(user: "username", passwd: "password", database: "database") - def conn(options) - user = options[:user] || DEFAULT_USERNAME - passwd = options[:passwd] || DEFAULT_PASSWORD - language = options[:language] || DEFAULT_LANG - host = options[:host] || DEFAULT_HOST - port = options[:port] || DEFAULT_PORT - database = options[:database] || DEFAULT_DATABASE - auth_type = options[:auth_type] || DEFAULT_AUTHTYPE + # Establish a new connection using named parameters. + # * user: username (default is monetdb) + # * passwd: password (default is monetdb) + # * language: lang (default is sql) + # * host: host to connect to (default is localhost) + # * port: port to connect to (default is 50000) + # * database: name of the database to connect to + # * auth_type: hashing function to use during authentication (default is SHA1) + # + # Conventionally named parameters are passed as an hash. + # + # Ruby 1.8: + # MonetDB::conn({ :user => "username", :passwd => "password", :database => "database"}) + # + # Ruby 1.9: + # MonetDB::conn(user: "username", passwd: "password", database: "database") + def conn(options) + user = options[:user] || DEFAULT_USERNAME + passwd = options[:passwd] || DEFAULT_PASSWORD + language = options[:language] || DEFAULT_LANG + host = options[:host] || DEFAULT_HOST + port = options[:port] || DEFAULT_PORT + database = options[:database] || DEFAULT_DATABASE + auth_type = options[:auth_type] || DEFAULT_AUTHTYPE - connect(user, passwd, language, host, port, database, auth_type) - end + connect(user, passwd, language, host, port, database, auth_type) + end - # Send a <b> user submitted </b> query to the server and store the response. - # Returns and instance of MonetDBData. - def query(q="") - if @connection != nil - @data = MonetDBData.new(@connection) - @data.execute(q) - end - return @data - end + # Send a <b> user submitted </b> query to the server and store the response. + # Returns and instance of MonetDBData. + def query(q="") + if @connection != nil + @data = MonetDBData.new(@connection) + @data.execute(q) + end + return @data + end - # Return true if there exists a "connection" object - def is_connected? - if @connection == nil - return false - else - return true - end - end + # Return true if there exists a "connection" object + def is_connected? + if @connection == nil + return false + else + return true + end + end - # Reconnect to the server - def reconnect - if @connection != nil - self.close + # Reconnect to the server + def reconnect + if @connection != nil + self.close - @connection = MonetDBConnection.new(user = @username, passwd = @password, lang = @lang, host = @host, port = @port) - @connection.connect(db_name = @db_name, auth_type = @auth_type) - end - end + @connection = MonetDBConnection.new(user = @username, passwd = @password, lang = @lang, host = @host, port = @port) + @connection.connect(db_name = @db_name, auth_type = @auth_type) + end + end - # Turn auto commit on/off - def auto_commit(flag=true) - @connection.set_auto_commit(flag) - end + # Turn auto commit on/off + def auto_commit(flag=true) + @connection.set_auto_commit(flag) + end - # Returns the current auto commit (on/off) settings. - def auto_commit? - @connection.auto_commit? - end + # Returns the current auto commit (on/off) settings. + def auto_commit? + @connection.auto_commit? + end - # Returns the name of the last savepoint in a transactions pool - def transactions - @connection.savepoint - end + # Returns the name of the last savepoint in a transactions pool + def transactions + @connection.savepoint + end - # Create a new savepoint ID - def save - @connection.transactions.save - end + # Create a new savepoint ID + def save + @connection.transactions.save + end - # Release a savepoint ID - def release - @connection.transactions.release - end + # Release a savepoint ID + def release + @connection.transactions.release + end - # Close an active connection - def close() - @connection.disconnect - @connection = nil - end + # Close an active connection + def close() + @connection.disconnect + @connection = nil + end end
--- a/lib/MonetDBConnection.rb +++ b/lib/MonetDBConnection.rb @@ -14,486 +14,486 @@ require_relative 'MonetDBExceptions' require 'uri' # parse merovingian redirects class MonetDBConnection - Q_TABLE = "1" # SELECT operation - Q_UPDATE = "2" # INSERT/UPDATE operations - Q_CREATE = "3" # CREATE/DROP TABLE operations - Q_TRANSACTION = "4" # TRANSACTION - Q_PREPARE = "5" - Q_BLOCK = "6" # QBLOCK message + Q_TABLE = "1" # SELECT operation + Q_UPDATE = "2" # INSERT/UPDATE operations + Q_CREATE = "3" # CREATE/DROP TABLE operations + Q_TRANSACTION = "4" # TRANSACTION + Q_PREPARE = "5" + Q_BLOCK = "6" # QBLOCK message - MSG_REDIRECT = '^' # auth redirection through merovingian - MSG_QUERY = '&' - MSG_SCHEMA_HEADER = '%' - MSG_INFO = '!' # info response from mserver - MSG_TUPLE = '[' - MSG_PROMPT = "" + MSG_REDIRECT = '^' # auth redirection through merovingian + MSG_QUERY = '&' + MSG_SCHEMA_HEADER = '%' + MSG_INFO = '!' # info response from mserver + MSG_TUPLE = '[' + MSG_PROMPT = "" - REPLY_SIZE = '-1' + REPLY_SIZE = '-1' - MAX_AUTH_ITERATION = 10 # maximum number of auth iterations (through merovingian) allowed + MAX_AUTH_ITERATION = 10 # maximum number of auth iterations (through merovingian) allowed - MONET_ERROR = -1 + MONET_ERROR = -1 - LANG_SQL = "sql" + LANG_SQL = "sql" - # Protocols - MAPIv9 = 9 + # Protocols + MAPIv9 = 9 - MONETDB_MEROVINGIAN = "merovingian" - MONETDB_MSERVER = "monetdb" + MONETDB_MEROVINGIAN = "merovingian" + MONETDB_MSERVER = "monetdb" - MEROVINGIAN_MAX_ITERATIONS = 10 + MEROVINGIAN_MAX_ITERATIONS = 10 - # enable debug output - @@DEBUG = false + # enable debug output + @@DEBUG = false - # hour in seconds, used for timezone calculation - @@HOUR = 3600 + # hour in seconds, used for timezone calculation + @@HOUR = 3600 - # maximum size (in bytes) for a monetdb message to be sent - @@MAX_MESSAGE_SIZE = 8190 + # maximum size (in bytes) for a monetdb message to be sent + @@MAX_MESSAGE_SIZE = 8190 - # endianness of a message sent to the server - @@CLIENT_ENDIANNESS = "BIG" + # endianness of a message sent to the server + @@CLIENT_ENDIANNESS = "BIG" - # MAPI protocols supported by the driver - @@SUPPORTED_PROTOCOLS = [ MonetDBConnection::MAPIv9 ] + # MAPI protocols supported by the driver + @@SUPPORTED_PROTOCOLS = [ MonetDBConnection::MAPIv9 ] - attr_reader :socket, :auto_commit, :transactions, :lang + attr_reader :socket, :auto_commit, :transactions, :lang - # Instantiates a new MonetDBConnection object - # * user: username (default is monetdb) - # * passwd: password (default is monetdb) - # * lang: language (default is sql) - # * host: server hostname or ip (default is localhost) - # * port: server port (default is 50000) + # Instantiates a new MonetDBConnection object + # * user: username (default is monetdb) + # * passwd: password (default is monetdb) + # * lang: language (default is sql) + # * host: server hostname or ip (default is localhost) + # * port: server port (default is 50000) - 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 + 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 + @client_endianness = @@CLIENT_ENDIANNESS - @auth_iteration = 0 - @connection_established = false + @auth_iteration = 0 + @connection_established = false - @transactions = MonetDBTransaction.new # handles a pool of transactions (generates and keeps track of savepoints) + @transactions = MonetDBTransaction.new # handles a pool of transactions (generates and keeps track of savepoints) - if @@DEBUG == true - require 'logger' - end + if @@DEBUG == true + require 'logger' + end - if @lang[0, 3] == 'sql' - @lang = "sql" - end + if @lang[0, 3] == 'sql' + @lang = "sql" + end - end + end - # Connect to the database, creates a new socket - def connect(db_name = 'demo', auth_type = 'SHA1') - @database = db_name - @auth_type = auth_type + # Connect to the database, creates a new socket + 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 == MonetDBConnection::LANG_SQL - set_timezone - set_reply_size - end - true - end - false - end + @socket = TCPSocket.new(@host, @port.to_i) + if real_connect + if @lang == MonetDBConnection::LANG_SQL + set_timezone + set_reply_size + end + true + end + false + end - # perform a real connection; retrieve challenge, proxy through merovinginan, build challenge and set the timezone - def real_connect + # perform a real connection; retrieve challenge, proxy through merovinginan, build challenge and set the timezone + def real_connect - server_challenge = retrieve_server_challenge() - 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 @@SUPPORTED_PROTOCOLS.include?(@protocol) == false - raise MonetDBProtocolError, "Protocol not supported. The current implementation of ruby-monetdb works with MAPI protocols #{@@SUPPORTED_PROTOCOLS} only." - end - @pwhash = server_challenge.split(':')[5] - else - raise MonetDBConnectionError, "Error: server returned an empty challenge string." - end + server_challenge = retrieve_server_challenge() + 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 @@SUPPORTED_PROTOCOLS.include?(@protocol) == false + raise MonetDBProtocolError, "Protocol not supported. The current implementation of ruby-monetdb works with MAPI protocols #{@@SUPPORTED_PROTOCOLS} only." + end + @pwhash = server_challenge.split(':')[5] + else + raise MonetDBConnectionError, "Error: server returned an empty challenge string." + end - # The server supports only RIPMED160 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" - raise MonetDBConnectionError, auth.upcase + " " + ": algorithm not supported by ruby-monetdb." - end - end + # The server supports only RIPMED160 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" + raise MonetDBConnectionError, auth.upcase + " " + ": algorithm not supported by ruby-monetdb." + end + end - reply = build_auth_string_v9(@auth_type, salt, @database) + reply = build_auth_string_v9(@auth_type, salt, @database) - if @socket != nil - @connection_established = true + if @socket != nil + @connection_established = true - send(reply) - monetdb_auth = receive + send(reply) + monetdb_auth = receive - if monetdb_auth.length == 0 - # auth succeeded - true - else - if monetdb_auth[0].chr == MonetDBConnection::MSG_REDIRECT - #redirection + if monetdb_auth.length == 0 + # auth succeeded + true + else + if monetdb_auth[0].chr == MonetDBConnection::MSG_REDIRECT + #redirection - redirects = [] # store a list of possible redirects + 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 + 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 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 == MonetDBConnection::MONETDB_MEROVINGIAN - if @auth_iteration <= MonetDBConnection::MEROVINGIAN_MAX_ITERATIONS - @auth_iteration += 1 - real_connect - else - raise MonetDBConnectionError, "Merovingian: too many iterations while proxying." - end - elsif server_name == MonetDBConnection::MONETDB_MSERVER - begin - @socket.close - rescue - raise MonetDBConnectionError, "I/O error while closing connection to #{@socket}" - end - # reinitialize a connection - @host = host - @port = port + if server_name == MonetDBConnection::MONETDB_MEROVINGIAN + if @auth_iteration <= MonetDBConnection::MEROVINGIAN_MAX_ITERATIONS + @auth_iteration += 1 + real_connect + else + raise MonetDBConnectionError, "Merovingian: too many iterations while proxying." + end + elsif server_name == MonetDBConnection::MONETDB_MSERVER + 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 == MonetDBConnection::MSG_INFO - raise MonetDBConnectionError, monetdb_auth - end - end - end - end + connect(database, @auth_type) + else + @connection_established = false + raise MonetDBConnectionError, monetdb_auth + end + elsif monetdb_auth[0].chr == MonetDBConnection::MSG_INFO + raise MonetDBConnectionError, monetdb_auth + end + end + end + end - def savepoint - @transactions.savepoint - end + def savepoint + @transactions.savepoint + end - # Formats a <i>command</i> string so that it can be parsed by the server - def format_command(x) - return "X" + x + "\n" - end + # Formats a <i>command</i> string so that it can be parsed by the server + def format_command(x) + return "X" + x + "\n" + end - # send an 'export' command to the server - def set_export(id, idx, offset) - send(format_command("export " + id.to_s + " " + idx.to_s + " " + offset.to_s )) - end + # send an 'export' command to the server + def set_export(id, idx, offset) + send(format_command("export " + id.to_s + " " + idx.to_s + " " + offset.to_s )) + end - # send a 'reply_size' command to the server - def set_reply_size - send(format_command(("reply_size " + MonetDBConnection::REPLY_SIZE))) + # send a 'reply_size' command to the server + def set_reply_size + send(format_command(("reply_size " + MonetDBConnection::REPLY_SIZE))) - response = receive + response = receive - if response == MonetDBConnection::MSG_PROMPT - true - elsif response[0] == MonetDBConnection::MSG_INFO - raise MonetDBCommandError, "Unable to set reply_size: #{response}" - end + if response == MonetDBConnection::MSG_PROMPT + true + elsif response[0] == MonetDBConnection::MSG_INFO + raise MonetDBCommandError, "Unable to set reply_size: #{response}" + end - end + end - def set_output_seq - send(format_command("output seq")) - end + def set_output_seq + send(format_command("output seq")) + end - # Disconnect from server - def disconnect() - if @connection_established - begin - @socket.close - rescue => e - $stderr.print e - end - else - raise MonetDBConnectionError, "No connection established." - end - end + # Disconnect from server + def disconnect() + if @connection_established + begin + @socket.close + rescue => e + $stderr.print e + end + else + raise MonetDBConnectionError, "No connection established." + end + end - # send data to a monetdb5 server instance and returns server's response - def send(data) - encode_message(data).each do |m| - @socket.write(m) - end - end + # send data to a monetdb5 server instance and returns server's response + def send(data) + encode_message(data).each do |m| + @socket.write(m) + end + end - # receive data from a monetdb5 server instance - def receive - is_final, chunk_size = recv_decode_hdr + # receive data from a monetdb5 server instance + 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 + 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) + 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 + 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 + return data + end - # - # Builds and authentication string given the parameters submitted by the user (MAPI protocol v9). - # - 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) + # + # Builds and authentication string given the parameters submitted by the user (MAPI protocol v9). + # + 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 + 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) + 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 + 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 - # builds a message to be sent to the server - def encode_message(msg = "") - message = Array.new - data = "" + # builds a message to be sent to the server + def encode_message(msg = "") + message = Array.new + data = "" - hdr = 0 # package header - pos = 0 - is_final = false # last package in the stream + 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 + 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 + 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.length << 1) | last_bit].pack('v') - message << hdr + data.to_s # Short Little Endian Encoding - end + message << hdr + data.to_s # Short Little Endian Encoding + end - message.freeze # freeze and return the encode message - end + message.freeze # freeze and return the encode message + end - # Used as the first step in the authentication phase; retrieves a challenge string from the server. - def retrieve_server_challenge() - server_challenge = receive - end + # Used as the first step in the authentication phase; retrieves a challenge string from the server. + def retrieve_server_challenge() + server_challenge = receive + end - # reads and decodes the header of a server message - def recv_decode_hdr() - if @socket != nil - fb = @socket.recv(1) - sb = @socket.recv(1) + # reads and decodes the header of a server message + def recv_decode_hdr() + if @socket != nil + fb = @socket.recv(1) + sb = @socket.recv(1) - # Use exceptions 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 + # Use exceptions 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) + chunk_size = (sb << 7) | (fb >> 1) - is_final = false - if ( (fb & 1) == 1 ) - is_final = true + 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 + end + # return the size of the chunk (in bytes) + return is_final, chunk_size + else + raise MonetDBSocketError, "Error while receiving data\n" + end + end - # Sets the time zone according to the Operating System settings - def set_timezone() - tz = Time.new - tz_offset = tz.gmt_offset / @@HOUR + # Sets the time zone according to the Operating System settings + def set_timezone() + tz = Time.new + tz_offset = tz.gmt_offset / @@HOUR - # verify minute count! - if tz_offset <= -10 - tz_offset = "'" + tz_offset.to_s + ":00'" - elsif tz_offset < 0 - tz_offset = -tz_offset - tz_offset = "'-0" + tz_offset.to_s + ":00'" - elsif tz_offset <= 9 - 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;" + # verify minute count! + if tz_offset <= -10 + tz_offset = "'" + tz_offset.to_s + ":00'" + elsif tz_offset < 0 + tz_offset = -tz_offset + tz_offset = "'-0" + tz_offset.to_s + ":00'" + elsif tz_offset <= 9 + 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 + # Perform the query directly within the method + send(query_tz) + response = receive - if response == MonetDBConnection::MSG_PROMPT - true - elsif response[0].chr == MonetDBConnection::MSG_INFO - raise MonetDBQueryError, response - end - end + if response == MonetDBConnection::MSG_PROMPT + true + elsif response[0].chr == MonetDBConnection::MSG_INFO + raise MonetDBQueryError, response + end + end - # Turns auto commit on/off - def set_auto_commit(flag=true) - if flag == false - ac = " 0" - else - ac = " 1" - end + # Turns auto commit on/off + def set_auto_commit(flag=true) + if flag == false + ac = " 0" + else + ac = " 1" + end - send(format_command("auto_commit " + ac)) + send(format_command("auto_commit " + ac)) - response = receive - if response == MonetDBConnection::MSG_PROMPT - @auto_commit = flag - elsif response[0].chr == MonetDBConnection::MSG_INFO - raise MonetDBCommandError, response - return - end + response = receive + if response == MonetDBConnection::MSG_PROMPT + @auto_commit = flag + elsif response[0].chr == MonetDBConnection::MSG_INFO + raise MonetDBCommandError, response + return + end - end + end - # Check the auto commit status (on/off) - def auto_commit? - @auto_commit - end + # Check the auto commit status (on/off) + def auto_commit? + @auto_commit + end - # Check if monetdb is running behind the merovingian proxy and forward the connection in case - def merovingian? - if @server_name.downcase == MonetDBConnection::MONETDB_MEROVINGIAN - true - else - false - end - end + # Check if monetdb is running behind the merovingian proxy and forward the connection in case + def merovingian? + if @server_name.downcase == MonetDBConnection::MONETDB_MEROVINGIAN + true + else + false + end + end - def mserver? - if @server_name.downcase == MonetDBConnection::MONETDB_MSERVER - true - else - false - end - end + def mserver? + if @server_name.downcase == MonetDBConnection::MONETDB_MSERVER + true + else + false + end + end end # handles transactions and savepoints. Can be used to simulate nested transactions. class MonetDBTransaction - SAVEPOINT_STRING = "monetdbsp" + SAVEPOINT_STRING = "monetdbsp" - def initialize - @id = 0 - @savepoint = "" - end + def initialize + @id = 0 + @savepoint = "" + end - def savepoint - @savepoint = SAVEPOINT_STRING + @id.to_s - end + def savepoint + @savepoint = SAVEPOINT_STRING + @id.to_s + end - def release - prev_id - end + def release + prev_id + end - def save - next_id - end + def save + next_id + end - private - def next_id - @id += 1 - end + private + def next_id + @id += 1 + end - def prev_id - @id -= 1 - end + def prev_id + @id -= 1 + end end
--- a/lib/MonetDBData.rb +++ b/lib/MonetDBData.rb @@ -16,368 +16,368 @@ require_relative 'MonetDBConnection' require 'logger' class MonetDBData - @@DEBUG = false + @@DEBUG = false - def initialize(connection) - @connection = connection - @lang = @connection.lang + def initialize(connection) + @connection = connection + @lang = @connection.lang - # Structure containing the header+results set for a fired Q_TABLE query - @header = [] - @query = {} + # Structure containing the header+results set for a fired Q_TABLE query + @header = [] + @query = {} - @record_set = [] - @index = 0 # Position of the last returned record + @record_set = [] + @index = 0 # Position of the last returned record - @row_count = 0 - @row_offset = 10 - @row_index = Integer(MonetDBConnection::REPLY_SIZE) - end + @row_count = 0 + @row_offset = 10 + @row_index = Integer(MonetDBConnection::REPLY_SIZE) + end - # Fire a query and return the server response - def execute(q) - # fire a query and get ready to receive the data - @connection.send(format_query(q)) - data = @connection.receive + # Fire a query and return the server response + def execute(q) + # fire a query and get ready to receive the data + @connection.send(format_query(q)) + data = @connection.receive - return if data == nil + return if data == nil - record_set = "" # temporarily store retrieved rows - record_set = receive_record_set(data) + record_set = "" # temporarily store retrieved rows + record_set = receive_record_set(data) - if (@lang == MonetDBConnection::LANG_SQL) - rows = receive_record_set(data) - # the fired query is a SELECT; store and return the whole record set - if @action == MonetDBConnection::Q_TABLE - @header = parse_header_table(@header) - @header.freeze + if (@lang == MonetDBConnection::LANG_SQL) + rows = receive_record_set(data) + # the fired query is a SELECT; store and return the whole record set + if @action == MonetDBConnection::Q_TABLE + @header = parse_header_table(@header) + @header.freeze - if @row_index.to_i < @row_count.to_i - block_rows = "" - while next_block - data = @connection.receive - block_rows += receive_record_set(data) - end - record_set += block_rows - end - end + if @row_index.to_i < @row_count.to_i + block_rows = "" + while next_block + data = @connection.receive + block_rows += receive_record_set(data) + end + record_set += block_rows + end + end - # ruby string management seems to not properly understand the MSG_PROMPT escape character. - # In order to avoid data loss the @record_set array is built once that all tuples have been retrieved - @record_set = record_set.split("\t]\n") + # ruby string management seems to not properly understand the MSG_PROMPT escape character. + # In order to avoid data loss the @record_set array is built once that all tuples have been retrieved + @record_set = record_set.split("\t]\n") - if @record_set.length != @query['rows'].to_i - raise MonetDBQueryError, "Warning: Query #{@query['id']} declared to result in #{@query['rows']} but #{@record_set.length} returned instead" - end - end - @record_set.freeze - end + if @record_set.length != @query['rows'].to_i + raise MonetDBQueryError, "Warning: Query #{@query['id']} declared to result in #{@query['rows']} but #{@record_set.length} returned instead" + end + end + @record_set.freeze + end - # Returns the record set entries hashed by column name ordered by column position - def fetch_all_as_column_hash - columns = {} - @header["columns_name"].each do |col_name| - columns[col_name] = fetch_by_column_name(col_name) - end + # Returns the record set entries hashed by column name ordered by column position + def fetch_all_as_column_hash + columns = {} + @header["columns_name"].each do |col_name| + columns[col_name] = fetch_by_column_name(col_name) + end - return columns - end + return columns + end - # returns a record hash (i.e: { id: 1, name: "John Doe", age: 42 } ) - def fetch_hash - return false if @index >= @query['rows'].to_i + # returns a record hash (i.e: { id: 1, name: "John Doe", age: 42 } ) + def fetch_hash + return false if @index >= @query['rows'].to_i - record_hash = record_hash(parse_tuple(@record_set[@index])) - @index += 1 - return record_hash - end + record_hash = record_hash(parse_tuple(@record_set[@index])) + @index += 1 + return record_hash + end - # loops through all the hashes of the records and yields them to a given block - def each_record_as_hash - @record_set.each do |record| - parsed_record = parse_tuple(record) - yield(record_hash(parsed_record)) - end - end + # loops through all the hashes of the records and yields them to a given block + def each_record_as_hash + @record_set.each do |record| + parsed_record = parse_tuple(record) + yield(record_hash(parsed_record)) + end + end - # Returns the values for the column 'field' - def fetch_by_column_name(column_name="") - position = @header["columns_order"].fetch(column_name) + # Returns the values for the column 'field' + def fetch_by_column_name(column_name="") + position = @header["columns_order"].fetch(column_name) - column_values = [] - @record_set.each do |row| - column_values << parse_tuple(row)[position] - end + column_values = [] + @record_set.each do |row| + column_values << parse_tuple(row)[position] + end - return column_values - end + return column_values + end - # fetches a single record, updates the iterator index - def fetch - return false if @index >= @query['rows'].to_i + # fetches a single record, updates the iterator index + def fetch + return false if @index >= @query['rows'].to_i - result = parse_tuple(@record_set[@index]) - @index += 1 + result = parse_tuple(@record_set[@index]) + @index += 1 - return result - end + return result + end - # resets the internal iterator index used by fetch and fetch_hash - def reset_index - @index = 0 - end + # resets the internal iterator index used by fetch and fetch_hash + def reset_index + @index = 0 + end - # loops through all records and yields to a given block parameter - def each_record - raise MonetDBDataError, "There is no record set currently available" unless @query['type'] == MonetDBConnection::Q_TABLE - @record_set.each { |record| yield(parse_tuple(record)) } - end + # loops through all records and yields to a given block parameter + def each_record + raise MonetDBDataError, "There is no record set currently available" unless @query['type'] == MonetDBConnection::Q_TABLE + @record_set.each { |record| yield(parse_tuple(record)) } + end - # Cursor method that returns all the records - def fetch_all - result = [] - each_record do |record| - result.push(record) - end - return result - end + # Cursor method that returns all the records + def fetch_all + result = [] + each_record do |record| + result.push(record) + end + return result + end - # Returns the number of rows in the record set - def num_rows - return @query['rows'].to_i - end + # Returns the number of rows in the record set + def num_rows + return @query['rows'].to_i + end - # Returns the number of fields in the record set - def num_fields - return @query['columns'].to_i - end + # Returns the number of fields in the record set + def num_fields + return @query['columns'].to_i + end - # Returns the (ordered) name of the columns' tables in the record set - def table_name_fields - return @header['table_name'] - end + # Returns the (ordered) name of the columns' tables in the record set + def table_name_fields + return @header['table_name'] + end - # Returns the (ordered) name of the columns in the record set - def name_fields - return @header['columns_name'] - end + # Returns the (ordered) name of the columns in the record set + def name_fields + return @header['columns_name'] + end - # Returns the (ordered) name of the columns in the record set - def type_fields - return @header['columns_type'] - end + # Returns the (ordered) name of the columns in the record set + def type_fields + return @header['columns_type'] + end - # =================== - private - # =================== + # =================== + private + # =================== - # store block of data, parse it and store it. - def receive_record_set(response) - rows = "" - response.each_line do |row| - case row[0] - when MonetDBConnection::MSG_QUERY then parse_query(row) - when MonetDBConnection::MSG_INFO then raise MonetDBQueryError, row - when MonetDBConnection::MSG_SCHEMA_HEADER then @header << row - when MonetDBConnection::MSG_TUPLE then rows += row - when MonetDBConnection::MSG_PROMPT then return rows - end - end - return rows # return an array of unparsed tuples - end + # store block of data, parse it and store it. + def receive_record_set(response) + rows = "" + response.each_line do |row| + case row[0] + when MonetDBConnection::MSG_QUERY then parse_query(row) + when MonetDBConnection::MSG_INFO then raise MonetDBQueryError, row + when MonetDBConnection::MSG_SCHEMA_HEADER then @header << row + when MonetDBConnection::MSG_TUPLE then rows += row + when MonetDBConnection::MSG_PROMPT then return rows + end + end + return rows # return an array of unparsed tuples + end - def parse_query(row) - case row[1] - when MonetDBConnection::Q_TABLE - @action = MonetDBConnection::Q_TABLE - @query = parse_header_query(row) - @query.freeze - @row_count = @query['rows'].to_i #total number of rows in table - when MonetDBConnection::Q_BLOCK - @action = MonetDBConnection::Q_BLOCK # strip the block header from data - @block = parse_header_query(row) - when MonetDBConnection::Q_TRANSACTION - @action = MonetDBConnection::Q_TRANSACTION - when MonetDBConnection::Q_CREATE - @action = MonetDBConnection::Q_CREATE - end - end + def parse_query(row) + case row[1] + when MonetDBConnection::Q_TABLE + @action = MonetDBConnection::Q_TABLE + @query = parse_header_query(row) + @query.freeze + @row_count = @query['rows'].to_i #total number of rows in table + when MonetDBConnection::Q_BLOCK + @action = MonetDBConnection::Q_BLOCK # strip the block header from data + @block = parse_header_query(row) + when MonetDBConnection::Q_TRANSACTION + @action = MonetDBConnection::Q_TRANSACTION + when MonetDBConnection::Q_CREATE + @action = MonetDBConnection::Q_CREATE + end + end - def record_hash(record) - result = {} + def record_hash(record) + result = {} - @header["columns_name"].each do |column_name| - position = @header["columns_order"].fetch(column_name) - result[column_name] = record[position] - end + @header["columns_name"].each do |column_name| + position = @header["columns_order"].fetch(column_name) + result[column_name] = record[position] + end - return result - end + return result + end - def next_block - if @row_index == @row_count - return false - else - # The increment step is small to better deal with ruby socket's performance. - # For larger values of the step performance drop; - # - @row_offset = [@row_offset, (@row_count - @row_index)].min + def next_block + if @row_index == @row_count + return false + else + # The increment step is small to better deal with ruby socket's performance. + # For larger values of the step performance drop; + # + @row_offset = [@row_offset, (@row_count - @row_index)].min - # export offset amount - @connection.set_export(@query['id'], @row_index.to_s, @row_offset.to_s) - @row_index += @row_offset - @row_offset += 1 - end - return true + # export offset amount + @connection.set_export(@query['id'], @row_index.to_s, @row_offset.to_s) + @row_index += @row_offset + @row_offset += 1 + end + return true - end + end - # Formats a query <i>string</i> so that it can be parsed by the server - def format_query(q) - if @lang == MonetDBConnection::LANG_SQL - return "s" + q + "\n;" - else - raise LanguageNotSupported, @lang - end - end + # Formats a query <i>string</i> so that it can be parsed by the server + def format_query(q) + if @lang == MonetDBConnection::LANG_SQL + return "s" + q + "\n;" + else + raise LanguageNotSupported, @lang + end + end - # parse one tuple as returned from the server - def parse_tuple(tuple) - fields = [] - # remove trailing "[" - tuple = tuple.gsub(/^\[\s+/,'') - tuple.split(/,\t/).each_with_index do |field, index| - field_value = convert_type(field, index) - fields << field_value - end + # parse one tuple as returned from the server + def parse_tuple(tuple) + fields = [] + # remove trailing "[" + tuple = tuple.gsub(/^\[\s+/,'') + tuple.split(/,\t/).each_with_index do |field, index| + field_value = convert_type(field, index) + fields << field_value + end - return fields.freeze - end + return fields.freeze + end - # converts the given value the correct type - def convert_type(value, index) - return nil if "NULL" == value.upcase - return case type_fields.values[index] - when "int", "tinyint", "smallint", "bigint", "hugeint" then value.to_i - when "double", "real", "decimal" then value.to_f - when "boolean" then value.downcase == true - when "date" then Date.parse(value) - when "time" then Time.parse(value, Time.new("2000-01-01")) - when "timestamp" then DateTime.parse(value) - when "timestamptz" then DateTime.parse(value) - else value.gsub(/\\/, '').gsub(/^"/,'').gsub(/"$/,'').gsub(/\"/, '') - end - end + # converts the given value the correct type + def convert_type(value, index) + return nil if "NULL" == value.upcase + return case type_fields.values[index] + when "int", "tinyint", "smallint", "bigint", "hugeint" then value.to_i + when "double", "real", "decimal" then value.to_f + when "boolean" then value.downcase == true + when "date" then Date.parse(value) + when "time" then Time.parse(value, Time.new("2000-01-01")) + when "timestamp" then DateTime.parse(value) + when "timestamptz" then DateTime.parse(value) + else value.gsub(/\\/, '').gsub(/^"/,'').gsub(/"$/,'').gsub(/\"/, '') + end + end - # Parses a query header and returns information about the query. - def parse_header_query(row) - type = row[1].chr - if type == MonetDBConnection::Q_TABLE - # Performing a SELECT: store information about the table size, query id, total number of records and returned. - id = row.split(' ')[1] - rows = row.split(' ')[2] - columns = row.split(' ')[3] - returned = row.split(' ')[4] + # Parses a query header and returns information about the query. + def parse_header_query(row) + type = row[1].chr + if type == MonetDBConnection::Q_TABLE + # Performing a SELECT: store information about the table size, query id, total number of records and returned. + id = row.split(' ')[1] + rows = row.split(' ')[2] + columns = row.split(' ')[3] + returned = row.split(' ')[4] - header = { "id" => id, "type" => type, "rows" => rows, "columns" => columns, "returned" => returned } - elsif type == MonetDBConnection::Q_BLOCK - # processing block header + header = { "id" => id, "type" => type, "rows" => rows, "columns" => columns, "returned" => returned } + elsif type == MonetDBConnection::Q_BLOCK + # processing block header - id = row.split(' ')[1] - columns = row.split(' ')[2] - remains = row.split(' ')[3] - offset = row.split(' ')[4] + id = row.split(' ')[1] + columns = row.split(' ')[2] + remains = row.split(' ')[3] + offset = row.split(' ')[4] - header = { "id" => id, "type" => type, "remains" => remains, "columns" => columns, "offset" => offset } - else - header = {"type" => type} - end + header = { "id" => id, "type" => type, "remains" => remains, "columns" => columns, "offset" => offset } + else + header = {"type" => type} + end - return header.freeze - end + return header.freeze + end - # Parse escaped strings between double quotes - def parse_header_table_values(line, start, stop, results) - i = start - inString = false - escaped = false + # Parse escaped strings between double quotes + def parse_header_table_values(line, start, stop, results) + i = start + inString = false + escaped = false - until i == stop do - case line[i] - when '\\' - escaped = !escaped - when '"' - if !inString - inString = true - elsif !escaped - inString = false - end - escaped = false - when ',' - if !inString # && line[i + 1] == '\t' - if line[start] == '"' # Don't include the leading " in the column attribute - start += 1 - end - if line[i - 1] == '"' - next_end = 2 - else - next_end = 1 - end - results.push(line[start..i - next_end]) - i += 1 - start = i + 1 - end - escaped = false - else - escaped = false - end - i += 1 - end - if line[start] == '"' # Don't include the leading " in the column attribute - start += 1 - end - if line[stop] == '"' - next_end = 1 - else - next_end = 0 - end - results.push(line[start..stop - next_end]) - end + until i == stop do + case line[i] + when '\\' + escaped = !escaped + when '"' + if !inString + inString = true + elsif !escaped + inString = false + end + escaped = false + when ',' + if !inString # && line[i + 1] == '\t' + if line[start] == '"' # Don't include the leading " in the column attribute + start += 1 + end + if line[i - 1] == '"' + next_end = 2 + else + next_end = 1 + end + results.push(line[start..i - next_end]) + i += 1 + start = i + 1 + end + escaped = false + else + escaped = false + end + i += 1 + end + if line[start] == '"' # Don't include the leading " in the column attribute + start += 1 + end + if line[stop] == '"' + next_end = 1 + else + next_end = 0 + end + results.push(line[start..stop - next_end]) + end - # Parses a Q_TABLE header and returns information about the schema. - def parse_header_table(header_t) - if @query["type"] == MonetDBConnection::Q_TABLE - if header_t != nil + # Parses a Q_TABLE header and returns information about the schema. + def parse_header_table(header_t) + if @query["type"] == MonetDBConnection::Q_TABLE + if header_t != nil - name_t = Array.new - parse_header_table_values(header_t[0], 2, header_t[0].length - 15, name_t) + name_t = Array.new + parse_header_table_values(header_t[0], 2, header_t[0].length - 15, name_t) - name_cols = Array.new - parse_header_table_values(header_t[1], 2, header_t[1].length - 9, name_cols) + name_cols = Array.new + parse_header_table_values(header_t[1], 2, header_t[1].length - 9, name_cols) - type_cols = { } - type_cols_array = Array.new - parse_header_table_values(header_t[2], 2, header_t[2].length - 9, type_cols_array) - type_cols_array.each_with_index do |col, i| - type_cols[name_cols[i]] = col - end + type_cols = { } + type_cols_array = Array.new + parse_header_table_values(header_t[2], 2, header_t[2].length - 9, type_cols_array) + type_cols_array.each_with_index do |col, i| + type_cols[name_cols[i]] = col + end - length_cols = { } - length_cols_array = Array.new - parse_header_table_values(header_t[3], 2, header_t[3].length - 11, length_cols_array) - length_cols_array.each_with_index do |col, i| - length_cols[name_cols[i]] = col - end + length_cols = { } + length_cols_array = Array.new + parse_header_table_values(header_t[3], 2, header_t[3].length - 11, length_cols_array) + length_cols_array.each_with_index do |col, i| + length_cols[name_cols[i]] = col + end - columns_order = {} - name_cols.each_with_index do |col, i| - columns_order[col] = i - end + columns_order = {} + name_cols.each_with_index do |col, i| + columns_order[col] = i + end - return {"table_name" => name_t, "columns_name" => name_cols, "columns_type" => type_cols, - "columns_length" => length_cols, "columns_order" => columns_order}.freeze - end - end - end + return {"table_name" => name_t, "columns_name" => name_cols, "columns_type" => type_cols, + "columns_length" => length_cols, "columns_order" => columns_order}.freeze + end + end + end end
--- a/lib/MonetDBExceptions.rb +++ b/lib/MonetDBExceptions.rb @@ -7,38 +7,38 @@ # Exception classes for the ruby-monetdb driver class MonetDBQueryError < StandardError - def initialize(e) - $stderr.puts e - end + def initialize(e) + $stderr.puts e + end end class MonetDBDataError < StandardError - def initialize(e) - $stderr.puts e - end + def initialize(e) + $stderr.puts e + end end class MonetDBCommandError < StandardError - def initialize(e) - $stderr.puts e - end + def initialize(e) + $stderr.puts e + end end class MonetDBConnectionError < StandardError - def initialize(e) - $stderr.puts e - end + def initialize(e) + $stderr.puts e + end end class MonetDBSocketError < StandardError - def initialize(e) - $stderr.puts e - end + def initialize(e) + $stderr.puts e + end end class MonetDBProtocolError < StandardError - def initialize(e) - $stderr.puts e - end + def initialize(e) + $stderr.puts e + end end
--- a/lib/hasher.rb +++ b/lib/hasher.rb @@ -13,25 +13,25 @@ class Hasher # method = "SHA1" or "MD5" # pwd = Password def initialize(method, pwd) - if (method.upcase == "SHA1") - @hashfunc = Digest::SHA1.new - @hashname = method.upcase - elsif (method.upcase == "SHA256") + if (method.upcase == "SHA1") + @hashfunc = Digest::SHA1.new + @hashname = method.upcase + elsif (method.upcase == "SHA256") @hashfunc = Digest::SHA256.new @hashname = method.upcase - elsif (method.upcase == "SHA384") + elsif (method.upcase == "SHA384") @hashfunc = Digest::SHA384.new @hashname = method.upcase - elsif (method.upcase == "SHA512") + elsif (method.upcase == "SHA512") @hashfunc = Digest::SHA512.new @hashname = method.upcase - else + else # default to MD5 - @hashfunc = Digest::MD5.new - @hashname = "MD5" - end - @pwd = pwd - end + @hashfunc = Digest::MD5.new + @hashname = "MD5" + end + @pwd = pwd + end def hashname
--- a/monetdb-sql.gemspec +++ b/monetdb-sql.gemspec @@ -1,17 +1,17 @@ Gem::Specification.new do |s| - s.required_ruby_version = '>= 1.8.0' - s.name = %q{monetdb-sql} - s.version = "1.2" - s.date = %q{2020-10-20} - s.authors = ["G Modena", "R Koopmanschap"] - s.email = "info@monetdb.org" - s.license = "MPL-2.0" - s.summary = %q{Pure Ruby database driver for MonetDB/SQL} - s.homepage = %q{http://www.monetdb.org/} - s.description = %q{Pure Ruby database driver for the MonetDB/SQL columnar database management system} - s.files = ["lib/MonetDB.rb", "lib/MonetDBConnection.rb", "lib/MonetDBData.rb", "lib/MonetDBExceptions.rb", "lib/hasher.rb"] - s.require_path = './lib' - # placeholder project to avoid warning about not having a rubyforge_project - s.rubyforge_project = "nowarning" + s.required_ruby_version = '>= 1.8.0' + s.name = %q{monetdb-sql} + s.version = "1.2" + s.date = %q{2020-10-20} + s.authors = ["G Modena", "R Koopmanschap"] + s.email = "info@monetdb.org" + s.license = "MPL-2.0" + s.summary = %q{Pure Ruby database driver for MonetDB/SQL} + s.homepage = %q{http://www.monetdb.org/} + s.description = %q{Pure Ruby database driver for the MonetDB/SQL columnar database management system} + s.files = ["lib/MonetDB.rb", "lib/MonetDBConnection.rb", "lib/MonetDBData.rb", "lib/MonetDBExceptions.rb", "lib/hasher.rb"] + s.require_path = './lib' + # placeholder project to avoid warning about not having a rubyforge_project + s.rubyforge_project = "nowarning" end