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 +5 -13
- data/.travis.yml +5 -0
- data/CHANGELOG.rdoc +4 -0
- data/Gemfile +0 -15
- data/README.rdoc +2 -2
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/monetdb.rb +6 -255
- data/lib/monetdb/connection.rb +94 -412
- data/lib/monetdb/connection/messages.rb +16 -0
- data/lib/monetdb/connection/query.rb +136 -0
- data/lib/monetdb/connection/setup.rb +125 -0
- data/lib/monetdb/error.rb +6 -12
- data/lib/monetdb/version.rb +3 -3
- data/monetdb.gemspec +6 -3
- data/script/console +16 -1
- data/test/test_helper.rb +3 -0
- data/test/test_helper/minitest.rb +7 -0
- data/test/test_helper/simple_connection.rb +13 -0
- data/test/unit/connection/test_messages.rb +48 -0
- data/test/unit/connection/test_query.rb +276 -0
- data/test/unit/connection/test_setup.rb +364 -0
- data/test/unit/test_connection.rb +178 -0
- data/test/unit/test_monetdb.rb +77 -1
- metadata +62 -24
- data/lib/monetdb/core_ext.rb +0 -1
- data/lib/monetdb/core_ext/string.rb +0 -67
- data/lib/monetdb/data.rb +0 -300
- data/lib/monetdb/hasher.rb +0 -40
- data/lib/monetdb/transaction.rb +0 -36
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
NjJiZWNiZDM2MjA5Zjg2ZmNkNGU1YTllNmU2YjA5MjkwYjNjNWEzMw==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b124803dcab2b1006f95c5bfd252d1982350ae6c
|
4
|
+
data.tar.gz: 7472c8d1410fa5cd5e39bb79dea8059c43d5ba7a
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
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
data/CHANGELOG.rdoc
CHANGED
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
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/monetdb.rb
CHANGED
@@ -1,118 +1,10 @@
|
|
1
|
-
require "
|
2
|
-
|
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
|
-
|
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 =
|
137
|
-
|
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
|
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 [1m[35mSQL (#{((Time.now - start) * 1000).round(1)}ms)[0m #{qry}[0m"
|
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
|
data/lib/monetdb/connection.rb
CHANGED
@@ -1,442 +1,124 @@
|
|
1
1
|
require "socket"
|
2
|
-
require "
|
3
|
-
require "
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
217
|
-
|
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
|
-
|
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
|
-
|
245
|
-
|
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
|
-
|
255
|
-
|
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
|
-
|
280
|
-
|
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
|
-
|
312
|
-
|
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
|
-
|
325
|
-
|
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
|
-
|
332
|
-
|
333
|
-
|
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
|
-
|
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
|
-
|
379
|
-
|
380
|
-
|
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
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
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
|
439
|
-
|
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
|