activerecord-hbase-adapter 0.0.7 → 0.0.71
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/activerecord-hbase-adapter.gemspec +9 -2
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +801 -0
- data/lib/active_record/connection_adapters/hbase/client.rb +210 -0
- data/lib/active_record/connection_adapters/hbase/error.rb +78 -0
- data/lib/active_record/connection_adapters/hbase/result.rb +45 -0
- data/lib/activerecord-hbase-adapter/version.rb +1 -1
- metadata +19 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d20cfbf09998f3be62222929739135c35bcfe1b7
|
4
|
+
data.tar.gz: 5262a6b3e72274fdabb48b0fdb51a9e4acc8d445
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d54be906d76ad9e6a51653f0425f250015a20fa8eefa7b08cb6b9b3714f893fb8250040c5baae48abec0f37a46f81ce351f16f71d8f266a418ac263a374225f
|
7
|
+
data.tar.gz: d9002bcd7755455128d4f714af265a1105b6c2485cc26143f5bcb91b8c0a076cd539e1fae51cca60371ab01b77a19f8e524c5834c57822282d231524422e83a4
|
@@ -11,8 +11,12 @@ Gem::Specification.new do |gem|
|
|
11
11
|
gem.files = [ 'Gemfile',
|
12
12
|
'activerecord-hbase-adapter.gemspec',
|
13
13
|
'lib/activerecord-hbase-adapter/version.rb',
|
14
|
-
'lib/active_record/connection_adapters/hbase_adapter.rb'
|
15
|
-
|
14
|
+
'lib/active_record/connection_adapters/hbase_adapter.rb',
|
15
|
+
'lib/active_record/connection_adapters/abstract_mysql_adapter.rb',
|
16
|
+
'lib/active_record/connection_adapters/hbase/error.rb',
|
17
|
+
'lib/active_record/connection_adapters/hbase/result.rb',
|
18
|
+
'lib/active_record/connection_adapters/hbase/client.rb']
|
19
|
+
#gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
16
20
|
gem.name = "activerecord-hbase-adapter"
|
17
21
|
gem.require_paths = ["lib"]
|
18
22
|
gem.version = Activerecord::Hbase::Adapter::VERSION
|
@@ -24,14 +28,17 @@ Gem::Specification.new do |gem|
|
|
24
28
|
gem.add_runtime_dependency(%q<hipster_sql_to_hbase>, [">= 0"])
|
25
29
|
gem.add_runtime_dependency(%q<httparty>, [">= 0"])
|
26
30
|
gem.add_runtime_dependency(%q<msgpack>, [">= 0"])
|
31
|
+
gem.add_runtime_dependency(%q<nokogiri>, [">= 0"])
|
27
32
|
else
|
28
33
|
gem.add_dependency(%q<hipster_sql_to_hbase>, [">= 0"])
|
29
34
|
gem.add_dependency(%q<httparty>, [">= 0"])
|
30
35
|
gem.add_dependency(%q<msgpack>, [">= 0"])
|
36
|
+
gem.add_dependency(%q<nokogiri>, [">= 0"])
|
31
37
|
end
|
32
38
|
else
|
33
39
|
gem.add_dependency(%q<hipster_sql_to_hbase>, [">= 0"])
|
34
40
|
gem.add_dependency(%q<httparty>, [">= 0"])
|
35
41
|
gem.add_dependency(%q<msgpack>, [">= 0"])
|
42
|
+
gem.add_dependency(%q<nokogiri>, [">= 0"])
|
36
43
|
end
|
37
44
|
end
|
@@ -0,0 +1,801 @@
|
|
1
|
+
require 'arel/visitors/bind_visitor'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
class AbstractMysqlAdapter < AbstractAdapter
|
6
|
+
include Savepoints
|
7
|
+
|
8
|
+
class SchemaCreation < AbstractAdapter::SchemaCreation
|
9
|
+
|
10
|
+
def visit_AddColumn(o)
|
11
|
+
add_column_position!(super, column_options(o))
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def visit_ChangeColumnDefinition(o)
|
16
|
+
column = o.column
|
17
|
+
options = o.options
|
18
|
+
sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale])
|
19
|
+
change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}"
|
20
|
+
add_column_options!(change_column_sql, options.merge(column: column))
|
21
|
+
add_column_position!(change_column_sql, options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_column_position!(sql, options)
|
25
|
+
if options[:first]
|
26
|
+
sql << " FIRST"
|
27
|
+
elsif options[:after]
|
28
|
+
sql << " AFTER #{quote_column_name(options[:after])}"
|
29
|
+
end
|
30
|
+
sql
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def schema_creation
|
35
|
+
SchemaCreation.new self
|
36
|
+
end
|
37
|
+
|
38
|
+
class Column < ConnectionAdapters::Column # :nodoc:
|
39
|
+
attr_reader :collation, :strict, :extra
|
40
|
+
|
41
|
+
def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
|
42
|
+
@strict = strict
|
43
|
+
@collation = collation
|
44
|
+
@extra = extra
|
45
|
+
super(name, default, sql_type, null)
|
46
|
+
end
|
47
|
+
|
48
|
+
def extract_default(default)
|
49
|
+
if blob_or_text_column?
|
50
|
+
if default.blank?
|
51
|
+
null || strict ? nil : ''
|
52
|
+
else
|
53
|
+
raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
|
54
|
+
end
|
55
|
+
elsif missing_default_forged_as_empty_string?(default)
|
56
|
+
nil
|
57
|
+
else
|
58
|
+
super
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def has_default?
|
63
|
+
return false if blob_or_text_column? #mysql forbids defaults on blob and text columns
|
64
|
+
super
|
65
|
+
end
|
66
|
+
|
67
|
+
def blob_or_text_column?
|
68
|
+
sql_type =~ /blob/i || type == :text
|
69
|
+
end
|
70
|
+
|
71
|
+
# Must return the relevant concrete adapter
|
72
|
+
def adapter
|
73
|
+
raise NotImplementedError
|
74
|
+
end
|
75
|
+
|
76
|
+
def case_sensitive?
|
77
|
+
collation && !collation.match(/_ci$/)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def simplified_type(field_type)
|
83
|
+
return :boolean if adapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
|
84
|
+
|
85
|
+
case field_type
|
86
|
+
when /enum/i, /set/i then :string
|
87
|
+
when /year/i then :integer
|
88
|
+
when /bit/i then :binary
|
89
|
+
else
|
90
|
+
super
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def extract_limit(sql_type)
|
95
|
+
case sql_type
|
96
|
+
when /^enum\((.+)\)/i
|
97
|
+
$1.split(',').map{|enum| enum.strip.length - 2}.max
|
98
|
+
when /blob|text/i
|
99
|
+
case sql_type
|
100
|
+
when /tiny/i
|
101
|
+
255
|
102
|
+
when /medium/i
|
103
|
+
16777215
|
104
|
+
when /long/i
|
105
|
+
2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
|
106
|
+
else
|
107
|
+
super # we could return 65535 here, but we leave it undecorated by default
|
108
|
+
end
|
109
|
+
when /^bigint/i; 8
|
110
|
+
when /^int/i; 4
|
111
|
+
when /^mediumint/i; 3
|
112
|
+
when /^smallint/i; 2
|
113
|
+
when /^tinyint/i; 1
|
114
|
+
when /^float/i; 24
|
115
|
+
when /^double/i; 53
|
116
|
+
else
|
117
|
+
super
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# MySQL misreports NOT NULL column default when none is given.
|
122
|
+
# We can't detect this for columns which may have a legitimate ''
|
123
|
+
# default (string) but we can for others (integer, datetime, boolean,
|
124
|
+
# and the rest).
|
125
|
+
#
|
126
|
+
# Test whether the column has default '', is not null, and is not
|
127
|
+
# a type allowing default ''.
|
128
|
+
def missing_default_forged_as_empty_string?(default)
|
129
|
+
type != :string && !null && default == ''
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
##
|
134
|
+
# :singleton-method:
|
135
|
+
# By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
|
136
|
+
# as boolean. If you wish to disable this emulation (which was the default
|
137
|
+
# behavior in versions 0.13.1 and earlier) you can add the following line
|
138
|
+
# to your application.rb file:
|
139
|
+
#
|
140
|
+
# ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
|
141
|
+
class_attribute :emulate_booleans
|
142
|
+
self.emulate_booleans = true
|
143
|
+
|
144
|
+
LOST_CONNECTION_ERROR_MESSAGES = [
|
145
|
+
"Server shutdown in progress",
|
146
|
+
"Broken pipe",
|
147
|
+
"Lost connection to MySQL server during query",
|
148
|
+
"MySQL server has gone away" ]
|
149
|
+
|
150
|
+
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
|
151
|
+
|
152
|
+
NATIVE_DATABASE_TYPES = {
|
153
|
+
:primary_key => "int(11) auto_increment PRIMARY KEY",
|
154
|
+
:string => { :name => "varchar", :limit => 255 },
|
155
|
+
:text => { :name => "text" },
|
156
|
+
:integer => { :name => "int", :limit => 4 },
|
157
|
+
:float => { :name => "float" },
|
158
|
+
:decimal => { :name => "decimal" },
|
159
|
+
:datetime => { :name => "datetime" },
|
160
|
+
:timestamp => { :name => "datetime" },
|
161
|
+
:time => { :name => "time" },
|
162
|
+
:date => { :name => "date" },
|
163
|
+
:binary => { :name => "blob" },
|
164
|
+
:boolean => { :name => "tinyint", :limit => 1 }
|
165
|
+
}
|
166
|
+
|
167
|
+
INDEX_TYPES = [:fulltext, :spatial]
|
168
|
+
INDEX_USINGS = [:btree, :hash]
|
169
|
+
|
170
|
+
class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
|
171
|
+
include Arel::Visitors::BindVisitor
|
172
|
+
end
|
173
|
+
|
174
|
+
# FIXME: Make the first parameter more similar for the two adapters
|
175
|
+
def initialize(connection, logger, connection_options, config)
|
176
|
+
super(connection, logger)
|
177
|
+
@connection_options, @config = connection_options, config
|
178
|
+
@quoted_column_names, @quoted_table_names = {}, {}
|
179
|
+
|
180
|
+
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
|
181
|
+
@prepared_statements = true
|
182
|
+
@visitor = Arel::Visitors::MySQL.new self
|
183
|
+
else
|
184
|
+
@visitor = unprepared_visitor
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def adapter_name #:nodoc:
|
189
|
+
self.class::ADAPTER_NAME
|
190
|
+
end
|
191
|
+
|
192
|
+
# Returns true, since this connection adapter supports migrations.
|
193
|
+
def supports_migrations?
|
194
|
+
true
|
195
|
+
end
|
196
|
+
|
197
|
+
def supports_primary_key?
|
198
|
+
true
|
199
|
+
end
|
200
|
+
|
201
|
+
def supports_bulk_alter? #:nodoc:
|
202
|
+
true
|
203
|
+
end
|
204
|
+
|
205
|
+
# Technically MySQL allows to create indexes with the sort order syntax
|
206
|
+
# but at the moment (5.5) it doesn't yet implement them
|
207
|
+
def supports_index_sort_order?
|
208
|
+
true
|
209
|
+
end
|
210
|
+
|
211
|
+
def type_cast(value, column)
|
212
|
+
case value
|
213
|
+
when TrueClass
|
214
|
+
1
|
215
|
+
when FalseClass
|
216
|
+
0
|
217
|
+
else
|
218
|
+
super
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# MySQL 4 technically support transaction isolation, but it is affected by a bug
|
223
|
+
# where the transaction level gets persisted for the whole session:
|
224
|
+
#
|
225
|
+
# http://bugs.mysql.com/bug.php?id=39170
|
226
|
+
def supports_transaction_isolation?
|
227
|
+
version[0] >= 5
|
228
|
+
end
|
229
|
+
|
230
|
+
def native_database_types
|
231
|
+
NATIVE_DATABASE_TYPES
|
232
|
+
end
|
233
|
+
|
234
|
+
def index_algorithms
|
235
|
+
{ default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
|
236
|
+
end
|
237
|
+
|
238
|
+
# HELPER METHODS ===========================================
|
239
|
+
|
240
|
+
# The two drivers have slightly different ways of yielding hashes of results, so
|
241
|
+
# this method must be implemented to provide a uniform interface.
|
242
|
+
def each_hash(result) # :nodoc:
|
243
|
+
raise NotImplementedError
|
244
|
+
end
|
245
|
+
|
246
|
+
# Overridden by the adapters to instantiate their specific Column type.
|
247
|
+
def new_column(field, default, type, null, collation, extra = "") # :nodoc:
|
248
|
+
Column.new(field, default, type, null, collation, extra)
|
249
|
+
end
|
250
|
+
|
251
|
+
# Must return the Mysql error number from the exception, if the exception has an
|
252
|
+
# error number.
|
253
|
+
def error_number(exception) # :nodoc:
|
254
|
+
raise NotImplementedError
|
255
|
+
end
|
256
|
+
|
257
|
+
# QUOTING ==================================================
|
258
|
+
|
259
|
+
def quote(value, column = nil)
|
260
|
+
if value.kind_of?(String) && column && column.type == :binary
|
261
|
+
s = value.unpack("H*")[0]
|
262
|
+
"x'#{s}'"
|
263
|
+
elsif value.kind_of?(BigDecimal)
|
264
|
+
value.to_s("F")
|
265
|
+
else
|
266
|
+
super
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def quote_column_name(name) #:nodoc:
|
271
|
+
@quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
|
272
|
+
end
|
273
|
+
|
274
|
+
def quote_table_name(name) #:nodoc:
|
275
|
+
@quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
|
276
|
+
end
|
277
|
+
|
278
|
+
def quoted_true
|
279
|
+
QUOTED_TRUE
|
280
|
+
end
|
281
|
+
|
282
|
+
def quoted_false
|
283
|
+
QUOTED_FALSE
|
284
|
+
end
|
285
|
+
|
286
|
+
# REFERENTIAL INTEGRITY ====================================
|
287
|
+
|
288
|
+
def disable_referential_integrity #:nodoc:
|
289
|
+
old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
|
290
|
+
|
291
|
+
begin
|
292
|
+
update("SET FOREIGN_KEY_CHECKS = 0")
|
293
|
+
yield
|
294
|
+
ensure
|
295
|
+
update("SET FOREIGN_KEY_CHECKS = #{old}")
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# DATABASE STATEMENTS ======================================
|
300
|
+
|
301
|
+
# Executes the SQL statement in the context of this connection.
|
302
|
+
def execute(sql, name = nil)
|
303
|
+
File.open('/tmp/thing.txt', 'a') { |file| file.write("\n\n!!!execute\n#{sql}") }
|
304
|
+
#result = @connection.query('SELECT `mythings`.* FROM `mythings`')
|
305
|
+
result = @connection.query(sql)
|
306
|
+
File.open('/tmp/thing.txt', 'a') { |file| file.write("\n#{result.inspect}") }
|
307
|
+
#binding.pry
|
308
|
+
#puts $the_e.inspect
|
309
|
+
log(sql, name) { result }
|
310
|
+
end
|
311
|
+
|
312
|
+
# MysqlAdapter has to free a result after using it, so we use this method to write
|
313
|
+
# stuff in an abstract way without concerning ourselves about whether it needs to be
|
314
|
+
# explicitly freed or not.
|
315
|
+
def execute_and_free(sql, name = nil) #:nodoc:
|
316
|
+
yield execute(sql, name)
|
317
|
+
end
|
318
|
+
|
319
|
+
def update_sql(sql, name = nil) #:nodoc:
|
320
|
+
super
|
321
|
+
@connection.affected_rows
|
322
|
+
end
|
323
|
+
|
324
|
+
def begin_db_transaction
|
325
|
+
execute "BEGIN"
|
326
|
+
end
|
327
|
+
|
328
|
+
def begin_isolated_db_transaction(isolation)
|
329
|
+
execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
|
330
|
+
begin_db_transaction
|
331
|
+
end
|
332
|
+
|
333
|
+
def commit_db_transaction #:nodoc:
|
334
|
+
execute "COMMIT"
|
335
|
+
end
|
336
|
+
|
337
|
+
def rollback_db_transaction #:nodoc:
|
338
|
+
execute "ROLLBACK"
|
339
|
+
end
|
340
|
+
|
341
|
+
# In the simple case, MySQL allows us to place JOINs directly into the UPDATE
|
342
|
+
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
|
343
|
+
# these, we must use a subquery.
|
344
|
+
def join_to_update(update, select) #:nodoc:
|
345
|
+
if select.limit || select.offset || select.orders.any?
|
346
|
+
super
|
347
|
+
else
|
348
|
+
update.table select.source
|
349
|
+
update.wheres = select.constraints
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
def empty_insert_statement_value
|
354
|
+
"VALUES ()"
|
355
|
+
end
|
356
|
+
|
357
|
+
# SCHEMA STATEMENTS ========================================
|
358
|
+
|
359
|
+
# Drops the database specified on the +name+ attribute
|
360
|
+
# and creates it again using the provided +options+.
|
361
|
+
def recreate_database(name, options = {})
|
362
|
+
drop_database(name)
|
363
|
+
sql = create_database(name, options)
|
364
|
+
reconnect!
|
365
|
+
sql
|
366
|
+
end
|
367
|
+
|
368
|
+
# Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
|
369
|
+
# Charset defaults to utf8.
|
370
|
+
#
|
371
|
+
# Example:
|
372
|
+
# create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
|
373
|
+
# create_database 'matt_development'
|
374
|
+
# create_database 'matt_development', charset: :big5
|
375
|
+
def create_database(name, options = {})
|
376
|
+
if options[:collation]
|
377
|
+
execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
|
378
|
+
else
|
379
|
+
execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
# Drops a MySQL database.
|
384
|
+
#
|
385
|
+
# Example:
|
386
|
+
# drop_database('sebastian_development')
|
387
|
+
def drop_database(name) #:nodoc:
|
388
|
+
execute "DROP DATABASE IF EXISTS `#{name}`"
|
389
|
+
end
|
390
|
+
|
391
|
+
def current_database
|
392
|
+
select_value 'SELECT DATABASE() as db'
|
393
|
+
end
|
394
|
+
|
395
|
+
# Returns the database character set.
|
396
|
+
def charset
|
397
|
+
show_variable 'character_set_database'
|
398
|
+
end
|
399
|
+
|
400
|
+
# Returns the database collation strategy.
|
401
|
+
def collation
|
402
|
+
show_variable 'collation_database'
|
403
|
+
end
|
404
|
+
|
405
|
+
def tables(name = nil, database = nil, like = nil) #:nodoc:
|
406
|
+
sql = "SHOW TABLES "
|
407
|
+
sql << "IN #{quote_table_name(database)} " if database
|
408
|
+
sql << "LIKE #{quote(like)}" if like
|
409
|
+
|
410
|
+
execute_and_free(sql, 'SCHEMA') do |result|
|
411
|
+
#binding.pry
|
412
|
+
result.collect { |field| field.first }
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
def table_exists?(name)
|
417
|
+
return false unless name
|
418
|
+
return true if tables(nil, nil, name).any?
|
419
|
+
|
420
|
+
name = name.to_s
|
421
|
+
schema, table = name.split('.', 2)
|
422
|
+
|
423
|
+
unless table # A table was provided without a schema
|
424
|
+
table = schema
|
425
|
+
schema = nil
|
426
|
+
end
|
427
|
+
|
428
|
+
tables(nil, schema, table).any?
|
429
|
+
end
|
430
|
+
|
431
|
+
# Returns an array of indexes for the given table.
|
432
|
+
def indexes(table_name, name = nil) #:nodoc:
|
433
|
+
indexes = []
|
434
|
+
current_index = nil
|
435
|
+
execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
|
436
|
+
each_hash(result) do |row|
|
437
|
+
if current_index != row[:Key_name]
|
438
|
+
next if row[:Key_name] == 'PRIMARY' # skip the primary key
|
439
|
+
current_index = row[:Key_name]
|
440
|
+
|
441
|
+
mysql_index_type = row[:Index_type].downcase.to_sym
|
442
|
+
index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
|
443
|
+
index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
|
444
|
+
indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
|
445
|
+
end
|
446
|
+
|
447
|
+
indexes.last.columns << row[:Column_name]
|
448
|
+
indexes.last.lengths << row[:Sub_part]
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
indexes
|
453
|
+
end
|
454
|
+
|
455
|
+
# Returns an array of +Column+ objects for the table specified by +table_name+.
|
456
|
+
def columns(table_name)#:nodoc:
|
457
|
+
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
|
458
|
+
execute_and_free(sql, 'SCHEMA') do |result|
|
459
|
+
each_hash(result).map do |field|
|
460
|
+
#binding.pry
|
461
|
+
field_name = set_field_encoding(field[:Field])
|
462
|
+
new_column(field_name, field[:Default], field[:Type], field[:Null] == "YES", field[:Collation], field[:Extra])
|
463
|
+
end
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
def create_table(table_name, options = {}) #:nodoc:
|
468
|
+
super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
|
469
|
+
end
|
470
|
+
|
471
|
+
def bulk_change_table(table_name, operations) #:nodoc:
|
472
|
+
sqls = operations.map do |command, args|
|
473
|
+
table, arguments = args.shift, args
|
474
|
+
method = :"#{command}_sql"
|
475
|
+
|
476
|
+
if respond_to?(method, true)
|
477
|
+
send(method, table, *arguments)
|
478
|
+
else
|
479
|
+
raise "Unknown method called : #{method}(#{arguments.inspect})"
|
480
|
+
end
|
481
|
+
end.flatten.join(", ")
|
482
|
+
|
483
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
|
484
|
+
end
|
485
|
+
|
486
|
+
# Renames a table.
|
487
|
+
#
|
488
|
+
# Example:
|
489
|
+
# rename_table('octopuses', 'octopi')
|
490
|
+
def rename_table(table_name, new_name)
|
491
|
+
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
|
492
|
+
rename_table_indexes(table_name, new_name)
|
493
|
+
end
|
494
|
+
|
495
|
+
def drop_table(table_name, options = {})
|
496
|
+
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}"
|
497
|
+
end
|
498
|
+
|
499
|
+
def rename_index(table_name, old_name, new_name)
|
500
|
+
if supports_rename_index?
|
501
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
|
502
|
+
else
|
503
|
+
super
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
def change_column_default(table_name, column_name, default)
|
508
|
+
column = column_for(table_name, column_name)
|
509
|
+
change_column table_name, column_name, column.sql_type, :default => default
|
510
|
+
end
|
511
|
+
|
512
|
+
def change_column_null(table_name, column_name, null, default = nil)
|
513
|
+
column = column_for(table_name, column_name)
|
514
|
+
|
515
|
+
unless null || default.nil?
|
516
|
+
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
517
|
+
end
|
518
|
+
|
519
|
+
change_column table_name, column_name, column.sql_type, :null => null
|
520
|
+
end
|
521
|
+
|
522
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
523
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
|
524
|
+
end
|
525
|
+
|
526
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
527
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
|
528
|
+
rename_column_indexes(table_name, column_name, new_column_name)
|
529
|
+
end
|
530
|
+
|
531
|
+
def add_index(table_name, column_name, options = {}) #:nodoc:
|
532
|
+
index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
|
533
|
+
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
|
534
|
+
end
|
535
|
+
|
536
|
+
# Maps logical Rails types to MySQL-specific data types.
|
537
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
538
|
+
case type.to_s
|
539
|
+
when 'binary'
|
540
|
+
case limit
|
541
|
+
when 0..0xfff; "varbinary(#{limit})"
|
542
|
+
when nil; "blob"
|
543
|
+
when 0x1000..0xffffffff; "blob(#{limit})"
|
544
|
+
else raise(ActiveRecordError, "No binary type has character length #{limit}")
|
545
|
+
end
|
546
|
+
when 'integer'
|
547
|
+
case limit
|
548
|
+
when 1; 'tinyint'
|
549
|
+
when 2; 'smallint'
|
550
|
+
when 3; 'mediumint'
|
551
|
+
when nil, 4, 11; 'int(11)' # compatibility with MySQL default
|
552
|
+
when 5..8; 'bigint'
|
553
|
+
else raise(ActiveRecordError, "No integer type has byte size #{limit}")
|
554
|
+
end
|
555
|
+
when 'text'
|
556
|
+
case limit
|
557
|
+
when 0..0xff; 'tinytext'
|
558
|
+
when nil, 0x100..0xffff; 'text'
|
559
|
+
when 0x10000..0xffffff; 'mediumtext'
|
560
|
+
when 0x1000000..0xffffffff; 'longtext'
|
561
|
+
else raise(ActiveRecordError, "No text type has character length #{limit}")
|
562
|
+
end
|
563
|
+
else
|
564
|
+
super
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
def add_column_position!(sql, options)
|
569
|
+
if options[:first]
|
570
|
+
sql << " FIRST"
|
571
|
+
elsif options[:after]
|
572
|
+
sql << " AFTER #{quote_column_name(options[:after])}"
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
# SHOW VARIABLES LIKE 'name'
|
577
|
+
def show_variable(name)
|
578
|
+
variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
|
579
|
+
variables.first['Value'] unless variables.empty?
|
580
|
+
end
|
581
|
+
|
582
|
+
# Returns a table's primary key and belonging sequence.
|
583
|
+
def pk_and_sequence_for(table)
|
584
|
+
execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
|
585
|
+
#binding.pry
|
586
|
+
create_table = each_hash(result).first[:"Create Table"]
|
587
|
+
if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
|
588
|
+
keys = $1.split(",").map { |key| key.delete('`"') }
|
589
|
+
keys.length == 1 ? [keys.first, nil] : nil
|
590
|
+
else
|
591
|
+
nil
|
592
|
+
end
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
# Returns just a table's primary key
|
597
|
+
def primary_key(table)
|
598
|
+
pk_and_sequence = pk_and_sequence_for(table)
|
599
|
+
pk_and_sequence && pk_and_sequence.first
|
600
|
+
end
|
601
|
+
|
602
|
+
def case_sensitive_modifier(node)
|
603
|
+
Arel::Nodes::Bin.new(node)
|
604
|
+
end
|
605
|
+
|
606
|
+
def case_insensitive_comparison(table, attribute, column, value)
|
607
|
+
if column.case_sensitive?
|
608
|
+
super
|
609
|
+
else
|
610
|
+
table[attribute].eq(value)
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
|
615
|
+
where_sql
|
616
|
+
end
|
617
|
+
|
618
|
+
def strict_mode?
|
619
|
+
self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
|
620
|
+
end
|
621
|
+
|
622
|
+
def valid_type?(type)
|
623
|
+
!native_database_types[type].nil?
|
624
|
+
end
|
625
|
+
|
626
|
+
protected
|
627
|
+
|
628
|
+
# MySQL is too stupid to create a temporary table for use subquery, so we have
|
629
|
+
# to give it some prompting in the form of a subsubquery. Ugh!
|
630
|
+
def subquery_for(key, select)
|
631
|
+
subsubselect = select.clone
|
632
|
+
subsubselect.projections = [key]
|
633
|
+
|
634
|
+
subselect = Arel::SelectManager.new(select.engine)
|
635
|
+
subselect.project Arel.sql(key.name)
|
636
|
+
subselect.from subsubselect.as('__active_record_temp')
|
637
|
+
end
|
638
|
+
|
639
|
+
def add_index_length(option_strings, column_names, options = {})
|
640
|
+
if options.is_a?(Hash) && length = options[:length]
|
641
|
+
case length
|
642
|
+
when Hash
|
643
|
+
column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
|
644
|
+
when Fixnum
|
645
|
+
column_names.each {|name| option_strings[name] += "(#{length})"}
|
646
|
+
end
|
647
|
+
end
|
648
|
+
|
649
|
+
return option_strings
|
650
|
+
end
|
651
|
+
|
652
|
+
def quoted_columns_for_index(column_names, options = {})
|
653
|
+
option_strings = Hash[column_names.map {|name| [name, '']}]
|
654
|
+
|
655
|
+
# add index length
|
656
|
+
option_strings = add_index_length(option_strings, column_names, options)
|
657
|
+
|
658
|
+
# add index sort order
|
659
|
+
option_strings = add_index_sort_order(option_strings, column_names, options)
|
660
|
+
|
661
|
+
column_names.map {|name| quote_column_name(name) + option_strings[name]}
|
662
|
+
end
|
663
|
+
|
664
|
+
def translate_exception(exception, message)
|
665
|
+
case error_number(exception)
|
666
|
+
when 1062
|
667
|
+
RecordNotUnique.new(message, exception)
|
668
|
+
when 1452
|
669
|
+
InvalidForeignKey.new(message, exception)
|
670
|
+
else
|
671
|
+
super
|
672
|
+
end
|
673
|
+
end
|
674
|
+
|
675
|
+
def add_column_sql(table_name, column_name, type, options = {})
|
676
|
+
td = create_table_definition table_name, options[:temporary], options[:options]
|
677
|
+
cd = td.new_column_definition(column_name, type, options)
|
678
|
+
schema_creation.visit_AddColumn cd
|
679
|
+
end
|
680
|
+
|
681
|
+
def change_column_sql(table_name, column_name, type, options = {})
|
682
|
+
column = column_for(table_name, column_name)
|
683
|
+
|
684
|
+
unless options_include_default?(options)
|
685
|
+
options[:default] = column.default
|
686
|
+
end
|
687
|
+
|
688
|
+
unless options.has_key?(:null)
|
689
|
+
options[:null] = column.null
|
690
|
+
end
|
691
|
+
|
692
|
+
options[:name] = column.name
|
693
|
+
schema_creation.accept ChangeColumnDefinition.new column, type, options
|
694
|
+
end
|
695
|
+
|
696
|
+
def rename_column_sql(table_name, column_name, new_column_name)
|
697
|
+
options = { name: new_column_name }
|
698
|
+
|
699
|
+
if column = columns(table_name).find { |c| c.name == column_name.to_s }
|
700
|
+
options[:default] = column.default
|
701
|
+
options[:null] = column.null
|
702
|
+
options[:auto_increment] = (column.extra == "auto_increment")
|
703
|
+
else
|
704
|
+
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
|
705
|
+
end
|
706
|
+
|
707
|
+
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
|
708
|
+
schema_creation.accept ChangeColumnDefinition.new column, current_type, options
|
709
|
+
end
|
710
|
+
|
711
|
+
def remove_column_sql(table_name, column_name, type = nil, options = {})
|
712
|
+
"DROP #{quote_column_name(column_name)}"
|
713
|
+
end
|
714
|
+
|
715
|
+
def remove_columns_sql(table_name, *column_names)
|
716
|
+
column_names.map {|column_name| remove_column_sql(table_name, column_name) }
|
717
|
+
end
|
718
|
+
|
719
|
+
def add_index_sql(table_name, column_name, options = {})
|
720
|
+
index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
|
721
|
+
"ADD #{index_type} INDEX #{index_name} (#{index_columns})"
|
722
|
+
end
|
723
|
+
|
724
|
+
def remove_index_sql(table_name, options = {})
|
725
|
+
index_name = index_name_for_remove(table_name, options)
|
726
|
+
"DROP INDEX #{index_name}"
|
727
|
+
end
|
728
|
+
|
729
|
+
def add_timestamps_sql(table_name)
|
730
|
+
[add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
|
731
|
+
end
|
732
|
+
|
733
|
+
def remove_timestamps_sql(table_name)
|
734
|
+
[remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
|
735
|
+
end
|
736
|
+
|
737
|
+
private
|
738
|
+
|
739
|
+
def version
|
740
|
+
@version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
|
741
|
+
end
|
742
|
+
|
743
|
+
def mariadb?
|
744
|
+
full_version =~ /mariadb/i
|
745
|
+
end
|
746
|
+
|
747
|
+
def supports_views?
|
748
|
+
version[0] >= 5
|
749
|
+
end
|
750
|
+
|
751
|
+
def supports_rename_index?
|
752
|
+
mariadb? ? false : (version[0] == 5 && version[1] >= 7) || version[0] >= 6
|
753
|
+
end
|
754
|
+
|
755
|
+
def column_for(table_name, column_name)
|
756
|
+
unless column = columns(table_name).find { |c| c.name == column_name.to_s }
|
757
|
+
raise "No such column: #{table_name}.#{column_name}"
|
758
|
+
end
|
759
|
+
column
|
760
|
+
end
|
761
|
+
|
762
|
+
def configure_connection
|
763
|
+
variables = @config.fetch(:variables, {}).stringify_keys
|
764
|
+
|
765
|
+
# By default, MySQL 'where id is null' selects the last inserted id.
|
766
|
+
# Turn this off. http://dev.rubyonrails.org/ticket/6778
|
767
|
+
variables['sql_auto_is_null'] = 0
|
768
|
+
|
769
|
+
# Increase timeout so the server doesn't disconnect us.
|
770
|
+
wait_timeout = @config[:wait_timeout]
|
771
|
+
wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
|
772
|
+
variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
|
773
|
+
|
774
|
+
# Make MySQL reject illegal values rather than truncating or blanking them, see
|
775
|
+
# http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
|
776
|
+
# If the user has provided another value for sql_mode, don't replace it.
|
777
|
+
unless variables.has_key?('sql_mode')
|
778
|
+
variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
|
779
|
+
end
|
780
|
+
|
781
|
+
# NAMES does not have an equals sign, see
|
782
|
+
# http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
|
783
|
+
# (trailing comma because variable_assignments will always have content)
|
784
|
+
encoding = "NAMES #{@config[:encoding]}, " if @config[:encoding]
|
785
|
+
|
786
|
+
# Gather up all of the SET variables...
|
787
|
+
variable_assignments = variables.map do |k, v|
|
788
|
+
if v == ':default' || v == :default
|
789
|
+
"@@SESSION.#{k.to_s} = DEFAULT" # Sets the value to the global or compile default
|
790
|
+
elsif !v.nil?
|
791
|
+
"@@SESSION.#{k.to_s} = #{quote(v)}"
|
792
|
+
end
|
793
|
+
# or else nil; compact to clear nils out
|
794
|
+
end.compact.join(', ')
|
795
|
+
|
796
|
+
# ...and send them all in one query
|
797
|
+
@connection.query "SET #{encoding} #{variable_assignments}"
|
798
|
+
end
|
799
|
+
end
|
800
|
+
end
|
801
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'json'
|
3
|
+
require 'msgpack'
|
4
|
+
|
5
|
+
module HbaseRestIface
|
6
|
+
class Client
|
7
|
+
include HTTParty
|
8
|
+
|
9
|
+
def initialize(ops)
|
10
|
+
ops ||= {}
|
11
|
+
ops[:host] ||= 'localhost'
|
12
|
+
ops[:port] ||= '9191'
|
13
|
+
|
14
|
+
@ops = ops
|
15
|
+
@query_options = {}
|
16
|
+
@info = { :version => '1' }
|
17
|
+
|
18
|
+
connect
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](key)
|
22
|
+
@ops[key]
|
23
|
+
end
|
24
|
+
def []=(key,val)
|
25
|
+
@ops[key]=val
|
26
|
+
end
|
27
|
+
|
28
|
+
def query(sql)
|
29
|
+
#File.open('/tmp/thing.txt', 'a') { |file| file.write("\n\n!!!query\n#{sql}") }
|
30
|
+
query_object = HipsterSqlToHbase.parse_hash(sql)
|
31
|
+
#File.open('/tmp/thing.txt', 'a') { |file| file.write("\n#{query_object.inspect}") }
|
32
|
+
begin
|
33
|
+
#TODO: make these be msgpack instead of json
|
34
|
+
result = secure_request("/exec", { body: {query: JSON.generate(query_object)} })
|
35
|
+
Hbase::Result.new(result)
|
36
|
+
rescue Exception => e
|
37
|
+
#File.open('/tmp/thing.txt', 'a') { |file| file.write("\n\n!!!error\n#{e.message}") }
|
38
|
+
Hbase::Result.new()
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def connect
|
43
|
+
self.class.base_uri "#{@ops[:host]}:#{@ops[:port]}"
|
44
|
+
#File.open('/tmp/thing.txt', 'a') { |file| file.write("\n\n!!!connect\n#{@ops[:host]}:#{@ops[:port]}") }
|
45
|
+
ping
|
46
|
+
end
|
47
|
+
|
48
|
+
def ping
|
49
|
+
simple_request("/ping")[0]
|
50
|
+
end
|
51
|
+
|
52
|
+
def simple_request(route, query=nil)
|
53
|
+
if query.nil?
|
54
|
+
response = self.class.get(route)
|
55
|
+
else
|
56
|
+
response = self.class.get(route, query)
|
57
|
+
end
|
58
|
+
|
59
|
+
if response.code.to_s =~ /2\d\d/
|
60
|
+
#TODO: make these be msgpack instead of json
|
61
|
+
JSON.parse(response.body)
|
62
|
+
else
|
63
|
+
error = Nokogiri::HTML(response.body)
|
64
|
+
#binding.pry
|
65
|
+
raise error.css("title")[0].text
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def secure_request(route, body=nil)
|
70
|
+
if body.nil?
|
71
|
+
response = self.class.post(route)
|
72
|
+
else
|
73
|
+
response = self.class.post(route, body)
|
74
|
+
end
|
75
|
+
|
76
|
+
if response.code.to_s =~ /2\d\d/
|
77
|
+
#TODO: make these be msgpack instead of json
|
78
|
+
JSON.parse(response.body)
|
79
|
+
else
|
80
|
+
error = Nokogiri::HTML(response.body)
|
81
|
+
#binding.pry
|
82
|
+
raise error.css("title")[0].text
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def info
|
87
|
+
@info
|
88
|
+
end
|
89
|
+
|
90
|
+
def query_options
|
91
|
+
@query_options
|
92
|
+
end
|
93
|
+
|
94
|
+
def close
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
|
98
|
+
def abandon_results!
|
99
|
+
nil
|
100
|
+
end
|
101
|
+
|
102
|
+
def escape(string)
|
103
|
+
string
|
104
|
+
end
|
105
|
+
|
106
|
+
def info
|
107
|
+
nil
|
108
|
+
end
|
109
|
+
|
110
|
+
def server_info
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
|
114
|
+
def socket
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
|
118
|
+
def async_result
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
|
122
|
+
def last_id
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
|
126
|
+
def affected_rows
|
127
|
+
nil
|
128
|
+
end
|
129
|
+
|
130
|
+
def thread_id
|
131
|
+
nil
|
132
|
+
end
|
133
|
+
|
134
|
+
def select_db
|
135
|
+
nil
|
136
|
+
end
|
137
|
+
|
138
|
+
def more_results?
|
139
|
+
nil
|
140
|
+
end
|
141
|
+
|
142
|
+
def next_result
|
143
|
+
nil
|
144
|
+
end
|
145
|
+
|
146
|
+
def store_result
|
147
|
+
nil
|
148
|
+
end
|
149
|
+
|
150
|
+
def reconnect=
|
151
|
+
nil
|
152
|
+
end
|
153
|
+
|
154
|
+
def warning_count
|
155
|
+
nil
|
156
|
+
end
|
157
|
+
|
158
|
+
def query_info_string
|
159
|
+
nil
|
160
|
+
end
|
161
|
+
|
162
|
+
def encoding
|
163
|
+
nil
|
164
|
+
end
|
165
|
+
|
166
|
+
def connect_timeout=(o)
|
167
|
+
nil
|
168
|
+
end
|
169
|
+
|
170
|
+
def read_timeout=(o)
|
171
|
+
nil
|
172
|
+
end
|
173
|
+
|
174
|
+
def write_timeout=(o)
|
175
|
+
nil
|
176
|
+
end
|
177
|
+
|
178
|
+
def local_infile=(o)
|
179
|
+
nil
|
180
|
+
end
|
181
|
+
|
182
|
+
def charset_name=(o)
|
183
|
+
nil
|
184
|
+
end
|
185
|
+
|
186
|
+
def secure_auth=(o)
|
187
|
+
nil
|
188
|
+
end
|
189
|
+
|
190
|
+
def default_file=(o)
|
191
|
+
nil
|
192
|
+
end
|
193
|
+
|
194
|
+
def default_group=(o)
|
195
|
+
nil
|
196
|
+
end
|
197
|
+
|
198
|
+
def init_command=(o)
|
199
|
+
nil
|
200
|
+
end
|
201
|
+
|
202
|
+
def ssl_set
|
203
|
+
nil
|
204
|
+
end
|
205
|
+
|
206
|
+
def initialize_ext
|
207
|
+
nil
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Hbase
|
2
|
+
class Error < StandardError
|
3
|
+
REPLACEMENT_CHAR = '?'
|
4
|
+
ENCODE_OPTS = {:undef => :replace, :invalid => :replace, :replace => REPLACEMENT_CHAR}
|
5
|
+
|
6
|
+
attr_accessor :error_number
|
7
|
+
attr_reader :sql_state
|
8
|
+
attr_writer :server_version
|
9
|
+
|
10
|
+
# Mysql gem compatibility
|
11
|
+
alias_method :errno, :error_number
|
12
|
+
alias_method :error, :message
|
13
|
+
|
14
|
+
def initialize(msg, server_version=nil)
|
15
|
+
self.server_version = server_version
|
16
|
+
|
17
|
+
super(clean_message(msg))
|
18
|
+
end
|
19
|
+
|
20
|
+
def sql_state=(state)
|
21
|
+
@sql_state = ''.respond_to?(:encode) ? state.encode(ENCODE_OPTS) : state
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# In MySQL 5.5+ error messages are always constructed server-side as UTF-8
|
27
|
+
# then returned in the encoding set by the `character_set_results` system
|
28
|
+
# variable.
|
29
|
+
#
|
30
|
+
# See http://dev.mysql.com/doc/refman/5.5/en/charset-errors.html for
|
31
|
+
# more contetx.
|
32
|
+
#
|
33
|
+
# Before MySQL 5.5 error message template strings are in whatever encoding
|
34
|
+
# is associated with the error message language.
|
35
|
+
# See http://dev.mysql.com/doc/refman/5.1/en/error-message-language.html
|
36
|
+
# for more information.
|
37
|
+
#
|
38
|
+
# The issue is that the user-data inserted in the message could potentially
|
39
|
+
# be in any encoding MySQL supports and is insert into the latin1, euckr or
|
40
|
+
# koi8r string raw. Meaning there's a high probability the string will be
|
41
|
+
# corrupt encoding-wise.
|
42
|
+
#
|
43
|
+
# See http://dev.mysql.com/doc/refman/5.1/en/charset-errors.html for
|
44
|
+
# more information.
|
45
|
+
#
|
46
|
+
# So in an attempt to make sure the error message string is always in a valid
|
47
|
+
# encoding, we'll assume UTF-8 and clean the string of anything that's not a
|
48
|
+
# valid UTF-8 character.
|
49
|
+
#
|
50
|
+
# Except for if we're on 1.8, where we'll do nothing ;)
|
51
|
+
#
|
52
|
+
# Returns a valid UTF-8 string in Ruby 1.9+, the original string on Ruby 1.8
|
53
|
+
def clean_message(message)
|
54
|
+
return message if !message.respond_to?(:encoding)
|
55
|
+
|
56
|
+
if @server_version && @server_version > 50500
|
57
|
+
message.encode(ENCODE_OPTS)
|
58
|
+
else
|
59
|
+
if message.respond_to? :scrub
|
60
|
+
message.scrub(REPLACEMENT_CHAR).encode(ENCODE_OPTS)
|
61
|
+
else
|
62
|
+
# This is ugly as hell but Ruby 1.9 doesn't provide a way to clean a string
|
63
|
+
# and retain it's valid UTF-8 characters, that I know of.
|
64
|
+
|
65
|
+
new_message = "".force_encoding(Encoding::UTF_8)
|
66
|
+
message.chars.each do |char|
|
67
|
+
if char.valid_encoding?
|
68
|
+
new_message << char
|
69
|
+
else
|
70
|
+
new_message << REPLACEMENT_CHAR
|
71
|
+
end
|
72
|
+
end
|
73
|
+
new_message.encode(ENCODE_OPTS)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'active_record/connection_adapters/abstract_mysql_adapter'
|
2
|
+
|
3
|
+
module Hbase
|
4
|
+
class Result
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
attr_accessor :members
|
8
|
+
|
9
|
+
def initialize(result = {'fields' => [], 'results' => []})
|
10
|
+
@result = result
|
11
|
+
@members = @result['results']
|
12
|
+
end
|
13
|
+
|
14
|
+
def hasherize_rows(symbolize_keys = false)
|
15
|
+
if symbolize_keys
|
16
|
+
keys = @result['fields'].map! { |v| v.to_sym }
|
17
|
+
else
|
18
|
+
keys = @result['fields']
|
19
|
+
end
|
20
|
+
#binding.pry
|
21
|
+
hashed_rows = @result['results'].map { |row| Hash[keys.zip row] }
|
22
|
+
end
|
23
|
+
|
24
|
+
def each(arg_hash = {},&block)
|
25
|
+
unless arg_hash[:as].nil?
|
26
|
+
symbolize_keys = (arg_hash[:symbolize_keys].nil?) ? false : arg_hash[:symbolize_keys]
|
27
|
+
if arg_hash[:as] == :hash
|
28
|
+
hasherize_rows(symbolize_keys).each &block
|
29
|
+
end
|
30
|
+
else
|
31
|
+
@result['results'].each &block
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def fields
|
36
|
+
@result['fields']
|
37
|
+
end
|
38
|
+
|
39
|
+
def count
|
40
|
+
@result['results'].length
|
41
|
+
end
|
42
|
+
|
43
|
+
alias :size :count
|
44
|
+
end
|
45
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-hbase-adapter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.71
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jean Lescure
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: nokogiri
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
description: HBase ActiveRecord adapter based on HipsterSqlToHbase
|
56
70
|
email:
|
57
71
|
- jean@agilityfeat.com
|
@@ -61,6 +75,10 @@ extra_rdoc_files: []
|
|
61
75
|
files:
|
62
76
|
- Gemfile
|
63
77
|
- activerecord-hbase-adapter.gemspec
|
78
|
+
- lib/active_record/connection_adapters/abstract_mysql_adapter.rb
|
79
|
+
- lib/active_record/connection_adapters/hbase/client.rb
|
80
|
+
- lib/active_record/connection_adapters/hbase/error.rb
|
81
|
+
- lib/active_record/connection_adapters/hbase/result.rb
|
64
82
|
- lib/active_record/connection_adapters/hbase_adapter.rb
|
65
83
|
- lib/activerecord-hbase-adapter/version.rb
|
66
84
|
homepage: http://github.com/jeanlescure/activerecord-hbase-adapter
|