monetdb 0.1.3 → 0.2.0

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