ruby-monetdb-sql 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README ADDED
@@ -0,0 +1,154 @@
1
+ == Standalone driver ==
2
+ This directory contains the a ruby interface to monetdb5
3
+ written in pure ruby.
4
+
5
+ lib/MonetDB.rb
6
+ lib/MonetDBConnection.rb
7
+ lib/MonetDBStatements.rb
8
+ lib/MonetDBData.rb
9
+ lib/MonetDBExceptions.rb
10
+ lib/hasher.rb
11
+ lib/demo.rb: demo application how to interact with the database
12
+
13
+ ruby-monetdb-sql-0.1.gemspec: make file for rubygems
14
+
15
+ doc/: rubydoc in HTML format
16
+
17
+ == Installation ==
18
+
19
+ The standalone monetdb driver can be installed using the RubyGems Package Manager.
20
+
21
+ First build a gem file starting from the gemspec configuration:
22
+
23
+ $ gem build ruby-monetdb-sql-0.1.gemspec
24
+
25
+ Then install with the command:
26
+
27
+ $ gem install ruby-monetdb-sql-0.1.gem
28
+
29
+ == Usage ==
30
+ To use the standalone driver import the 'MonetDB' class and 'rubygems' (in case you installed it using gems).
31
+
32
+ A typical sequence of events is as follows:
33
+ Invoke query using the database handle to send the statement to the server and get back a result set object.
34
+
35
+ A result set object has methods for fetching rows, moving around in the result set, obtaining column metadata, and releasing the result set.
36
+ Use a row fetching method such as fetch_row or an iterator such as each to access the rows of the result set.
37
+ If you want a count of the number of rows in the result set: invoke 'num_rows' method.
38
+ Invoke 'free' to release the result set.
39
+
40
+ == Example ==
41
+
42
+ require 'MonetDB'
43
+
44
+ db = MonetDB.new
45
+ db.connect(user = "monetdb", passwd = "monetdb", lang = "sql", host="127.0.0.1", port = 50000, db_name = "demo", auth_type = "SHA1")
46
+
47
+ # set type_cast=true to enable MonetDB to Ruby type mapping
48
+ res = db.query("SELECT * from tables;", type_cast = false)
49
+
50
+ #puts res.debug_columns_type
51
+
52
+ puts "Number of rows returned: " + res.num_rows.to_s
53
+ puts "Number of fields: " + res.num_fields.to_s
54
+
55
+
56
+ # Get the columns' name
57
+ col_names = res.name_fields
58
+
59
+
60
+ # Iterate over the record set and retrieve on row at a time
61
+ puts res.fetch
62
+ while row = res.fetch do
63
+ printf "%s \n", row
64
+ end
65
+
66
+ # Release the result set.
67
+ res.free
68
+
69
+ # Disconnect from server
70
+ db.close
71
+
72
+ See lib/demo.rb and the MonetDBDatar class documentation for more examples.
73
+
74
+
75
+
76
+ == ActiveRecord connector adapter ==
77
+ Active Record connects business objects and database tables to create a persistable domain model where logic and data are presented in one wrapping. It‘s an implementation of the object-relational mapping (ORM) pattern.
78
+
79
+ Required files:
80
+
81
+ adapter/lib/active_record/monetdb_adapter.rb
82
+
83
+ Usage example follows:
84
+ require 'active_record'
85
+
86
+ ActiveRecord::Base.logger = Logger.new(STDERR)
87
+ ActiveRecord::Base.colorize_logging = true
88
+
89
+ ActiveRecord::Base.establish_connection(
90
+ :adapter => "monetdb",
91
+ :host => "localhost",
92
+ :database => "demo"
93
+ )
94
+
95
+ # Create a new table
96
+ class AddTests < ActiveRecord::Migration
97
+ def self.up
98
+ create_table :tests do |table|
99
+ table.column :name, :string
100
+ table.column :surname, :string
101
+ end
102
+ end
103
+
104
+ def self.down
105
+ drop_table :tests
106
+ end
107
+
108
+ end
109
+
110
+ AddTests.up
111
+
112
+ # Migration: add a column name with a default value
113
+ class AddAge < ActiveRecord::Migration
114
+ def self.up
115
+ add_column :tests, :age, :smallint, :default => 18
116
+ end
117
+
118
+ def self.down
119
+ remove_column :tests, :age
120
+ end
121
+
122
+ end
123
+
124
+ class Test < ActiveRecord::Base
125
+ end
126
+
127
+ # Insert an entry in the table
128
+ Test.create(:name => 'X', :surname => 'Y')
129
+
130
+ # add a column
131
+ AddAge.up
132
+
133
+ # return the first result of the query SELECT * from tables
134
+ row = Test.find(:first)
135
+ printf "SELECT * from tests LIMIT 1:\n"
136
+ printf "Name: %s, Surname: %s, Age: %s\n", row.name, row.surname, row.age
137
+
138
+ # Drop the table
139
+ AddTests.down
140
+
141
+ == Rubygem ==
142
+
143
+ The standalone ruby driver can be distributed as a ruby gem.
144
+ A gem file is already available; however, it can be generated
145
+ starting from the ruby-monetdb-sql-0.1.gemspec file:
146
+
147
+ $ gem build ruby-monetdb-sql-0.1.gemspec
148
+
149
+ To install the file run the command:
150
+
151
+ $ gem install ruby-monetdb-sql-0.1.gem
152
+
153
+ Documentation in ri and html format will be generated and installed as well
154
+
@@ -0,0 +1,274 @@
1
+ # The contents of this file are subject to the MonetDB Public License
2
+ # Version 1.1 (the "License"); you may not use this file except in
3
+ # compliance with the License. You may obtain a copy of the License at
4
+ # http://monetdb.cwi.nl/Legal/MonetDBLicense-1.1.html
5
+ #
6
+ # Software distributed under the License is distributed on an "AS IS"
7
+ # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
8
+ # License for the specific language governing rights and limitations
9
+ # under the License.
10
+ #
11
+ # The Original Code is the MonetDB Database System.
12
+ #
13
+ # The Initial Developer of the Original Code is CWI.
14
+ # Portions created by CWI are Copyright (C) 1997-July 2008 CWI.
15
+ # Copyright August 2008-2011 MonetDB B.V.
16
+ # All Rights Reserved.
17
+
18
+
19
+ # A typical sequence of events is as follows:
20
+ # Fire a query using the database handle to send the statement to the server and get back a result set object.
21
+
22
+ # A result set object has methods for fetching rows, moving around in the result set, obtaining column metadata, and releasing the result set.
23
+ # Use a row fetching method such as 'fetch_row' or an iterator such as each to access the rows of the result set.
24
+ # Call 'free' to release the result set.
25
+
26
+
27
+ #module MonetDB
28
+
29
+ require 'MonetDBConnection'
30
+ require 'MonetDBData'
31
+ require 'MonetDBExceptions'
32
+
33
+ # = Introduction
34
+ #
35
+ # A typical sequence of events is as follows:
36
+ # 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.
37
+ #
38
+ # A result set object has methods for fetching rows, moving around in the result set, obtaining column metadata, and releasing the result set.
39
+ # A result set object is an instance of the MonetDBData class.
40
+ #
41
+ # Records can be returneds as arrays and iterators over the set.
42
+ #
43
+ # A database handler (dbh) is and instance of the MonetDB class.
44
+ #
45
+ # = Connection management
46
+ #
47
+ # connect - establish a new connection
48
+ # * user: username (default is monetdb)
49
+ # * passwd: password (default is monetdb)
50
+ # * lang: language (default is sql)
51
+ # * host: server hostanme or ip (default is localhost)
52
+ # * port: server port (default is 50000)
53
+ # * db_name: name of the database to connect to
54
+ # * auth_type: hashing function to use during authentication (default is SHA1)
55
+ #
56
+ # is_connected? - returns true if there is an active connection to a server, false otherwise
57
+ # reconnect - recconnect to a server
58
+ # close - terminate a connection
59
+ # auto_commit? - returns ture if the session is running in auto commit mode, false otherwise
60
+ # auto_commit - enable/disable auto commit mode.
61
+ #
62
+ # query - fire a query
63
+ #
64
+ # Currently MAPI protocols 8 and 9 are supported.
65
+ #
66
+ # = Managing record sets
67
+ #
68
+ #
69
+ # A record set is represented as an instance of the MonetDBData class; the class provides methods to manage retrieved data.
70
+ #
71
+ #
72
+ # The following methods allow to iterate over data:
73
+ #
74
+ # fetch - iterates over the record set and retrieves on row at a time. Each row is returned as an array.
75
+ # fetch_hash - iterates over columns (on cell at a time).
76
+ # fetch_all_hash - returns record set entries hashed by column name orderd by column position.
77
+ #
78
+ # To return the record set as an array (with each tuple stored as array of fields) the following method can be used:
79
+ #
80
+ # fetch_all - fetch all rows and store them
81
+ #
82
+ #
83
+ # Information about the retrieved record set can be obtained via the following methods:
84
+ #
85
+ # num_rows - returns the number of rows present in the record set
86
+ # num_fields - returns the number of fields (columns) that compose the schema
87
+ # name_fields - returns the (ordered) name of the schema's columns
88
+ # type_fields - returns the (ordered) types list of the schema's columns
89
+ #
90
+ # To release a record set MonetDBData#free can be used.
91
+ #
92
+ # = Type conversion
93
+ #
94
+ # A mapping between SQL and ruby type is supported. Each retrieved field can be converted to a ruby datatype via
95
+ # a getTYPE method.
96
+ #
97
+ # The currently supported cast methods are:
98
+ #
99
+ # getInt - convert to an integer value
100
+ # getFloat - convert to a floating point value
101
+ # getString - return a string representation of the value, with trailing and leading " characters removed
102
+ # getBlob - convert an SQL stored HEX string to its binary representation
103
+ # getTime - return a string representation of a TIME field
104
+ # getDate - return a string representation of a DATE field
105
+ # getDateTime - convert a TIMESTAMP field to a ruby Time object
106
+ # getChar - on ruby >= 1.9, convert a CHAR field to char
107
+ # getBool - convert a BOOLEAN field to a ruby bool object. If the value of the field is unknown, nil is returned
108
+ # getNull - convert a NULL value to a nil object
109
+ #
110
+ #
111
+ # = Transactions
112
+ #
113
+ # By default monetdb works in auto_commit mode. To turn this feature off MonetDB#auto_commit(flag=false) can be used.
114
+ #
115
+ # Once auto_commit has been disable it is possible to start transactions, create/delete savepoints, rollback and commit with
116
+ # the usual SQL statements.
117
+ #
118
+ # Savepoints IDs can be generated using the MonetDB#save method. To release a savepoint ID use MonetDB#release.
119
+ #
120
+ # Savepoints can be accessed (as a stack) with the MonetDB#transactions method.
121
+ #
122
+ # example/standalone.rb contains usage example of the above mentioned methods.
123
+
124
+ # = Introduction
125
+ #
126
+ # A typical sequence of events is as follows:
127
+ # 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.
128
+ #
129
+ # A result set object has methods for fetching rows, moving around in the result set, obtaining column metadata, and releasing the result set.
130
+ # A result set object is an instance of the MonetDBData class.
131
+ #
132
+ # Records can be returneds as arrays and iterators over the set.
133
+ #
134
+ # A database handler (dbh) is and instance of the MonetDB class.
135
+ #
136
+ # = Connection management
137
+ #
138
+ # connect - establish a new connection
139
+ # * user: username (default is monetdb)
140
+ # * passwd: password (default is monetdb)
141
+ # * lang: language (default is sql)
142
+ # * host: server hostanme or ip (default is localhost)
143
+ # * port: server port (default is 50000)
144
+ # * db_name: name of the database to connect to
145
+ # * auth_type: hashing function to use during authentication (default is SHA1)
146
+ #
147
+ # is_connected? - returns true if there is an active connection to a server, false otherwise
148
+ # reconnect - recconnect to a server
149
+ # close - terminate a connection
150
+ # auto_commit? - returns ture if the session is running in auto commit mode, false otherwise
151
+ # auto_commit - enable/disable auto commit mode.
152
+ #
153
+ # query - fire a query
154
+ #
155
+ # Currently MAPI protocols 8 and 9 are supported.
156
+ #
157
+ # = Managing record sets
158
+ #
159
+ #
160
+ # A record set is represented as an instance of the MonetDBData class; the class provides methods to manage retrieved data.
161
+ #
162
+ #
163
+ # The following methods allow to iterate over data:
164
+ #
165
+ # fetch - iterates over the record set and retrieves on row at a time. Each row is returned as an array.
166
+ # fetch_hash - iterates over columns (on cell at a time).
167
+ # fetch_all_hash - returns record set entries hashed by column name orderd by column position.
168
+ #
169
+ # To return the record set as an array (with each tuple stored as array of fields) the following method can be used:
170
+ #
171
+ # fetch_all - fetch all rows and store them
172
+ #
173
+ #
174
+ # Information about the retrieved record set can be obtained via the following methods:
175
+ #
176
+ # num_rows - returns the number of rows present in the record set
177
+ # num_fields - returns the number of fields (columns) that compose the schema
178
+ # name_fields - returns the (ordered) name of the schema's columns
179
+ #
180
+ # To release a record set MonetDBData#free can be used.
181
+ #
182
+ # = Type conversion
183
+ #
184
+ # Invoking MonetDB#query with the flag type_conversion=true will result in a type cast of the record set fields from SQL types to ruby types
185
+ #
186
+ # demo.rb contains usage example of the above mentioned methods.
187
+
188
+ class MonetDB
189
+ def initalize()
190
+ @connection = nil
191
+ end
192
+
193
+ # Establish a new connection.
194
+ # * user: username (default is monetdb)
195
+ # * passwd: password (default is monetdb)
196
+ # * lang: language (default is sql)
197
+ # * host: server hostanme or ip (default is localhost)
198
+ # * port: server port (default is 50000)
199
+ # * db_name: name of the database to connect to
200
+ # * auth_type: hashing function to use during authentication (default is SHA1)
201
+ def connect(username = "monetdb", password = "monetdb", lang = "sql", host="127.0.0.1", port = "50000", db_name = "test", auth_type = "SHA1")
202
+ # TODO: handle pools of connections
203
+ @username = username
204
+ @password = password
205
+ @lang = lang
206
+ @host = host
207
+ @port = port
208
+ @db_name = db_name
209
+ @auth_type = auth_type
210
+ @connection = MonetDBConnection.new(user = @username, passwd = @password, lang = @lang, host = @host, port = @port)
211
+ @connection.connect(@db_name, @auth_type)
212
+ end
213
+
214
+ # Send a <b> user submitted </b> query to the server and store the response.
215
+ # Returns and instance of MonetDBData.
216
+ def query(q = "")
217
+ if @connection != nil
218
+ @data = MonetDBData.new(@connection)
219
+ @data.execute(q)
220
+ end
221
+ return @data
222
+ end
223
+
224
+ # Return true if there exists a "connection" object
225
+ def is_connected?
226
+ if @connection == nil
227
+ return false
228
+ else
229
+ return true
230
+ end
231
+ end
232
+
233
+ # Reconnect to the server
234
+ def reconnect
235
+ if @connection != nil
236
+ self.close
237
+
238
+ @connection = MonetDBConnection.new(user = @username, passwd = @password, lang = @lang, host = @host, port = @port)
239
+ @connection.connect(db_name = @db_name, auth_type = @auth_type)
240
+ end
241
+ end
242
+
243
+ # Turn auto commit on/off
244
+ def auto_commit(flag=true)
245
+ @connection.set_auto_commit(flag)
246
+ end
247
+
248
+ # Returns the current auto commit (on/off) settings.
249
+ def auto_commit?
250
+ @connection.auto_commit?
251
+ end
252
+
253
+ # Returns the name of the last savepoint in a transactions pool
254
+ def transactions
255
+ @connection.savepoint
256
+ end
257
+
258
+ # Create a new savepoint ID
259
+ def save
260
+ @connection.transactions.save
261
+ end
262
+
263
+ # Release a savepoint ID
264
+ def release
265
+ @connection.transactions.release
266
+ end
267
+
268
+ # Close an active connection
269
+ def close()
270
+ @connection.disconnect
271
+ @connection = nil
272
+ end
273
+ end
274
+ #end
@@ -0,0 +1,550 @@
1
+ # The contents of this file are subject to the MonetDB Public License
2
+ # Version 1.1 (the "License"); you may not use this file except in
3
+ # compliance with the License. You may obtain a copy of the License at
4
+ # http://monetdb.cwi.nl/Legal/MonetDBLicense-1.1.html
5
+ #
6
+ # Software distributed under the License is distributed on an "AS IS"
7
+ # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
8
+ # License for the specific language governing rights and limitations
9
+ # under the License.
10
+ #
11
+ # The Original Code is the MonetDB Database System.
12
+ #
13
+ # The Initial Developer of the Original Code is CWI.
14
+ # Portions created by CWI are Copyright (C) 1997-July 2008 CWI.
15
+ # Copyright August 2008-2011 MonetDB B.V.
16
+ # All Rights Reserved.
17
+
18
+ # Implements the MAPI communication protocol
19
+
20
+
21
+ require 'socket'
22
+ require 'time'
23
+ require 'hasher'
24
+ require 'MonetDBExceptions'
25
+ require 'iconv' # utf-8 support
26
+ require 'uri' # parse merovingian redirects
27
+
28
+ Q_TABLE = "1" # SELECT operation
29
+ Q_UPDATE = "2" # INSERT/UPDATE operations
30
+ Q_CREATE = "3" # CREATE/DROP TABLE operations
31
+ Q_TRANSACTION = "4" # TRANSACTION
32
+ Q_PREPARE = "5"
33
+ Q_BLOCK = "6" # QBLOCK message
34
+
35
+ MSG_REDIRECT = '^' # auth redirection through merovingian
36
+ MSG_QUERY = '&'
37
+ MSG_SCHEMA_HEADER = '%'
38
+ MSG_INFO = '!' # info response from mserver
39
+ MSG_TUPLE = '['
40
+ MSG_PROMPT = ""
41
+
42
+
43
+ REPLY_SIZE = '-1'
44
+
45
+ MAX_AUTH_ITERATION = 10 # maximum number of atuh iterations (thorough merovingian) allowed
46
+
47
+ MONET_ERROR = -1
48
+
49
+ LANG_SQL = "sql"
50
+
51
+ # Xquery support
52
+ LANG_XQUERY = "xquery"
53
+ XQUERY_OUTPUT_SEQ = true # use monetdb xquery's output seq
54
+
55
+ class MonetDBConnection
56
+
57
+
58
+ # enable debug output
59
+ @@DEBUG = false
60
+
61
+ # hour in seconds, used for timezone calculation
62
+ @@HOUR = 3600
63
+
64
+ # maximum size (in bytes) for a monetdb message to be sent
65
+ @@MAX_MESSAGE_SIZE = 32766
66
+
67
+ # endianness of a message sent to the server
68
+ @@CLIENT_ENDIANNESS = "BIG"
69
+
70
+ # MAPI protocols supported by the driver
71
+ @@SUPPORTED_PROTOCOLS = [ 8, 9 ]
72
+
73
+ attr_reader :socket, :auto_commit, :transactions, :lang
74
+
75
+ # Instantiates a new MonetDBConnection object
76
+ # * user: username (default is monetdb)
77
+ # * passwd: password (default is monetdb)
78
+ # * lang: language (default is sql)
79
+ # * host: server hostanme or ip (default is localhost)
80
+ # * port: server port (default is 50000)
81
+
82
+ def initialize(user = "monetdb", passwd = "monetdb", lang = "sql", host="127.0.0.1", port = "50000")
83
+ @user = user
84
+ @passwd = passwd
85
+ @lang = lang.downcase
86
+ @host = host
87
+ @port = port
88
+
89
+ @client_endianness = @@CLIENT_ENDIANNESS
90
+
91
+ @auth_iteration = 0
92
+ @connection_established = false
93
+
94
+ @transactions = MonetDBTransaction.new # handles a pool of transactions (generates and keeps track of savepoints)
95
+
96
+ if @@DEBUG == true
97
+ require 'logger'
98
+ end
99
+
100
+ end
101
+
102
+ # Connect to the database, creates a new socket
103
+ def connect(db_name = 'demo', auth_type = 'SHA1')
104
+ @database = db_name
105
+ @auth_type = auth_type
106
+
107
+ @socket = TCPSocket.new(@host, @port.to_i)
108
+ if real_connect
109
+ if @lang == LANG_SQL
110
+ set_timezone
111
+ set_reply_size
112
+ elsif (@lang == LANG_XQUERY) and XQUERY_OUTPUT_SEQ
113
+ # require xquery output to be in seq format
114
+ send(format_command("output seq"))
115
+ end
116
+ true
117
+ end
118
+ false
119
+ end
120
+
121
+
122
+ # perform a real connection; retrieve challenge, proxy through merovinginan, build challenge and set the timezone
123
+ def real_connect
124
+
125
+ server_challenge = retrieve_server_challenge()
126
+ print "CHALLENGE: " + server_challenge.to_s
127
+ if server_challenge != nil
128
+ salt = server_challenge.split(':')[0]
129
+ @server_name = server_challenge.split(':')[1]
130
+ @protocol = server_challenge.split(':')[2].to_i
131
+ @supported_auth_types = server_challenge.split(':')[3].split(',')
132
+ @server_endianness = server_challenge.split(':')[4]
133
+ if @protocol == 9
134
+ @pwhash = server_challenge.split(':')[5]
135
+ end
136
+ else
137
+ raise MonetDBConnectionError, "Error: server returned an empty challenge string."
138
+ end
139
+
140
+ # The server supports only RIPMED168 or crypt as an authentication hash function, but the driver does not.
141
+ if @supported_auth_types.length == 1
142
+ auth = @supported_auth_types[0]
143
+ if auth.upcase == "RIPEMD160" or auth.upcase == "CRYPT"
144
+ raise MonetDBConnectionError, auth.upcase + " " + ": algorithm not supported by ruby-monetdb."
145
+ end
146
+ end
147
+
148
+
149
+ # If the server protocol version is not 8: abort and notify the user.
150
+ if @@SUPPORTED_PROTOCOLS.include?(@protocol) == false
151
+ raise MonetDBProtocolError, "Protocol not supported. The current implementation of ruby-monetdb works with MAPI protocols #{@@SUPPORTED_PROTOCOLS} only."
152
+
153
+ elsif mapi_proto_v8?
154
+ reply = build_auth_string_v8(@auth_type, salt, @database)
155
+ elsif mapi_proto_v9?
156
+ reply = build_auth_string_v9(@auth_type, salt, @database)
157
+ end
158
+
159
+ if @socket != nil
160
+ @connection_established = true
161
+
162
+ send(reply)
163
+ monetdb_auth = receive
164
+
165
+ if monetdb_auth.length == 0
166
+ # auth succedeed
167
+ true
168
+ else
169
+ if monetdb_auth[0].chr == MSG_REDIRECT
170
+ #redirection
171
+
172
+ redirects = [] # store a list of possible redirects
173
+
174
+ monetdb_auth.split('\n').each do |m|
175
+ # strip the trailing ^mapi:
176
+ # if the redirect string start with something != "^mapi:" or is empty, the redirect is invalid and shall not be included.
177
+ if m[0..5] == "^mapi:"
178
+ redir = m[6..m.length]
179
+ # url parse redir
180
+ redirects.push(redir)
181
+ else
182
+ $stderr.print "Warning: Invalid Redirect #{m}"
183
+ end
184
+ end
185
+
186
+ if redirects.size == 0
187
+ raise MonetDBConnectionError, "No valid redirect received"
188
+ else
189
+ begin
190
+ uri = URI.split(redirects[0])
191
+ # Splits the string on following parts and returns array with result:
192
+ #
193
+ # * Scheme
194
+ # * Userinfo
195
+ # * Host
196
+ # * Port
197
+ # * Registry
198
+ # * Path
199
+ # * Opaque
200
+ # * Query
201
+ # * Fragment
202
+ server_name = uri[0]
203
+ host = uri[2]
204
+ port = uri[3]
205
+ database = uri[5].gsub(/^\//, '') if uri[5] != nil
206
+ rescue URI::InvalidURIError
207
+ raise MonetDBConnectionError, "Invalid redirect: #{redirects[0]}"
208
+ end
209
+ end
210
+
211
+ if server_name == "merovingian"
212
+ if @auth_iteration <= 10
213
+ @auth_iteration += 1
214
+ real_connect
215
+ else
216
+ raise MonetDBConnectionError, "Merovingian: too many iterations while proxying."
217
+ end
218
+ elsif server_name == "monetdb"
219
+ begin
220
+ @socket.close
221
+ rescue
222
+ raise MonetDBConnectionError, "I/O error while closing connection to #{@socket}"
223
+ end
224
+ # reinitialize a connection
225
+ @host = host
226
+ @port = port
227
+
228
+ connect(database, @auth_type)
229
+ else
230
+ @connection_established = false
231
+ raise MonetDBConnectionError, monetdb_auth
232
+ end
233
+ elsif monetdb_auth[0].chr == MSG_INFO
234
+ raise MonetDBConnectionError, monetdb_auth
235
+ end
236
+ end
237
+ end
238
+ end
239
+ def savepoint
240
+ @transactions.savepoint
241
+ end
242
+
243
+ # Formats a <i>command</i> string so that it can be parsed by the server
244
+ def format_command(x)
245
+ return "X" + x + "\n"
246
+ end
247
+
248
+
249
+ # send an 'export' command to the server
250
+ def set_export(id, idx, offset)
251
+ send(format_command("export " + id.to_s + " " + idx.to_s + " " + offset.to_s ))
252
+ end
253
+
254
+ # send a 'reply_size' command to the server
255
+ def set_reply_size
256
+ send(format_command(("reply_size " + REPLY_SIZE)))
257
+
258
+ response = receive
259
+
260
+ if response == MSG_PROMPT
261
+ true
262
+ elsif response[0] == MSG_INFO
263
+ raise MonetDBCommandError, "Unable to set reply_size: #{response}"
264
+ end
265
+
266
+ end
267
+
268
+ def set_output_seq
269
+ send(format_command("output seq"))
270
+ end
271
+
272
+ # Disconnect from server
273
+ def disconnect()
274
+ if @connection_established
275
+ begin
276
+ @socket.close
277
+ rescue => e
278
+ $stderr.print e
279
+ end
280
+ else
281
+ raise MonetDBConnectionError, "No connection established."
282
+ end
283
+ end
284
+
285
+ # send data to a monetdb5 server instance and returns server's response
286
+ def send(data)
287
+ encode_message(data).each do |m|
288
+ @socket.write(m)
289
+ end
290
+ end
291
+
292
+ # receive data from a monetdb5 server instance
293
+ def receive
294
+ is_final, chunk_size = recv_decode_hdr
295
+
296
+ if chunk_size == 0
297
+ return "" # needed on ruby-1.8.6 linux/64bit; recv(0) hangs on this configuration.
298
+ end
299
+
300
+ data = @socket.recv(chunk_size)
301
+
302
+ if is_final == false
303
+ while is_final == false
304
+ is_final, chunk_size = recv_decode_hdr
305
+ data += @socket.recv(chunk_size)
306
+ end
307
+ end
308
+
309
+ return data
310
+ end
311
+
312
+ # Builds and authentication string given the parameters submitted by the user (MAPI protocol v8).
313
+ #
314
+ def build_auth_string_v8(auth_type, salt, db_name)
315
+ # seed = password + salt
316
+ if (auth_type.upcase == "MD5" or auth_type.upcase == "SHA1") and @supported_auth_types.include?(auth_type.upcase)
317
+ auth_type = auth_type.upcase
318
+ digest = Hasher.new(auth_type, @passwd+salt)
319
+ hashsum = digest.hashsum
320
+ elsif auth_type.downcase == "plain" or not @supported_auth_types.include?(auth_type.upcase)
321
+ auth_type = 'plain'
322
+ hashsum = @passwd + salt
323
+
324
+ elsif auth_type.downcase == "crypt"
325
+ auth_type = @supported_auth_types[@supported_auth_types.index(auth_type)+1]
326
+ $stderr.print "The selected hashing algorithm is not supported by the Ruby driver. #{auth_type} will be used instead."
327
+ digest = Hasher.new(auth_type, @passwd+salt)
328
+ hashsum = digest.hashsum
329
+ else
330
+ # The user selected an auth type not supported by the server.
331
+ raise MonetDBConnectionError, "#{auth_type} not supported by the server. Please choose one from #{@supported_auth_types}"
332
+
333
+ end
334
+ # Build the reply message with header
335
+ reply = @client_endianness + ":" + @user + ":{" + auth_type + "}" + hashsum + ":" + @lang + ":" + db_name + ":"
336
+ end
337
+
338
+ #
339
+ # Builds and authentication string given the parameters submitted by the user (MAPI protocol v9).
340
+ #
341
+ def build_auth_string_v9(auth_type, salt, db_name)
342
+ if (auth_type.upcase == "MD5" or auth_type.upcase == "SHA1") and @supported_auth_types.include?(auth_type.upcase)
343
+ auth_type = auth_type.upcase
344
+ # Hash the password
345
+ pwhash = Hasher.new(@pwhash, @passwd)
346
+
347
+ digest = Hasher.new(auth_type, pwhash.hashsum + salt)
348
+ hashsum = digest.hashsum
349
+
350
+ elsif auth_type.downcase == "plain" # or not @supported_auth_types.include?(auth_type.upcase)
351
+ # Keep it for compatibility with merovingian
352
+ auth_type = 'plain'
353
+ hashsum = @passwd + salt
354
+ elsif @supported_auth_types.include?(auth_type.upcase)
355
+ if auth_type.upcase == "RIPEMD160"
356
+ auth_type = @supported_auth_types[@supported_auth_types.index(auth_type)+1]
357
+ $stderr.print "The selected hashing algorithm is not supported by the Ruby driver. #{auth_type} will be used instead."
358
+ end
359
+ # Hash the password
360
+ pwhash = Hasher.new(@pwhash, @passwd)
361
+
362
+ digest = Hasher.new(auth_type, pwhash.hashsum + salt)
363
+ hashsum = digest.hashsum
364
+ else
365
+ # The user selected an auth type not supported by the server.
366
+ raise MonetDBConnectionError, "#{auth_type} not supported by the server. Please choose one from #{@supported_auth_types}"
367
+ end
368
+ # Build the reply message with header
369
+ reply = @client_endianness + ":" + @user + ":{" + auth_type + "}" + hashsum + ":" + @lang + ":" + db_name + ":"
370
+ end
371
+
372
+ # builds a message to be sent to the server
373
+ def encode_message(msg = "")
374
+ message = Array.new
375
+ data = ""
376
+
377
+ hdr = 0 # package header
378
+ pos = 0
379
+ is_final = false # last package in the stream
380
+
381
+ while (! is_final)
382
+ data = msg[pos..pos+[@@MAX_MESSAGE_SIZE.to_i, (msg.length - pos).to_i].min]
383
+ pos += data.length
384
+
385
+ if (msg.length - pos) == 0
386
+ last_bit = 1
387
+ is_final = true
388
+ else
389
+ last_bit = 0
390
+ end
391
+
392
+ hdr = [(data.length << 1) | last_bit].pack('v')
393
+
394
+ message << hdr + data.to_s # Short Little Endian Encoding
395
+ end
396
+
397
+ message.freeze # freeze and return the encode message
398
+ end
399
+
400
+ # Used as the first step in the authentication phase; retrives a challenge string from the server.
401
+ def retrieve_server_challenge()
402
+ server_challenge = receive
403
+ end
404
+
405
+ # reads and decodes the header of a server message
406
+ def recv_decode_hdr()
407
+ if @socket != nil
408
+ fb = @socket.recv(1)
409
+ sb = @socket.recv(1)
410
+
411
+ # Use execeptions handling to keep compatibility between different ruby
412
+ # versions.
413
+ #
414
+ # Chars are treated differently in ruby 1.8 and 1.9
415
+ # try do to ascii to int conversion using ord (ruby 1.9)
416
+ # and if it fail fallback to character.to_i (ruby 1.8)
417
+ begin
418
+ fb = fb[0].ord
419
+ sb = sb[0].ord
420
+ rescue NoMethodError => one_eight
421
+ fb = fb[0].to_i
422
+ sb = sb[0].to_i
423
+ end
424
+
425
+ chunk_size = (sb << 7) | (fb >> 1)
426
+
427
+ is_final = false
428
+ if ( (fb & 1) == 1 )
429
+ is_final = true
430
+
431
+ end
432
+ # return the size of the chunk (in bytes)
433
+ return is_final, chunk_size
434
+ else
435
+ raise MonetDBSocketError, "Error while receiving data\n"
436
+ end
437
+ end
438
+
439
+ # Sets the time zone according to the Operating System settings
440
+ def set_timezone()
441
+ tz = Time.new
442
+ tz_offset = tz.gmt_offset / @@HOUR
443
+
444
+ if tz_offset <= 9 # verify minute count!
445
+ tz_offset = "'+0" + tz_offset.to_s + ":00'"
446
+ else
447
+ tz_offset = "'+" + tz_offset.to_s + ":00'"
448
+ end
449
+ query_tz = "sSET TIME ZONE INTERVAL " + tz_offset + " HOUR TO MINUTE;"
450
+
451
+ # Perform the query directly within the method
452
+ send(query_tz)
453
+ response = receive
454
+
455
+ if response == MSG_PROMPT
456
+ true
457
+ elsif response[0].chr == MSG_INFO
458
+ raise MonetDBQueryError, response
459
+ end
460
+ end
461
+
462
+ # Turns auto commit on/off
463
+ def set_auto_commit(flag=true)
464
+ if flag == false
465
+ ac = " 0"
466
+ else
467
+ ac = " 1"
468
+ end
469
+
470
+ send(format_command("auto_commit " + ac))
471
+
472
+ response = receive
473
+ if response == MSG_PROMPT
474
+ @auto_commit = flag
475
+ elsif response[0].chr == MSG_INFO
476
+ raise MonetDBCommandError, response
477
+ return
478
+ end
479
+
480
+ end
481
+
482
+ # Check the auto commit status (on/off)
483
+ def auto_commit?
484
+ @auto_commit
485
+ end
486
+
487
+ # Check if monetdb is running behind the merovingian proxy and forward the connection in case
488
+ def merovingian?
489
+ if @server_name.downcase == 'merovingian'
490
+ true
491
+ else
492
+ false
493
+ end
494
+ end
495
+
496
+ def mserver?
497
+ if @server_name.downcase == 'monetdb'
498
+ true
499
+ else
500
+ false
501
+ end
502
+ end
503
+
504
+ # Check which protocol is spoken by the server
505
+ def mapi_proto_v8?
506
+ if @protocol == 8
507
+ true
508
+ else
509
+ false
510
+ end
511
+ end
512
+
513
+ def mapi_proto_v9?
514
+ if @protocol == 9
515
+ true
516
+ else
517
+ false
518
+ end
519
+ end
520
+ end
521
+
522
+ # handles transactions and savepoints. Can be used to simulate nested transactions.
523
+ class MonetDBTransaction
524
+ def initialize
525
+ @id = 0
526
+ @savepoint = ""
527
+ end
528
+
529
+ def savepoint
530
+ @savepoint = "monetdbsp" + @id.to_s
531
+ end
532
+
533
+ def release
534
+ prev_id
535
+ end
536
+
537
+ def save
538
+ next_id
539
+ end
540
+
541
+ private
542
+ def next_id
543
+ @id += 1
544
+ end
545
+
546
+ def prev_id
547
+ @id -= 1
548
+ end
549
+
550
+ end