monetdb 0.1.3 → 0.2.0

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.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- ZWY3ZDg1YzZlMDc5MDk4YTIyNWIwMmZlMjBhNzhmYTFhZGZiYjhhZg==
5
- data.tar.gz: !binary |-
6
- NjJiZWNiZDM2MjA5Zjg2ZmNkNGU1YTllNmU2YjA5MjkwYjNjNWEzMw==
2
+ SHA1:
3
+ metadata.gz: b124803dcab2b1006f95c5bfd252d1982350ae6c
4
+ data.tar.gz: 7472c8d1410fa5cd5e39bb79dea8059c43d5ba7a
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- NmEyNTZjNDE2NWQ5ZjIyOTMyZTNjM2NiOGQ0MWMwMjBkMjFjYTdkZWViZDUx
10
- ZGVjNzM1OWQ1YjFiNGZkNDVkYzUyYTlkOTZkODNlMDhhNjQyMjk4NTJlMDUx
11
- YWM1ZGYzMTdiMTlhNThmNGYzMjU0OThlY2MzYzYwZjAwZjFjZWU=
12
- data.tar.gz: !binary |-
13
- ZjY3NTY2ZDMzYzAzMGRhNTU4MTM2YzEwZTg2OWFiZGY1MDU1ZGZjMzUwMWVi
14
- MTMwNThmMTI1N2FlYWQ2OGNhZjI4ZmQ5MWYzZjNiYTMxMjEzYTk3YWE2NDQx
15
- ZGNiOTNhN2JmOGUzOTRjODY1NGVhY2EyYzNjZDBjOTc4NzQ3ZTM=
6
+ metadata.gz: b894e704798f71cd4791a4c019fde839a69cb75c89d30073210c8032e0e8e042da347cf786bdb8615dd86a26fe946994f6513e0d9b4ed238811da96f8ef44f71
7
+ data.tar.gz: 7bc0cd53a34cf88e7c85c5ede1343fe35e3a63ab9659b9c427a7a582f6a2df33f93f167703bd3d434cff036914faea273ce6d450ca282846428fb9ccfe29d69e
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.2
4
+ - 2.0.0
5
+ - 1.9.3
data/CHANGELOG.rdoc CHANGED
@@ -1,5 +1,9 @@
1
1
  = MonetDB CHANGELOG
2
2
 
3
+ == Version 0.2.0 (October 13, 2014)
4
+
5
+ * Complete rewrite of the gem (only focussed on querying data using SQL)
6
+
3
7
  == Version 0.1.3 (October 8, 2014)
4
8
 
5
9
  * Corrected MonetDB#select_rows (was not fetching entire result set)
data/Gemfile CHANGED
@@ -1,18 +1,3 @@
1
1
  source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
-
5
- group :development do
6
- gem "yard"
7
- end
8
-
9
- group :development, :test do
10
- gem "monetdb", :path => "."
11
- gem "pry"
12
- end
13
-
14
- group :test do
15
- gem "simplecov", :require => false
16
- gem "minitest"
17
- gem "mocha"
18
- end
data/README.rdoc CHANGED
@@ -1,6 +1,6 @@
1
- == MonetDB
1
+ == MonetDB {<img src="https://secure.travis-ci.org/archan937/monetdb.png"/>}[http://travis-ci.org/archan937/monetdb] {<img src="https://codeclimate.com/github/archan937/monetdb/badges/gpa.svg"/>}[https://codeclimate.com/github/archan937/monetdb]
2
2
 
3
- A pure Ruby database driver for MonetDB (monetdb5).
3
+ A pure Ruby database driver for MonetDB (monetdb5-sql).
4
4
 
5
5
  === Installation
6
6
 
data/Rakefile CHANGED
@@ -7,4 +7,4 @@ task :default => :test
7
7
 
8
8
  Rake::TestTask.new do |test|
9
9
  test.pattern = "test/**/test_*.rb"
10
- end
10
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.3
1
+ 0.2.0
data/lib/monetdb.rb CHANGED
@@ -1,118 +1,10 @@
1
- require "monetdb/core_ext"
2
- require "monetdb/hasher"
1
+ require "active_support/core_ext/hash/reverse_merge"
2
+
3
3
  require "monetdb/connection"
4
- require "monetdb/transaction"
5
- require "monetdb/data"
6
4
  require "monetdb/error"
7
5
  require "monetdb/version"
8
6
 
9
- # 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.
10
- #
11
- # A result set object is an instance of the MonetDB::Data class and has methods for fetching rows, moving around in the result set, obtaining column metadata, and releasing the result set.
12
- #
13
- # Records can be returned as arrays and iterators over the set.
14
- #
15
- # A database handler (dbh) is an instance of the MonetDB class.
16
- #
17
- #
18
- # = Connection management
19
- #
20
- # connect - establish a new connection
21
- # * user : username (default is monetdb)
22
- # * passwd : password (default is monetdb)
23
- # * lang : language (default is sql)
24
- # * host : server hostanme or ip (default is localhost)
25
- # * port : server port (default is 50000)
26
- # * db_name : name of the database to connect to
27
- # * auth_type : hashing function to use during authentication (default is SHA1)
28
- # connected? - returns true if there is an active connection to a server, false otherwise
29
- # reconnect - reconnect to a server
30
- # close - terminate a connection
31
- # auto_commit? - returns ture if the session is running in auto commit mode, false otherwise
32
- # auto_commit - enable/disable auto commit mode
33
- # query - fire a query
34
- #
35
- # Currently MAPI protocols 8 and 9 are supported.
36
- #
37
- #
38
- # = Managing record sets
39
- #
40
- # A record set is represented as an instance of the MonetDB::Data class; the class provides methods to manage retrieved data.
41
- #
42
- # The following methods allow to iterate over data:
43
- #
44
- # fetch - iterates over the record set and retrieves on row at a time. Each row is returned as an array
45
- # fetch_hash - iterates over columns (on cell at a time)
46
- # fetch_all_hash - returns record set entries hashed by column name orderd by column position
47
- #
48
- # To return the record set as an array (with each tuple stored as array of fields) the following method can be used:
49
- #
50
- # fetch_all - fetch all rows and store them
51
- #
52
- # Information about the retrieved record set can be obtained via the following methods:
53
- #
54
- # num_rows - returns the number of rows present in the record set
55
- # num_fields - returns the number of fields (columns) that compose the schema
56
- # name_fields - returns the (ordered) name of the schema's columns
57
- # type_fields - returns the (ordered) types list of the schema's columns
58
- #
59
- # To release a record set MonetDB::Data#free can be used.
60
- #
61
- #
62
- # = Type conversion
63
- #
64
- # A mapping between SQL and ruby type is supported. Each retrieved field can be converted to a ruby datatype via
65
- # a getTYPE method.
66
- #
67
- # The currently supported cast methods are:
68
- #
69
- # getInt - convert to an integer value
70
- # getFloat - convert to a floating point value
71
- # getString - return a string representation of the value, with trailing and leading " characters removed
72
- # getBlob - convert an SQL stored HEX string to its binary representation
73
- # getTime - return a string representation of a TIME field
74
- # getDate - return a string representation of a DATE field
75
- # getDateTime - convert a TIMESTAMP field to a ruby Time object
76
- # getChar - on Ruby >= 1.9, convert a CHAR field to char
77
- # getBool - convert a BOOLEAN field to a ruby bool object. If the value of the field is unknown, nil is returned
78
- # getNull - convert a NULL value to a nil object
79
- #
80
- #
81
- # = Transactions
82
- #
83
- # By default MonetDB works in auto_commit mode. To turn this feature off MonetDB#auto_commit(flag = false) can be used.
84
- #
85
- # Once auto_commit has been disabled it is possible to start transactions, create/delete savepoints, rollback and commit with
86
- # the usual SQL statements.
87
- #
88
- # Savepoints IDs can be generated using the MonetDB#save method. To release a savepoint ID use MonetDB#release.
89
- #
90
- # You can access savepoints (as a stack) with the MonetDB#transactions method.
91
- #
92
-
93
- class MonetDB
94
-
95
- Q_TABLE = "1" # SELECT operation
96
- Q_UPDATE = "2" # INSERT/UPDATE operations
97
- Q_CREATE = "3" # CREATE/DROP TABLE operations
98
- Q_TRANSACTION = "4" # TRANSACTION
99
- Q_PREPARE = "5" # QPREPARE message
100
- Q_BLOCK = "6" # QBLOCK message
101
-
102
- MSG_REDIRECT = "^" # auth redirection through merovingian
103
- MSG_QUERY = "&"
104
- MSG_SCHEMA_HEADER = "%"
105
- MSG_INFO = "!" # info response from mserver
106
- MSG_TUPLE = "["
107
- MSG_PROMPT = ""
108
-
109
- REPLY_SIZE = "-1"
110
- MAX_AUTH_ITERATION = 10 # maximum number of auth iterations (through merovingian) allowed
111
- MONET_ERROR = -1
112
-
113
- LANG_SQL = "sql"
114
- LANG_XQUERY = "xquery"
115
- XQUERY_OUTPUT_SEQ = true # use MonetDB XQuery's output seq
7
+ module MonetDB
116
8
 
117
9
  def self.logger=(logger)
118
10
  @logger = logger
@@ -133,13 +25,10 @@ class MonetDB
133
25
  def self.establish_connection(arg)
134
26
  config = arg.is_a?(Hash) ? arg : (configurations || {})[arg.to_s]
135
27
  if config
136
- @connection = MonetDB.new.tap do |connection|
137
- config = {language: "sql", encryption: "SHA1"}.merge(config.inject({}){|h, (k, v)| h[k.to_sym] = v; h})
138
- connection.instance_variable_set(:@config, config)
139
- connection.connect *config.values_at(:username, :password, :language, :host, :port, :database, :encryption)
140
- end
28
+ @connection = Connection.new(config)
29
+ @connection.connect
141
30
  else
142
- raise Error, "Unable to establish connection for #{arg.inspect}"
31
+ raise ConnectionError, "Unable to establish connection for #{arg.inspect}"
143
32
  end
144
33
  end
145
34
 
@@ -147,142 +36,4 @@ class MonetDB
147
36
  @connection
148
37
  end
149
38
 
150
- # Establish a new connection.
151
- # * user : username (default is monetdb)
152
- # * passwd : password (default is monetdb)
153
- # * lang : language (default is sql)
154
- # * host : server hostanme or ip (default is localhost)
155
- # * port : server port (default is 50000)
156
- # * db_name : name of the database to connect to
157
- # * auth_type : hashing function to use during authentication (default is SHA1)
158
- def connect(username = "monetdb", password = "monetdb", lang = "sql", host = "127.0.0.1", port = "50000", db_name = "test", auth_type = "SHA1")
159
- # TODO: Handle pools of connections
160
- @username = username
161
- @password = password
162
- @lang = lang
163
- @host = host
164
- @port = port
165
- @db_name = db_name
166
- @auth_type = auth_type
167
- @connection = MonetDB::Connection.new(user = @username, passwd = @password, lang = @lang, host = @host, port = @port)
168
- @connection.connect(@db_name, @auth_type)
169
- end
170
-
171
- # Send a <b>user submitted</b> query to the server and store the response.
172
- #
173
- # Returns an instance of MonetDB::Data.
174
- def query(q = "")
175
- unless @connection.nil?
176
- @data = MonetDB::Data.new(@connection)
177
- @data.execute(q)
178
- end
179
- @data
180
- end
181
-
182
- # Send a <b>user submitted select</b> query to the server and store the response.
183
- #
184
- # Returns an array of arrays with casted values.
185
- def select_rows(qry)
186
- return if @connection.nil?
187
- data = MonetDB::Data.new @connection
188
-
189
- start = Time.now
190
- @connection.send(data.send(:format_query, qry))
191
- response = @connection.receive.split("\n")
192
-
193
- if (row = response[0]).chr == MSG_INFO
194
- raise QueryError, row
195
- end
196
-
197
- headers, rows = response.partition{|x| [MSG_SCHEMA_HEADER, MSG_QUERY].include? x[0]}
198
- data.send :receive_record_set, headers.join("\n")
199
- query_header = data.send :parse_header_query, headers.shift
200
- table_header = data.send :parse_header_table, headers
201
- rows = rows.join("\n")
202
-
203
- row_count = rows.split("\t]\n").size
204
- while row_count < query_header["rows"].to_i
205
- received = @connection.receive
206
- row_count += received.scan("\t]\n").size
207
- rows << received
208
- end
209
- rows = rows.split("\t]\n")
210
-
211
- log :info, "\n SQL (#{((Time.now - start) * 1000).round(1)}ms) #{qry}"
212
-
213
- column_types = table_header["columns_type"]
214
- types = table_header["columns_name"].collect{|x| column_types[x]}
215
-
216
- rows.collect do |row|
217
- parsed, values = [], row.gsub(/^\[\s*/, "").split(",\t")
218
- values.each_with_index do |value, index|
219
- parsed << begin
220
- unless value.strip == "NULL"
221
- case types[index]
222
- when "bigint", "int"
223
- value.to_i
224
- when "double"
225
- value.to_f
226
- else
227
- value.strip.gsub(/(^"|"$|\\|\")/, "").force_encoding("UTF-8")
228
- end
229
- end
230
- end
231
- end
232
- parsed
233
- end
234
- end
235
-
236
- # Returns whether a "connection" object exists.
237
- def connected?
238
- !@connection.nil?
239
- end
240
-
241
- # Reconnect to the server.
242
- def reconnect
243
- if @connection != nil
244
- self.close
245
- @connection = MonetDB::Connection.new(user = @username, passwd = @password, lang = @lang, host = @host, port = @port)
246
- @connection.connect(db_name = @db_name, auth_type = @auth_type)
247
- end
248
- end
249
-
250
- # Turn auto commit on/off.
251
- def auto_commit(flag = true)
252
- @connection.set_auto_commit(flag)
253
- end
254
-
255
- # Returns the current auto commit (on/off) setting.
256
- def auto_commit?
257
- @connection.auto_commit?
258
- end
259
-
260
- # Returns the name of the last savepoint in a transactions pool.
261
- def transactions
262
- @connection.savepoint
263
- end
264
-
265
- # Create a new savepoint ID.
266
- def save
267
- @connection.transactions.save
268
- end
269
-
270
- # Release a savepoint ID.
271
- def release
272
- @connection.transactions.release
273
- end
274
-
275
- # Close an active connection.
276
- def close
277
- @connection.disconnect
278
- @connection = nil
279
- end
280
-
281
- private
282
-
283
- # Log message.
284
- def log(type, msg)
285
- MonetDB.logger.send type, msg if MonetDB.logger
286
- end
287
-
288
39
  end
@@ -1,442 +1,124 @@
1
1
  require "socket"
2
- require "time"
3
- require "uri"
2
+ require "monetdb/connection/messages"
3
+ require "monetdb/connection/setup"
4
+ require "monetdb/connection/query"
4
5
 
5
- #
6
- # Implements the MAPI communication protocol
7
- #
8
-
9
- class MonetDB
6
+ module MonetDB
10
7
  class Connection
11
8
 
12
- # enable debug output
13
- @@DEBUG = false
14
-
15
- # hour in seconds, used for timezone calculation
16
- @@HOUR = 3600
17
-
18
- # maximum size (in bytes) for a monetdb message to be sent
19
- @@MAX_MESSAGE_SIZE = 32766
20
-
21
- # endianness of a message sent to the server
22
- @@CLIENT_ENDIANNESS = "BIG"
23
-
24
- # MAPI protocols supported by the driver
25
- @@SUPPORTED_PROTOCOLS = [8, 9]
26
-
27
- attr_reader :socket, :auto_commit, :transactions, :lang
28
-
29
- # A new instance of MonetDB::Connection.
30
- # * user : username (default is monetdb)
31
- # * passwd : password (default is monetdb)
32
- # * lang : language (default is sql)
33
- # * host : server hostanme or ip (default is localhost)
34
- # * port : server port (default is 50000)
35
- def initialize(user = "monetdb", passwd = "monetdb", lang = "sql", host = "127.0.0.1", port = "50000")
36
- @user = user
37
- @passwd = passwd
38
- @lang = lang.downcase
39
- @host = host
40
- @port = port
41
-
42
- @client_endianness = @@CLIENT_ENDIANNESS
43
- @auth_iteration = 0
44
- @connection_established = false
45
- @transactions = MonetDB::Transaction.new
46
- end
47
-
48
- # Connect to the database, creates a new socket.
49
- def connect(db_name = "demo", auth_type = "SHA1")
50
- @database = db_name
51
- @auth_type = auth_type
52
- @socket = TCPSocket.new(@host, @port.to_i)
53
-
54
- if real_connect
55
- if @lang == LANG_SQL
56
- set_timezone
57
- set_reply_size
58
- elsif (@lang == LANG_XQUERY) and XQUERY_OUTPUT_SEQ
59
- # require xquery output to be in seq format
60
- send(format_command("output seq"))
61
- end
62
- true
63
- else
64
- false
65
- end
66
- end
67
-
68
- # Perform a real connection; retrieve challenge, proxy through merovinginan, build challenge and set the timezone.
69
- def real_connect
70
- server_challenge = retrieve_server_challenge()
71
-
72
- if server_challenge != nil
73
- salt = server_challenge.split(':')[0]
74
- @server_name = server_challenge.split(':')[1]
75
- @protocol = server_challenge.split(':')[2].to_i
76
- @supported_auth_types = server_challenge.split(':')[3].split(',')
77
- @server_endianness = server_challenge.split(':')[4]
78
- if @protocol == 9
79
- @pwhash = server_challenge.split(':')[5]
80
- end
81
- else
82
- raise MonetDB::ConnectionError, "Error: server returned an empty challenge string."
83
- end
84
-
85
- # The server supports only RIPMED168 or crypt as an authentication hash function, but the driver does not.
86
- if @supported_auth_types.length == 1
87
- auth = @supported_auth_types[0]
88
- if auth.upcase == "RIPEMD160" or auth.upcase == "CRYPT"
89
- raise MonetDB::ConnectionError, auth.upcase + " " + ": algorithm not supported by ruby-monetdb."
90
- end
91
- end
92
-
93
- # If the server protocol version is not 8: abort and notify the user.
94
- if @@SUPPORTED_PROTOCOLS.include?(@protocol) == false
95
- raise MonetDB::ProtocolError, "Protocol not supported. The current implementation of ruby-monetdb works with MAPI protocols #{@@SUPPORTED_PROTOCOLS} only."
96
-
97
- elsif mapi_proto_v8?
98
- reply = build_auth_string_v8(@auth_type, salt, @database)
99
- elsif mapi_proto_v9?
100
- reply = build_auth_string_v9(@auth_type, salt, @database)
101
- end
102
-
103
- if @socket != nil
104
- @connection_established = true
105
-
106
- send(reply)
107
- monetdb_auth = receive
108
-
109
- if monetdb_auth.length == 0
110
- # auth succedeed
111
- true
112
- else
113
- if monetdb_auth[0].chr == MSG_REDIRECT
114
- #redirection
115
-
116
- redirects = [] # store a list of possible redirects
117
-
118
- monetdb_auth.split('\n').each do |m|
119
- # strip the trailing ^mapi:
120
- # if the redirect string start with something != "^mapi:" or is empty, the redirect is invalid and shall not be included.
121
- if m[0..5] == "^mapi:"
122
- redir = m[6..m.length]
123
- # url parse redir
124
- redirects.push(redir)
125
- else
126
- $stderr.print "Warning: Invalid Redirect #{m}"
127
- end
128
- end
129
-
130
- if redirects.size == 0
131
- raise MonetDB::ConnectionError, "No valid redirect received"
132
- else
133
- begin
134
- uri = URI.split(redirects[0])
135
- # Splits the string on following parts and returns array with result:
136
- #
137
- # * Scheme
138
- # * Userinfo
139
- # * Host
140
- # * Port
141
- # * Registry
142
- # * Path
143
- # * Opaque
144
- # * Query
145
- # * Fragment
146
- server_name = uri[0]
147
- host = uri[2]
148
- port = uri[3]
149
- database = uri[5].gsub(/^\//, '') if uri[5] != nil
150
- rescue URI::InvalidURIError
151
- raise MonetDB::ConnectionError, "Invalid redirect: #{redirects[0]}"
152
- end
153
- end
154
-
155
- if server_name == "merovingian"
156
- if @auth_iteration <= 10
157
- @auth_iteration += 1
158
- real_connect
159
- else
160
- raise MonetDB::ConnectionError, "Merovingian: too many iterations while proxying."
161
- end
162
- elsif server_name == "monetdb"
163
- begin
164
- @socket.close
165
- rescue
166
- raise MonetDB::ConnectionError, "I/O error while closing connection to #{@socket}"
167
- end
168
- # reinitialize a connection
169
- @host = host
170
- @port = port
171
-
172
- connect(database, @auth_type)
173
- else
174
- @connection_established = false
175
- raise MonetDB::ConnectionError, monetdb_auth
176
- end
177
- elsif monetdb_auth[0].chr == MSG_INFO
178
- raise MonetDB::ConnectionError, monetdb_auth
179
- end
180
- end
181
- end
182
- end
183
-
184
- def savepoint
185
- @transactions.savepoint
186
- end
187
-
188
- # Formats a <i>command</i> string so that it can be parsed by the server.
189
- def format_command(x)
190
- "X" + x + "\n"
191
- end
192
-
193
- # Send an 'export' command to the server.
194
- def set_export(id, idx, offset)
195
- send(format_command("export " + id.to_s + " " + idx.to_s + " " + offset.to_s ))
9
+ include Messages
10
+ include Setup
11
+ include Query
12
+
13
+ Q_TABLE = "1" # SELECT statement
14
+ Q_UPDATE = "2" # INSERT/UPDATE statement
15
+ Q_CREATE = "3" # CREATE/DROP TABLE statement
16
+ Q_TRANSACTION = "4" # TRANSACTION
17
+ Q_PREPARE = "5" # QPREPARE message
18
+ Q_BLOCK = "6" # QBLOCK message
19
+
20
+ MSG_PROMPT = ""
21
+ MSG_ERROR = "!"
22
+ MSG_REDIRECT = "^"
23
+ MSG_QUERY = "&"
24
+ MSG_SCHEME = "%"
25
+ MSG_TUPLE = "["
26
+
27
+ ENDIANNESS = "BIG"
28
+ LANG = "sql"
29
+ REPLY_SIZE = "-1"
30
+ MAX_MSG_SIZE = 32766
31
+
32
+ MAPI_V8 = "8"
33
+ MAPI_V9 = "9"
34
+ PROTOCOLS = [MAPI_V8, MAPI_V9]
35
+
36
+ AUTH_MD5 = "MD5"
37
+ AUTH_SHA512 = "SHA512"
38
+ AUTH_SHA384 = "SHA384"
39
+ AUTH_SHA256 = "SHA256"
40
+ AUTH_SHA1 = "SHA1"
41
+ AUTH_PLAIN = "PLAIN"
42
+ AUTH_TYPES = [AUTH_MD5, AUTH_SHA512, AUTH_SHA384, AUTH_SHA256, AUTH_SHA1, AUTH_PLAIN]
43
+
44
+ def initialize(config = {})
45
+ @config = {
46
+ :host => "localhost",
47
+ :port => 50000,
48
+ :username => "monetdb",
49
+ :password => "monetdb"
50
+ }.merge(
51
+ config.inject({}){|h, (k, v)| h[k.to_sym] = v; h}
52
+ )
53
+ end
54
+
55
+ def connect
56
+ disconnect if connected?
57
+ @socket = TCPSocket.new config[:host], config[:port].to_i
58
+ setup
59
+ true
60
+ end
61
+
62
+ def connected?
63
+ !socket.nil?
196
64
  end
197
65
 
198
- # Send a 'reply_size' command to the server.
199
- def set_reply_size
200
- send(format_command(("reply_size " + REPLY_SIZE)))
201
- response = receive
202
-
203
- if response == MSG_PROMPT
204
- true
205
- elsif response[0] == MSG_INFO
206
- raise MonetDB::CommandError, "Unable to set reply_size: #{response}"
207
- end
208
- end
209
-
210
- def set_output_seq
211
- send(format_command("output seq"))
212
- end
213
-
214
- # Disconnect from server.
215
66
  def disconnect
216
- if @connection_established
217
- begin
218
- @socket.close
219
- rescue => e
220
- $stderr.print e
221
- end
222
- else
223
- raise MonetDB::ConnectionError, "No connection established."
224
- end
225
- end
226
-
227
- # Send data to a monetdb5 server instance and returns server response.
228
- def send(data)
229
- encode_message(data).each do |m|
230
- @socket.write(m)
231
- end
67
+ socket.disconnect if connected?
68
+ @socket = nil
232
69
  end
233
70
 
234
- # Receive data from a monetdb5 server instance.
235
- def receive
236
- is_final, chunk_size = recv_decode_hdr
237
-
238
- if chunk_size == 0
239
- return "" # needed on ruby-1.8.6 linux/64bit; recv(0) hangs on this configuration.
240
- end
241
-
242
- data = @socket.recv(chunk_size)
71
+ private
243
72
 
244
- if is_final == false
245
- while is_final == false
246
- is_final, chunk_size = recv_decode_hdr
247
- data += @socket.recv(chunk_size)
248
- end
249
- end
250
-
251
- data
73
+ def config
74
+ @config
252
75
  end
253
76
 
254
- # Build and authentication string given the parameters submitted by the user (MAPI protocol v8).
255
- def build_auth_string_v8(auth_type, salt, db_name)
256
- # seed = password + salt
257
- if (auth_type.upcase == "MD5" or auth_type.upcase == "SHA1") and @supported_auth_types.include?(auth_type.upcase)
258
- auth_type = auth_type.upcase
259
- digest = Hasher.new(auth_type, @passwd+salt)
260
- hashsum = digest.hashsum
261
- elsif auth_type.downcase == "plain" or not @supported_auth_types.include?(auth_type.upcase)
262
- auth_type = 'plain'
263
- hashsum = @passwd + salt
264
-
265
- elsif auth_type.downcase == "crypt"
266
- auth_type = @supported_auth_types[@supported_auth_types.index(auth_type)+1]
267
- $stderr.print "The selected hashing algorithm is not supported by the Ruby driver. #{auth_type} will be used instead."
268
- digest = Hasher.new(auth_type, @passwd+salt)
269
- hashsum = digest.hashsum
270
- else
271
- # The user selected an auth type not supported by the server.
272
- raise MonetDB::ConnectionError, "#{auth_type} not supported by the server. Please choose one from #{@supported_auth_types}"
273
-
274
- end
275
- # Build the reply message with header
276
- reply = @client_endianness + ":" + @user + ":{" + auth_type + "}" + hashsum + ":" + @lang + ":" + db_name + ":"
77
+ def socket
78
+ @socket
277
79
  end
278
80
 
279
- # Build and authentication string given the parameters submitted by the user (MAPI protocol v9).
280
- def build_auth_string_v9(auth_type, salt, db_name)
281
- if (auth_type.upcase == "MD5" or auth_type.upcase == "SHA1") and @supported_auth_types.include?(auth_type.upcase)
282
- auth_type = auth_type.upcase
283
- # Hash the password
284
- pwhash = Hasher.new(@pwhash, @passwd)
285
-
286
- digest = Hasher.new(auth_type, pwhash.hashsum + salt)
287
- hashsum = digest.hashsum
288
-
289
- elsif auth_type.downcase == "plain" # or not @supported_auth_types.include?(auth_type.upcase)
290
- # Keep it for compatibility with merovingian
291
- auth_type = 'plain'
292
- hashsum = @passwd + salt
293
- elsif @supported_auth_types.include?(auth_type.upcase)
294
- if auth_type.upcase == "RIPEMD160"
295
- auth_type = @supported_auth_types[@supported_auth_types.index(auth_type)+1]
296
- $stderr.print "The selected hashing algorithm is not supported by the Ruby driver. #{auth_type} will be used instead."
297
- end
298
- # Hash the password
299
- pwhash = Hasher.new(@pwhash, @passwd)
300
-
301
- digest = Hasher.new(auth_type, pwhash.hashsum + salt)
302
- hashsum = digest.hashsum
303
- else
304
- # The user selected an auth type not supported by the server.
305
- raise MonetDB::ConnectionError, "#{auth_type} not supported by the server. Please choose one from #{@supported_auth_types}"
306
- end
307
- # Build the reply message with header
308
- reply = @client_endianness + ":" + @user + ":{" + auth_type + "}" + hashsum + ":" + @lang + ":" + db_name + ":"
81
+ def log(type, msg)
82
+ MonetDB.logger.send(type, msg) if MonetDB.logger
309
83
  end
310
84
 
311
- # Build a message to be sent to the server.
312
- def encode_message(msg = "")
313
- message = Array.new
314
- data = ""
315
-
316
- hdr = 0 # package header
317
- pos = 0
318
- is_final = false # last package in the stream
319
-
320
- while (! is_final)
321
- data = msg[pos..pos+[@@MAX_MESSAGE_SIZE.to_i, (msg.length - pos).to_i].min]
322
- pos += data.length
85
+ def read
86
+ raise ConnectionError, "Not connected to server" unless connected?
323
87
 
324
- if (msg.length - pos) == 0
325
- last_bit = 1
326
- is_final = true
327
- else
328
- last_bit = 0
329
- end
88
+ length, last_chunk = read_length
89
+ data, iterations = "", 0
330
90
 
331
- hdr = [(data.length << 1) | last_bit].pack('v')
332
-
333
- message << hdr + data.to_s # Short Little Endian Encoding
91
+ while (length > 0) && (iterations < 1000) do
92
+ received = socket.recv(length)
93
+ data << received
94
+ length -= received.bytesize
95
+ iterations += 1
334
96
  end
97
+ data << read unless last_chunk
335
98
 
336
- message.freeze # freeze and return the encode message
337
- end
338
-
339
- # Used as the first step in the authentication phase; retrieves a challenge string from the server.
340
- def retrieve_server_challenge
341
- server_challenge = receive
342
- end
343
-
344
- # Reads and decodes the header of a server message.
345
- def recv_decode_hdr
346
- if @socket != nil
347
- fb = @socket.recv(1)
348
- sb = @socket.recv(1)
349
-
350
- # Use execeptions handling to keep compatibility between different ruby
351
- # versions.
352
- #
353
- # Chars are treated differently in ruby 1.8 and 1.9
354
- # try do to ascii to int conversion using ord (ruby 1.9)
355
- # and if it fail fallback to character.to_i (ruby 1.8)
356
- begin
357
- fb = fb[0].ord
358
- sb = sb[0].ord
359
- rescue NoMethodError => one_eight
360
- fb = fb[0].to_i
361
- sb = sb[0].to_i
362
- end
363
-
364
- chunk_size = (sb << 7) | (fb >> 1)
365
-
366
- is_final = false
367
- if ( (fb & 1) == 1 )
368
- is_final = true
369
-
370
- end
371
- # return the size of the chunk (in bytes)
372
- return is_final, chunk_size
373
- else
374
- raise MonetDB::SocketError, "Error while receiving data\n"
375
- end
99
+ data
376
100
  end
377
101
 
378
- # Sets the time zone according to the Operating System settings.
379
- def set_timezone
380
- tz = Time.new
381
- tz_offset = tz.gmt_offset / @@HOUR
382
-
383
- if tz_offset <= 9 # verify minute count!
384
- tz_offset = "'+0" + tz_offset.to_s + ":00'"
385
- else
386
- tz_offset = "'+" + tz_offset.to_s + ":00'"
387
- end
388
- query_tz = "sSET TIME ZONE INTERVAL " + tz_offset + " HOUR TO MINUTE;"
389
-
390
- # Perform the query directly within the method
391
- send(query_tz)
392
- response = receive
393
-
394
- if response == MSG_PROMPT
395
- true
396
- elsif response[0].chr == MSG_INFO
397
- raise MonetDB::QueryError, response
398
- end
102
+ def read_length
103
+ bytes = socket.recv(2).unpack("v")[0]
104
+ [(bytes >> 1), (bytes & 1) == 1]
399
105
  end
400
106
 
401
- # Turns auto commit on/off.
402
- def set_auto_commit(flag = true)
403
- if flag == false
404
- ac = " 0"
405
- else
406
- ac = " 1"
107
+ def write(message)
108
+ raise ConnectionError, "Not connected to server" unless connected?
109
+ pack(message).each do |chunk|
110
+ socket.write(chunk)
407
111
  end
408
-
409
- send(format_command("auto_commit " + ac))
410
- response = receive
411
-
412
- if response == MSG_PROMPT
413
- @auto_commit = flag
414
- elsif response[0].chr == MSG_INFO
415
- raise MonetDB::CommandError, response
416
- end
417
- end
418
-
419
- # Check the auto commit status (on/off).
420
- def auto_commit?
421
- !!@auto_commit
422
- end
423
-
424
- # Check if monetdb is running behind the merovingian proxy and forward the connection in case.
425
- def merovingian?
426
- @server_name.downcase == "merovingian"
427
- end
428
-
429
- def mserver?
430
- @server_name.downcase == "monetdb"
431
- end
432
-
433
- # Check which protocol is spoken by the server.
434
- def mapi_proto_v8?
435
- @protocol == 8
112
+ true
436
113
  end
437
114
 
438
- def mapi_proto_v9?
439
- @protocol == 9
115
+ def pack(message)
116
+ chunks = message.scan(/.{1,#{MAX_MSG_SIZE}}/m)
117
+ chunks.each_with_index.to_a.collect do |chunk, index|
118
+ last_bit = (index == chunks.size - 1) ? 1 : 0
119
+ length = [(chunk.size << 1) | last_bit].pack("v")
120
+ "#{length}#{chunk}"
121
+ end.freeze
440
122
  end
441
123
 
442
124
  end