activerecord-hbase-adapter 0.0.7 → 0.0.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c9b0cf4fd299122fc45486397c36ccc43e2923c6
4
- data.tar.gz: 109f0b4a74e3a763eee842152117aeee01a3ec21
3
+ metadata.gz: d20cfbf09998f3be62222929739135c35bcfe1b7
4
+ data.tar.gz: 5262a6b3e72274fdabb48b0fdb51a9e4acc8d445
5
5
  SHA512:
6
- metadata.gz: efdc375a03df4c79831f209565ad5ce1a377c758054d3c753802a1ce168799cd71178e40b6ad5b0e63cb05c779f04cfe1a8b1feb588ab6fae59f33c3414dbfe2
7
- data.tar.gz: 758e44e26fb0884febac15e40e503b2f809d6aa052aecc93601efaa06499c29ce486c321760da4064f96d7bdbd1ad87a87f306e0e3031afd3546634878a9166c
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
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
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
@@ -1,7 +1,7 @@
1
1
  module Activerecord
2
2
  module Hbase
3
3
  module Adapter
4
- VERSION = "0.0.7"
4
+ VERSION = "0.0.71"
5
5
  end
6
6
  end
7
7
  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.7
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