changeset 0:aab36be83762

Created a new repository for the Ruby driver for MonetDB. The Ruby files are based on the files in changeset 5db87c0b5920 in the default branch of the MonetDB repository.
author Sjoerd Mullender <sjoerd@acm.org>
date Wed, 02 Mar 2016 14:50:19 +0100 (2016-03-02)
parents
children 4e3a4f2a58ff
files .hgignore Changelog Makefile TODO debian/changelog debian/compat debian/control debian/copyright debian/rules debian/source/format doc/readme.txt lib/MonetDB.rb lib/MonetDBConnection.rb lib/MonetDBData.rb lib/MonetDBExceptions.rb lib/example.rb lib/hasher.rb monetdb-sql.gemspec rubygem-monetdb-sql.spec
diffstat 19 files changed, 1409 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,3 @@
+syntax: glob
+
+*.gem
new file mode 100644
--- /dev/null
+++ b/Changelog
@@ -0,0 +1,38 @@
+* Tue 30 Jun 2009 22:14:38 CEST
+Added test cases for the standalone driver (ruby unit test).
+
+* Thu 25 Jun 2009 17:31:02 CEST
+Fixed a major bug that resulted in data corruption;
+MAPI implementation code cleanup and bug fixes;
+Support to ruby 1.9;
+Both the standalone driver and activerecord now support transactions; nested transactions
+are simulated via savepoints in activerecord.
+Added a Rakefile and script to run activerecord unit test suite.
+Type conversion in the standalone driver is now performed after data has been retrieved and can be executed on single fields.
+
+* Mon 25 May 2009 17:52:01 CEST
+Imported last week changes (protocol 9 support, parametrized the connection options in the activerecord adapter, fixed a bug in the auth protocol v8).
+Fixed a bug in the mapi protocol that resulted in data loss (wrong handling of TRANSACTIONS).
+Began to port the activerecord test suite to monetdb (not all test cases can be performed).
+Removed an unneeded file ('lib/MonetDBStatement.rb') from the gemspec of the standalone driver (the feature will be moved to HEAD).
+Began to port the driver to ruby 1.9.
+Removed *.gem files from cvs.
+
+* Mon 18 May 2009 15:22:31 CEST
+Fixed bugs that prevented the correct working of activerecords' migration;
+The activerecord connector now supports insertion, update and alter table operations;
+Type casting is working in activerecord;
+Added a rubygem and rakefile for activerecord-monetdb-adapter;
+Added a new usage example for activerecord to the README file;
+Added an example directory to the cvs tree;
+The driver now correctly works with merovingian.
+
+
+* Sat  9 May 2009 15:58:36 CEST
+Fixed bugs with the query processing in the standalone driver;
+Added INSERT and UPDATE methods in the activerecord connector.
+
+* Thu  7 May 2009 17:03:01 CEST
+
+Added a check against the protocol version during authentication;
+Imported the activerecord code (available under adapter/).
new file mode 100644
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,9 @@
+VERSION = 1.0
+
+monetdb-sql-$(VERSION).gem: monetdb-sql.gemspec \
+		lib/MonetDB.rb lib/MonetDBConnection.rb \
+		lib/MonetDBData.rb lib/MonetDBExceptions.rb lib/hasher.rb
+	gem build $<
+
+clean:
+	rm -f *.gem
new file mode 100644
--- /dev/null
+++ b/TODO
@@ -0,0 +1,5 @@
+* test and improve utf8 and type conversion
+
+* documentation cleanup
+
+* OSM on rails demo (slowed down due to third party plugins requirements)
new file mode 100644
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,5 @@
+ruby-monetdb-sql (1.0-1) unstable; urgency=low
+
+  * The Ruby interface to MonetDB is now a separate package.
+
+ -- Sjoerd Mullender <sjoerd@acm.org>  Wed, 02 Mar 2016 12:27:21 +0100
new file mode 100644
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1,1 @@
+9
new file mode 100644
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,29 @@
+Source: ruby-monetdb-sql
+Section: ruby
+Priority: optional
+Maintainer: MonetDB BV <info@monetdb.org>
+Build-Depends: debhelper (>= 9~),
+               gem2deb
+Standards-Version: 3.9.6
+Vcs-Hg: http://dev.monetdb.org/hg/monetdb-ruby/
+Vcs-Browser: http://dev.monetdb.org/hg/monetdb-ruby/
+Homepage: http://www.monetdb.org/
+Testsuite: autopkgtest-pkg-ruby
+XS-Ruby-Versions: all
+
+Package: ruby-monetdb-sql
+Architecture: all
+XB-Ruby-Versions: ${ruby:Versions}
+Depends: ruby | ruby-interpreter,
+         ${misc:Depends},
+         ${shlibs:Depends}
+Recommends: monetdb5-sql (>= 11.22.0)
+Conflicts: ruby-monetdb-client
+Replaces: ruby-monetdb-client
+Description: Pure Ruby database driver for MonetDB/SQL
+ MonetDB is a database management system that is developed from a
+ main-memory perspective with use of a fully decomposed storage model,
+ automatic index management, extensibility of data types and search
+ accelerators.  It also has an SQL frontend.
+ .
+ This package contains a pure Ruby database driver for MonetDB/SQL.
new file mode 100644
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,13 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: monetdb-sql
+Upstream-Contact: info@monetdb.org
+Source: http://dev.monetdb.org/downloads/
+
+Files: *
+Copyright: 2016 MonetDB B.V.
+License: MPL-2.0
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0.  If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ .
+ Copyright 1997 - July 2008 CWI, August 2008 - 2016 MonetDB B.V.
new file mode 100755
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,18 @@
+#!/usr/bin/make -f
+#export DH_VERBOSE=1
+#
+# Uncomment to ignore all test failures (but the tests will run anyway)
+#export DH_RUBY_IGNORE_TESTS=all
+#
+# Uncomment to ignore some test failures (but the tests will run anyway).
+# Valid values:
+#export DH_RUBY_IGNORE_TESTS=ruby2.1 ruby2.2
+#
+# If you need to specify the .gemspec (eg there is more than one)
+#export DH_RUBY_GEMSPEC=gem.gemspec
+#
+# Uncomment to check dependencies during build:
+# export GEM2DEB_TEST_RUNNER = --check-dependencies
+
+%:
+	dh $@ --buildsystem=ruby --with ruby
new file mode 100644
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1,1 @@
+3.0 (quilt)
new file mode 100644
--- /dev/null
+++ b/doc/readme.txt
@@ -0,0 +1,15 @@
+=== Install the standalone driver ===
+
+First build a gem file starting from the given gemspec:
+
+$ gem build ruby-monetdb-sql-0.2.gemspec
+
+and install the resulting gem with the command:
+
+$ gem install --local ruby-monetdb-sql-0.2.gem
+
+=== Tutorial ===
+
+A short example on how to use Ruby with MonetDB can be found in the lib/example.rb file. Make sure you create a database named "testdatabase2" and have a MonetDB server running on your system prior to trying the script. Instructions on how to run the server and create the database can be found here:
+
+https://www.monetdb.org/Documentation/UserGuide/Tutorial
new file mode 100644
--- /dev/null
+++ b/lib/MonetDB.rb
@@ -0,0 +1,209 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0.  If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# Copyright 1997 - July 2008 CWI, August 2008 - 2016 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 hostanme 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     - recconnect to a server
+    #  close         - terminate a connection
+    #  auto_commit?  - returns ture 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"
+  
+  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 hostanme 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
+    
+    @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
+    
+    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
+  
+  # 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
+      
+      @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
+
+  # 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
+  
+  # Create a new savepoint ID
+  def save
+    @connection.transactions.save
+  end
+  
+  # Release a savepoint ID
+  def release
+    @connection.transactions.release
+  end
+  
+  # Close an active connection
+  def close()
+    @connection.disconnect
+    @connection = nil
+  end
+end
new file mode 100644
--- /dev/null
+++ b/lib/MonetDBConnection.rb
@@ -0,0 +1,493 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0.  If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# Copyright 1997 - July 2008 CWI, August 2008 - 2016 MonetDB B.V.
+
+# Implements the MAPI communication protocol
+
+
+require 'socket'
+require 'time'
+require_relative 'hasher'
+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
+
+  MSG_REDIRECT          = '^' # auth redirection through merovingian
+  MSG_QUERY             = '&'
+  MSG_SCHEMA_HEADER     = '%'
+  MSG_INFO              = '!' # info response from mserver
+  MSG_TUPLE             = '['
+  MSG_PROMPT            =  ""
+
+
+  REPLY_SIZE            = '-1'
+
+  MAX_AUTH_ITERATION    = 10  # maximum number of atuh iterations (thorough merovingian) allowed
+   
+  MONET_ERROR           = -1
+
+  LANG_SQL = "sql"
+
+  # Protocols
+  MAPIv9 = 9
+
+  MONETDB_MEROVINGIAN = "merovingian"
+  MONETDB_MSERVER     = "monetdb"
+
+  MEROVINGIAN_MAX_ITERATIONS = 10
+
+  
+  # enable debug output
+  @@DEBUG               = false
+
+  # hour in seconds, used for timezone calculation
+  @@HOUR                = 3600
+
+  # maximum size (in bytes) for a monetdb message to be sent
+  @@MAX_MESSAGE_SIZE    = 32766
+  
+  # endianness of a message sent to the server
+  @@CLIENT_ENDIANNESS   = "BIG"
+  
+  # MAPI protocols supported by the driver
+  @@SUPPORTED_PROTOCOLS = [ MonetDBConnection::MAPIv9 ]
+  
+  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 hostanme 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
+
+    @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
+
+    if @lang[0, 3] == 'sql'
+      @lang = "sql"
+    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
+    
+    @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
+    
+    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
+
+    reply = build_auth_string_v9(@auth_type, salt, @database)
+
+    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 == MonetDBConnection::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 == 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
+
+  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
+  
+
+  # 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)))
+    
+    response = receive
+  
+    if response == MonetDBConnection::MSG_PROMPT
+      true
+    elsif response[0] == MonetDBConnection::MSG_INFO
+      raise MonetDBCommandError, "Unable to set reply_size: #{response}"
+    end
+    
+  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
+  
+  # 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
+
+    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
+
+  #
+  # 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
+        
+    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
+
+  # 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
+    
+    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')
+      
+      message << hdr + data.to_s # Short Little Endian Encoding  
+    end
+    
+    message.freeze # freeze and return the encode message
+  end
+
+  # Used as the first step in the authentication phase; retrives 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)
+      
+      # 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
+  
+  # Sets the time zone according to the Operating System settings
+  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 == 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
+
+    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
+    
+  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
+  
+  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"
+  
+  def initialize
+    @id = 0
+    @savepoint = ""
+  end
+  
+  def savepoint
+    @savepoint = SAVEPOINT_STRING + @id.to_s
+  end
+  
+  def release
+    prev_id
+  end
+  
+  def save
+    next_id
+  end
+  
+  private
+  def next_id
+    @id += 1
+  end
+  
+  def prev_id
+    @id -= 1
+  end
+  
+end
new file mode 100644
--- /dev/null
+++ b/lib/MonetDBData.rb
@@ -0,0 +1,328 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0.  If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# Copyright 1997 - July 2008 CWI, August 2008 - 2016 MonetDB B.V.
+
+# Models a MonetDB RecordSet
+require 'time'
+require 'date'
+require 'ostruct'
+
+require "bigdecimal"
+
+require_relative 'MonetDBConnection'
+
+require 'logger'
+
+class MonetDBData 
+  @@DEBUG               = false
+ 
+  def initialize(connection)
+    @connection = connection
+    @lang = @connection.lang
+
+    # Structure containing the header+results set for a fired Q_TABLE query     
+    @header = []
+    @query  = {}
+    
+    @record_set = []
+    @index = 0 # Position of the last returned record
+    
+    
+    @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
+    
+    return if data == nil
+    
+    record_set = "" # temporarly 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 @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")
+      
+      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 orderd 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
+
+  # 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
+
+  # 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)
+
+    column_values = []
+    @record_set.each do |row|
+      column_values << parse_tuple(row)[position]
+    end
+
+    return column_values
+  end
+
+  # 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
+
+    return result
+  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 paramter
+  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
+  
+  # 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 (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
+  
+  # ===================
+          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
+
+  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 = {}
+
+    @header["columns_name"].each do |column_name|
+       position = @header["columns_order"].fetch(column_name)
+       result[column_name] = record[position]
+     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
+      
+      # 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
+  
+  # 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
+    
+    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
+  
+  # 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 informations 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
+    
+      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
+    
+    return header.freeze
+  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
+        name_t = header_t[0].split(' ')[1].gsub(/,$/, '')
+        name_cols = Array.new
+      
+        header_t[1].split('%')[1].gsub(/'^\%'/, '').split('#')[0].split(' ').each do |col|
+          name_cols << col.gsub(/,$/, '')
+        end
+      
+        type_cols = { }
+        header_t[2].split('%')[1].gsub(/'^\%'/, '').split('#')[0].split(' ').each_with_index do |col, i|
+          if  col.gsub(/,$/, '') != nil
+            type_cols[ name_cols[i] ] = col.gsub(/,$/, '') 
+          end
+        end
+      
+        length_cols = { }
+        header_t[3].split('%')[1].gsub(/'^\%'/, '').split('#')[0].split(' ').each_with_index do |col, i|
+          length_cols[ name_cols[i] ] = col.gsub(/,$/, '')
+        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
+end
new file mode 100644
--- /dev/null
+++ b/lib/MonetDBExceptions.rb
@@ -0,0 +1,44 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0.  If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# Copyright 1997 - July 2008 CWI, August 2008 - 2016 MonetDB B.V.
+
+# Exception classes for the ruby-monetdb driver
+
+class MonetDBQueryError < StandardError
+  def initialize(e)
+    $stderr.puts e
+  end
+end
+
+class MonetDBDataError < StandardError
+  def initialize(e)
+    $stderr.puts e
+  end
+end
+
+class MonetDBCommandError < StandardError
+  def initialize(e)
+    $stderr.puts e
+  end
+end
+
+class MonetDBConnectionError < StandardError
+  def initialize(e)
+    $stderr.puts e
+  end
+end
+
+
+class MonetDBSocketError < StandardError
+  def initialize(e)
+    $stderr.puts e
+  end
+end
+
+class MonetDBProtocolError < StandardError
+   def initialize(e)
+     $stderr.puts e
+   end
+end
new file mode 100644
--- /dev/null
+++ b/lib/example.rb
@@ -0,0 +1,65 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0.  If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# Copyright 1997 - July 2008 CWI, August 2008 - 2016 MonetDB B.V.
+
+require_relative 'MonetDB'
+
+def print_result(result, message="")
+	puts message
+	puts "========"
+	result.each_record do |record|
+		puts record
+	end
+	puts "========"
+	puts
+end
+
+def get_database_connection
+	database_connection = MonetDB.new
+	database_connection.connect(user = "monetdb", passwd = "monetdb", lang = "sql", host="127.0.0.1", port = 50000, database_connection_name = "testdatabase2", auth_type = "SHA1")
+	return database_connection
+end
+
+# Connect to the database. This assumes you have a MonetDB server running with a database called testdatabase2
+db = get_database_connection
+
+# Add a table and some data to the database
+db.query("CREATE TABLE testtable (testnumber INTEGER)")
+db.query("INSERT INTO testtable(testnumber) VALUES (1)")
+db.query("INSERT INTO testtable(testnumber) VALUES (2)")
+
+# Get some data from the database
+result = db.query("SELECT * FROM testtable")
+print_result(result, "Showing all data in table")
+
+# Use of autocommit
+db.auto_commit(false)
+db.query("INSERT INTO testtable(testnumber) VALUES (100)")
+db.query("INSERT INTO testtable(testnumber) VALUES (101)")
+result = db.query("SELECT * FROM testtable")
+print_result(result, "First connection: 100 and 101 have not yet been saved, but are shown because they were added by the same connection")
+
+# Show that the data is not really in the database yet
+db2 = get_database_connection
+result = db2.query("SELECT * FROM testtable")
+print_result(result, "Second connection: 100 and 101 are not shown, because autocommit is off and it is a different connection")
+
+# Save the database data
+db.query("COMMIT")
+result = db2.query("SELECT * FROM testtable")
+print_result(result, "Second connection: 100 and 101 are shown now, because they were committed in db connection 1")
+db.auto_commit(true)
+
+# Get the data with other public interface methods
+puts "number of rows: #{result.num_rows}"
+puts "Fetch all: #{result.fetch_all}"
+puts "As column hash: #{result.fetch_all_as_column_hash}"
+puts "Iterate one at a time: #{result.fetch}"
+result.reset_index # restart the iterator used in the single fetch methods
+puts "Iterate one at a time as hash: #{result.fetch_hash}"
+
+# close the connection
+db.query("DROP TABLE testtable")
+db.close
new file mode 100644
--- /dev/null
+++ b/lib/hasher.rb
@@ -0,0 +1,45 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0.  If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# Copyright 1997 - July 2008 CWI, August 2008 - 2016 MonetDB B.V.
+
+require 'digest/md5'
+require 'digest/sha1'
+require 'digest/sha2' 
+
+class Hasher
+	# Constructor
+	# 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") 
+            @hashfunc = Digest::SHA256.new
+            @hashname = method.upcase
+          elsif (method.upcase == "SHA384")
+            @hashfunc = Digest::SHA384.new
+            @hashname = method.upcase
+          elsif (method.upcase == "SHA512")
+            @hashfunc = Digest::SHA512.new
+            @hashname = method.upcase
+          else
+            # default to MD5
+                  @hashfunc = Digest::MD5.new
+                  @hashname = "MD5"
+          end
+          @pwd = pwd
+  end
+  
+
+	def hashname
+		@hashname
+	end
+
+	# Compute hash code
+	def hashsum
+		return @hashfunc.hexdigest(@pwd)
+	end
+end
new file mode 100644
--- /dev/null
+++ b/monetdb-sql.gemspec
@@ -0,0 +1,18 @@
+
+Gem::Specification.new do |s|
+   s.required_ruby_version = '>= 1.8.0'
+   s.name = %q{monetdb-sql}
+   s.version = "1.0"
+   s.date = %q{2016-03-02}
+   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.has_rdoc = true
+   s.require_path = './lib'
+   # placeholder project to avoid warning about not having a rubyforge_project
+   s.rubyforge_project = "nowarning"
+end
new file mode 100644
--- /dev/null
+++ b/rubygem-monetdb-sql.spec
@@ -0,0 +1,70 @@
+%global gem_name	monetdb-sql
+
+Name:		rubygem-%{gem_name}
+Epoch:		1
+Version:	1.0
+Release:	1%{?dist}
+Summary:	Pure Ruby database driver for MonetDB/SQL
+Group:		Applications/Databases
+
+License:	MPLv2.0
+URL:		http://www.monetdb.org/
+Source0:	http://dev.monetdb.org/downloads/ruby/gems/%{gem_name}-%{version}.gem
+
+BuildRequires:	ruby(release)
+BuildRequires:	rubygems-devel
+BuildRequires:	ruby >= 1.8.0
+BuildArch:	noarch
+
+Requires:	ruby(release)
+Requires:	rubygem-bigdecimal
+
+Recommends:	MonetDB-SQL-server5 >= 11.22.0
+Suggests:	%{name}-doc = %{version}-%{release}
+
+%description
+MonetDB is a database management system that is developed from a
+main-memory perspective with use of a fully decomposed storage model,
+automatic index management, extensibility of data types and search
+accelerators.  It also has an SQL frontend.
+
+This package contains a pure Ruby database driver for MonetDB/SQL.
+
+%package	doc
+Summary:	Documentation for %{name}
+Group:		Documentation
+Requires:	%{name} = %{version}-%{release}
+BuildArch:	noarch
+
+%description	doc
+This package contains documentation for %{name}.
+
+%prep
+%setup -q -c -T
+cp %{SOURCE0} .
+
+
+%build
+%gem_install
+
+
+%install
+mkdir -p %{buildroot}%{gem_dir}
+cp -a .%{gem_dir}/* %{buildroot}/%{gem_dir}
+
+find %{buildroot}%{gem_instdir} -name \*.rb -exec chmod 0644 '{}' +
+
+
+%files
+%dir %{gem_instdir}
+%{gem_libdir}
+%exclude %{gem_cache}
+%{gem_spec}
+
+%files doc
+%doc %{gem_docdir}
+
+
+%changelog
+* Wed Mar  2 2016 Sjoerd Mullender <sjoerd@acm.org> - 1.0-1
+- The Ruby interface to MonetDB is now a separate package.