ruby-monetdb-sql 0.1

Sign up to get free protection for your applications and to get access to all the features.
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