bigbroda 0.0.1

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.
Files changed (131) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +25 -0
  3. data/.pryrc +3 -0
  4. data/.rspec +2 -0
  5. data/.rvmrc +1 -0
  6. data/Gemfile +18 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +408 -0
  9. data/Rakefile +12 -0
  10. data/google_bigquery.gemspec +30 -0
  11. data/lib/.DS_Store +0 -0
  12. data/lib/active_record/.DS_Store +0 -0
  13. data/lib/active_record/connection_adapters/bigquery_adapter.rb +949 -0
  14. data/lib/active_record/tasks/bigquery_database_tasks.rb +42 -0
  15. data/lib/generators/.DS_Store +0 -0
  16. data/lib/generators/google_bigquery/.DS_Store +0 -0
  17. data/lib/generators/google_bigquery/install/install_generator.rb +21 -0
  18. data/lib/generators/templates/README +11 -0
  19. data/lib/generators/templates/bigquery.rb.erb +7 -0
  20. data/lib/google_bigquery/auth.rb +27 -0
  21. data/lib/google_bigquery/client.rb +52 -0
  22. data/lib/google_bigquery/config.rb +17 -0
  23. data/lib/google_bigquery/dataset.rb +77 -0
  24. data/lib/google_bigquery/engine.rb +21 -0
  25. data/lib/google_bigquery/jobs.rb +173 -0
  26. data/lib/google_bigquery/project.rb +16 -0
  27. data/lib/google_bigquery/railtie.rb +39 -0
  28. data/lib/google_bigquery/table.rb +63 -0
  29. data/lib/google_bigquery/table_data.rb +23 -0
  30. data/lib/google_bigquery/version.rb +3 -0
  31. data/lib/google_bigquery.rb +27 -0
  32. data/spec/.DS_Store +0 -0
  33. data/spec/dummy/.DS_Store +0 -0
  34. data/spec/dummy/.gitignore +20 -0
  35. data/spec/dummy/README.rdoc +261 -0
  36. data/spec/dummy/Rakefile +7 -0
  37. data/spec/dummy/app/assets/javascripts/application.js +16 -0
  38. data/spec/dummy/app/assets/stylesheets/application.css.scss +13 -0
  39. data/spec/dummy/app/controllers/application_controller.rb +4 -0
  40. data/spec/dummy/app/helpers/application_helper.rb +3 -0
  41. data/spec/dummy/app/mailers/.gitkeep +0 -0
  42. data/spec/dummy/app/models/log_data.rb +3 -0
  43. data/spec/dummy/app/models/post.rb +3 -0
  44. data/spec/dummy/app/models/user.rb +4 -0
  45. data/spec/dummy/app/views/layouts/application.html.haml +32 -0
  46. data/spec/dummy/config/application.rb +23 -0
  47. data/spec/dummy/config/boot.rb +11 -0
  48. data/spec/dummy/config/database.yml +32 -0
  49. data/spec/dummy/config/environment.rb +6 -0
  50. data/spec/dummy/config/environments/development.rb +29 -0
  51. data/spec/dummy/config/environments/production.rb +80 -0
  52. data/spec/dummy/config/environments/test.rb +36 -0
  53. data/spec/dummy/config/initializers/backtrace_silencers.rb +8 -0
  54. data/spec/dummy/config/initializers/bigquery.rb +19 -0
  55. data/spec/dummy/config/initializers/inflections.rb +16 -0
  56. data/spec/dummy/config/initializers/mime_types.rb +6 -0
  57. data/spec/dummy/config/initializers/secret_token.rb +8 -0
  58. data/spec/dummy/config/initializers/session_store.rb +9 -0
  59. data/spec/dummy/config/initializers/wrap_parameters.rb +15 -0
  60. data/spec/dummy/config/locales/devise.en.yml +58 -0
  61. data/spec/dummy/config/locales/en.yml +5 -0
  62. data/spec/dummy/config/locales/simple_form.en.yml +26 -0
  63. data/spec/dummy/config/routes.rb +4 -0
  64. data/spec/dummy/config.ru +4 -0
  65. data/spec/dummy/db/migrate/20140224051640_create_users.rb +11 -0
  66. data/spec/dummy/db/migrate/20140224063709_add_last_name_to_user.rb +5 -0
  67. data/spec/dummy/db/migrate/20140225014314_create_log_data.rb +12 -0
  68. data/spec/dummy/db/migrate/20140227015551_create_posts.rb +9 -0
  69. data/spec/dummy/db/schema.rb +39 -0
  70. data/spec/dummy/db/schema_migrations.json +1 -0
  71. data/spec/dummy/lib/assets/.gitkeep +0 -0
  72. data/spec/dummy/lib/templates/erb/scaffold/_form.html.erb +13 -0
  73. data/spec/dummy/log/.gitkeep +0 -0
  74. data/spec/dummy/public/404.html +26 -0
  75. data/spec/dummy/public/422.html +26 -0
  76. data/spec/dummy/public/500.html +25 -0
  77. data/spec/dummy/public/favicon.ico +0 -0
  78. data/spec/dummy/script/rails +6 -0
  79. data/spec/dummy/test/fixtures/log_data.yml +9 -0
  80. data/spec/dummy/test/fixtures/posts.yml +11 -0
  81. data/spec/dummy/test/fixtures/users.yml +11 -0
  82. data/spec/dummy/test/models/log_data_test.rb +7 -0
  83. data/spec/dummy/test/models/post_test.rb +7 -0
  84. data/spec/dummy/test/models/user_test.rb +7 -0
  85. data/spec/fixtures/.DS_Store +0 -0
  86. data/spec/fixtures/configs/account_config.yml-example +6 -0
  87. data/spec/fixtures/keys/.DS_Store +0 -0
  88. data/spec/fixtures/keys/example-privatekey-p12 +0 -0
  89. data/spec/fixtures/vcr_cassettes/ActiveRecord_Adapter/.DS_Store +0 -0
  90. data/spec/fixtures/vcr_cassettes/ActiveRecord_Adapter/adapter/simple_quering.yml +324 -0
  91. data/spec/fixtures/vcr_cassettes/ActiveRecord_Adapter/after_each.yml +154 -0
  92. data/spec/fixtures/vcr_cassettes/ActiveRecord_Adapter/authorize_config.yml +367 -0
  93. data/spec/fixtures/vcr_cassettes/ActiveRecord_Adapter/create_each.yml +195 -0
  94. data/spec/fixtures/vcr_cassettes/ActiveRecord_Adapter/migrations/_down/adds_the_email_at_utc_hour_column.yml +575 -0
  95. data/spec/fixtures/vcr_cassettes/ActiveRecord_Adapter/migrations/_up/adds_the_created_at_updated_at_column.yml +644 -0
  96. data/spec/fixtures/vcr_cassettes/ActiveRecord_Adapter/migrations/add_column/adds_published_column.yml +779 -0
  97. data/spec/fixtures/vcr_cassettes/ActiveRecord_Adapter/migrations/associations/users_posts.yml +1464 -0
  98. data/spec/fixtures/vcr_cassettes/ActiveRecord_Adapter/migrations/remove_column/should_raise_error.yml +713 -0
  99. data/spec/fixtures/vcr_cassettes/Dataset/_list.yml +64 -0
  100. data/spec/fixtures/vcr_cassettes/Dataset/authorize_config.yml +367 -0
  101. data/spec/fixtures/vcr_cassettes/Dataset/operations/_get_delete.yml +237 -0
  102. data/spec/fixtures/vcr_cassettes/Dataset/operations/_patch_delete.yml +240 -0
  103. data/spec/fixtures/vcr_cassettes/Dataset/operations/_update_delete.yml +297 -0
  104. data/spec/fixtures/vcr_cassettes/Dataset/operations/create_delete.yml +173 -0
  105. data/spec/fixtures/vcr_cassettes/Project/_list.yml +64 -0
  106. data/spec/fixtures/vcr_cassettes/Project/authorize_config.yml +2166 -0
  107. data/spec/fixtures/vcr_cassettes/Table/authorize_config.yml +367 -0
  108. data/spec/fixtures/vcr_cassettes/Table/operations/creation_edition/_create_delete.yml +404 -0
  109. data/spec/fixtures/vcr_cassettes/Table/operations/creation_edition/_create_update_delete.yml +471 -0
  110. data/spec/fixtures/vcr_cassettes/Table/operations/list.yml +232 -0
  111. data/spec/fixtures/vcr_cassettes/TableData/authorize_config.yml +2166 -0
  112. data/spec/fixtures/vcr_cassettes/TableData/create_each.yml +135 -0
  113. data/spec/fixtures/vcr_cassettes/TableData/delete_each.yml +154 -0
  114. data/spec/fixtures/vcr_cassettes/TableData/insertAll2.yml +189 -0
  115. data/spec/fixtures/vcr_cassettes/auth.yml +2168 -0
  116. data/spec/fixtures/vcr_cassettes/authorize_config.yml +2166 -0
  117. data/spec/fixtures/vcr_cassettes/datasets.yml +119 -0
  118. data/spec/fixtures/vcr_cassettes/delete_each_dataset.yml +48 -0
  119. data/spec/functional/adapter/adapter_spec.rb +213 -0
  120. data/spec/functional/auth_spec.rb +24 -0
  121. data/spec/functional/client_spec.rb +9 -0
  122. data/spec/functional/config_spec.rb +24 -0
  123. data/spec/functional/dataset_spec.rb +77 -0
  124. data/spec/functional/project_spec.rb +24 -0
  125. data/spec/functional/table_data_spec.rb +61 -0
  126. data/spec/functional/table_spec.rb +105 -0
  127. data/spec/models/user_spec.rb +0 -0
  128. data/spec/spec_helper.rb +48 -0
  129. data/spec/support/models.rb +11 -0
  130. data/spec/support/schema.rb +43 -0
  131. metadata +370 -0
@@ -0,0 +1,949 @@
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+ require 'active_record/connection_adapters/statement_pool'
3
+ require 'arel/visitors/bind_visitor'
4
+
5
+ module ActiveRecord
6
+
7
+ module Error
8
+ class Standard < StandardError; end
9
+ class NotImplementedFeature < Standard
10
+ def message
11
+ "This Adapter doesn't offer updating single rows, Google Big query is append only by design"
12
+ end
13
+ end
14
+ class NotImplementedColumnOperation < Standard
15
+ def message
16
+ "Google big query doesn't allow this column operation"
17
+ end
18
+ end
19
+
20
+ class PendingFeature < Standard
21
+ def message
22
+ "Sorry, this is a pending feature, it will be implemented soon."
23
+ end
24
+ end
25
+ end
26
+
27
+ module ConnectionHandling # :nodoc:
28
+ # bigquery adapter reuses GoogleBigquery::Auth.
29
+ def bigquery_connection(config)
30
+
31
+ # Require database.
32
+ unless config[:database]
33
+ raise ArgumentError, "No database file specified. Missing argument: database"
34
+ end
35
+ db = GoogleBigquery::Auth.authorized? ? GoogleBigquery::Auth.client : GoogleBigquery::Auth.new.authorize
36
+ #db #quizas deberia ser auth.api o auth.client
37
+
38
+ #In case we are using a bigquery adapter as standard config in database.yml
39
+ #All models are BigQuery enabled
40
+ ActiveRecord::Base.send :include, ActiveRecord::BigQueryPersistence
41
+ ActiveRecord::SchemaMigration.send :include, ActiveRecord::BigQuerySchemaMigration
42
+ ActiveRecord::Migrator.send :include, ActiveRecord::BigQueryMigrator
43
+ ActiveRecord::Relation.send :include, ActiveRecord::BigQueryRelation
44
+ ActiveRecord::Base.send :include, ActiveRecord::BigQuerying
45
+ #db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
46
+ ConnectionAdapters::BigqueryAdapter.new(db, logger, config)
47
+ rescue => e
48
+ raise e
49
+ #Errno::ENOENT => error
50
+ #if error.message.include?("No such file or directory")
51
+ # raise ActiveRecord::NoDatabaseError.new(error.message)
52
+ #else
53
+ # raise error
54
+ #end
55
+ end
56
+ end
57
+
58
+ module BQConnector
59
+ extend ActiveSupport::Concern
60
+ module ClassMethods
61
+ def establish_bq_connection(path)
62
+ self.send :include, ActiveRecord::BigQueryPersistence
63
+ establish_connection path
64
+ end
65
+ end
66
+ end
67
+
68
+ ActiveRecord::Base.send :include, BQConnector
69
+
70
+
71
+ # = Active Record Persistence
72
+ module BigQueryPersistence
73
+ extend ActiveSupport::Concern
74
+
75
+ def delete
76
+ raise Error::NotImplementedFeature
77
+ end
78
+
79
+ module ClassMethods
80
+
81
+ end
82
+
83
+ private
84
+ # Creates a record with values matching those of the instance attributes
85
+ # and returns its id.
86
+ def create_record(attribute_names = @attributes.keys)
87
+ record_timestamps_hardcoded
88
+ attributes_values = self.changes.values.map(&:last)
89
+
90
+ row_hash = Hash[ [ self.changes.keys, attributes_values ].transpose ]
91
+ new_id = SecureRandom.hex
92
+ @rows = {"rows"=> [{
93
+ "insertId"=> Time.now.to_i.to_s,
94
+ "json"=> row_hash.merge("id"=> new_id)
95
+ }]
96
+ }
97
+ conn_cfg = self.class.connection_config
98
+ result = GoogleBigquery::TableData.create(conn_cfg[:project],
99
+ conn_cfg[:database],
100
+ self.class.table_name ,
101
+ @rows )
102
+
103
+ #raise result["error"]["errors"].map{|o| "[#{o['domain']}]: #{o['reason']} #{o['message']}" }.join(", ") if result["error"].present?
104
+ #here we output the IN MEMORY id , because of the BQ latency
105
+ self.id = new_id #||= new_id if self.class.primary_key
106
+
107
+ @new_record = false
108
+ id
109
+ end
110
+
111
+ #Partially copied from activerecord::Timezones
112
+ def record_timestamps_hardcoded
113
+ if self.record_timestamps
114
+ current_time = current_time_from_proper_timezone
115
+
116
+ all_timestamp_attributes.each do |column|
117
+ if respond_to?(column) && respond_to?("#{column}=") && self.send(column).nil?
118
+ write_attribute(column.to_s, current_time)
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ # DISABLED FEATURE, Google Big query is append only by design.
125
+ def update_record(attribute_names = @attributes.keys)
126
+ raise Error::NotImplementedFeature
127
+ end
128
+ end
129
+
130
+ # = Active Record Quering
131
+ module BigQuerying
132
+ def find_by_sql(sql, binds = [])
133
+ cfg = ActiveRecord::Base.connection_config
134
+ result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
135
+ column_types = {}
136
+
137
+ if result_set.respond_to? :column_types
138
+ column_types = result_set.column_types
139
+ else
140
+ ActiveSupport::Deprecation.warn "the object returned from `select_all` must respond to `column_types`"
141
+ end
142
+ # When AR BigQuery queries uses joins , the fields appear as [database.table].field ,
143
+ # so at least whe clean the class columns to initialize the record propperly
144
+ #"whoa1393194159_users_id".gsub(/#{@config[:database]}_#{self.table_name}_/, "")
145
+ result_set.instance_variable_set("@columns", result_set.columns.map{|o| o.gsub(/#{cfg[:database]}_#{self.table_name}_/, "") } )
146
+
147
+ result_set.map { |record| instantiate(record, column_types) }
148
+ end
149
+ end
150
+
151
+ # = Active Record Relation
152
+ module BigQueryRelation
153
+
154
+ def self.included base
155
+ base.class_eval do
156
+ def delete(id_or_array)
157
+ raise Error::NotImplementedFeature
158
+ end
159
+
160
+ def update(id, attributes)
161
+ raise Error::NotImplementedFeature
162
+ end
163
+
164
+ def destroy_all(conditions = nil)
165
+ raise Error::NotImplementedFeature
166
+ end
167
+
168
+ def destroy(id)
169
+ raise Error::NotImplementedFeature
170
+ end
171
+
172
+ def delete_all(conditions = nil)
173
+ raise Error::NotImplementedFeature
174
+ end
175
+
176
+ def update_all(updates)
177
+ raise Error::NotImplementedFeature
178
+ end
179
+ end
180
+ end
181
+ end
182
+
183
+ module BigQuerySchemaMigration
184
+
185
+ def self.included base
186
+ attr_accessor :migration_file_pwd
187
+ base.instance_eval do
188
+ def schema_migration_hash
189
+ file = schema_migration_file("r")
190
+ json = JSON.parse(file.read)
191
+ end
192
+
193
+ def schema_migration_path
194
+ Dir.pwd + "/db/schema_migrations.json"
195
+ end
196
+
197
+ def schema_migration_file(mode="w+")
198
+ file_pwd = Dir.pwd + "/db/schema_migrations.json"
199
+ File.open( file_pwd, mode )
200
+ end
201
+
202
+ def create_table(limit=nil)
203
+ @migration_file_pwd = Dir.pwd + "/db/schema_migrations.json"
204
+ unless File.exists?(@migration_file_pwd)
205
+ puts "SCHEMA MIGRATION HERE"
206
+ version_options = {null: false}
207
+ version_options[:limit] = limit if limit
208
+
209
+ #connection.create_table(table_name, id: false) do |t|
210
+ # t.column :version, :string, version_options
211
+ #end
212
+ file = schema_migration_file
213
+ file.puts({ db:{ table_name.to_sym => [] } }.to_json )
214
+ file.close
215
+ #connection.add_index table_name, :version, unique: true, name: index_name
216
+ end
217
+ end
218
+
219
+ #def self.drop_table
220
+ # binding.pry
221
+ # File.delete(schema_migration_path)
222
+ #end
223
+
224
+ def delete_version(options)
225
+ #versions = ActiveRecord::SchemaMigration.where(:version => version.to_s)
226
+ version = options[:version]
227
+ new_data = SchemaMigration.schema_migration_hash["db"]["schema_migrations"].delete_if{|o| o["version"] == version.to_s}
228
+ hsh = {:db=>{:schema_migrations => new_data } }
229
+ f = schema_migration_file
230
+ f.puts hsh.to_json
231
+ f.close
232
+ end
233
+
234
+ def create!(args, *opts)
235
+ current_data = schema_migration_hash
236
+ unless schema_migration_hash["db"]["schema_migrations"].map{|o| o["version"]}.include?(args[:version].to_s)
237
+ hsh = {:db=>{:schema_migrations => current_data["db"]["schema_migrations"] << args } }
238
+ f = schema_migration_file
239
+ f.puts hsh.to_json
240
+ f.close
241
+ end
242
+ true
243
+ end
244
+
245
+ def all
246
+ schema_migration_hash["db"]["schema_migrations"]
247
+ end
248
+
249
+ def where(args)
250
+ all.select{|o| o[args.keys.first.to_s] == args.values.first}
251
+ end
252
+ end
253
+ end
254
+ end
255
+
256
+ module BigQueryMigrator
257
+
258
+ def self.included base
259
+ #overload class methods
260
+ base.instance_eval do
261
+ def get_all_versions
262
+ SchemaMigration.all.map { |x| x["version"].to_i }.sort
263
+ end
264
+
265
+ def current_version
266
+ sm_table = schema_migrations_table_name
267
+ migration_file_pwd = Dir.pwd + "/db/schema_migrations.json"
268
+
269
+ if File.exists?(migration_file_pwd)
270
+ get_all_versions.max || 0
271
+ else
272
+ 0
273
+ end
274
+ end
275
+
276
+ def needs_migration?
277
+ current_version < last_version
278
+ end
279
+
280
+ def last_version
281
+ get_all_versions.min.to_i
282
+ #last_migration.version
283
+ end
284
+
285
+ def last_migration #:nodoc:
286
+ migrations(migrations_paths).last || NullMigration.new
287
+ end
288
+
289
+ end
290
+ #overload instance methods
291
+ base.class_eval do
292
+ def current_version
293
+ migrated.max || 0
294
+ end
295
+
296
+ def current_migration
297
+ migrations.detect { |m| m["version"] == current_version }
298
+ end
299
+
300
+ #def migrated
301
+ # @migrated_versions ||= Set.new(self.class.get_all_versions)
302
+ #end
303
+
304
+ private
305
+
306
+ def record_version_state_after_migrating(version)
307
+
308
+ if down?
309
+ migrated.delete(version)
310
+ ActiveRecord::SchemaMigration.delete_version(:version => version.to_s)
311
+ else
312
+ migrated << version
313
+ ActiveRecord::SchemaMigration.create!(:version => version.to_s)
314
+ end
315
+ end
316
+ end
317
+ end
318
+
319
+ #alias :current :current_migration
320
+ end
321
+
322
+ module LoadOperations
323
+ extend ActiveSupport::Concern
324
+ module ClassMethods
325
+ def bigquery_export(bucket_location = nil)
326
+ bucket_location = bucket_location.nil? ? "#{table_name}.json" : bucket_location
327
+ cfg = connection_config
328
+ GoogleBigquery::Jobs.export(cfg[:project],
329
+ cfg[:database],
330
+ table_name,
331
+ "#{cfg[:database]}/#{bucket_location}")
332
+ end
333
+
334
+ def bigquery_load(bucket_location = [])
335
+ bucket_location = bucket_location.empty? ? ["#{cfg[:database]}/#{table_name}.json"] : bucket_location
336
+ cfg = connection_config
337
+ fields = columns.map{|o| {name: o.name, type: o.type} }
338
+ GoogleBigquery::Jobs.load(cfg[:project],
339
+ cfg[:database],
340
+ table_name,
341
+ bucket_location,
342
+ fields)
343
+ end
344
+
345
+ def bigquery_import()
346
+ end
347
+ end
348
+ end
349
+
350
+ ActiveRecord::Base.send :include, LoadOperations
351
+
352
+ module ConnectionAdapters
353
+
354
+
355
+ class BigqueryColumn < Column
356
+ class << self
357
+ TRUE_VALUES = [true, 1, '1', 'true', 'TRUE'].to_set
358
+ FALSE_VALUES = [false, 0, '0','false', 'FALSE'].to_set
359
+
360
+ def binary_to_string(value)
361
+ if value.encoding != Encoding::ASCII_8BIT
362
+ value = value.force_encoding(Encoding::ASCII_8BIT)
363
+ end
364
+ value
365
+ end
366
+
367
+ def string_to_time(string)
368
+ return string unless string.is_a?(String)
369
+ return nil if string.empty?
370
+ fast_string_to_time(string) || fallback_string_to_time(string) || Time.at(string.to_f).send(Base.default_timezone)
371
+ end
372
+ end
373
+ end
374
+
375
+ class BigqueryAdapter < AbstractAdapter
376
+
377
+
378
+ class Version
379
+ end
380
+
381
+ class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
382
+ attr_accessor :array
383
+ end
384
+
385
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
386
+
387
+ def primary_key(name, type = :primary_key, options = {})
388
+ return column name, :string, options
389
+ end
390
+
391
+ def record(*args)
392
+ options = args.extract_options!
393
+ column(:created_at, :record, options)
394
+ end
395
+
396
+ def timestamps(*args)
397
+ options = args.extract_options!
398
+ column(:created_at, :timestamp, options)
399
+ column(:updated_at, :timestamp, options)
400
+ end
401
+
402
+ def references(*args)
403
+ options = args.extract_options!
404
+ polymorphic = options.delete(:polymorphic)
405
+ index_options = options.delete(:index)
406
+ args.each do |col|
407
+ column("#{col}_id", :string, options)
408
+ column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
409
+ index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
410
+ end
411
+ end
412
+
413
+ end
414
+
415
+ class StatementPool < ConnectionAdapters::StatementPool
416
+ def initialize(connection, max)
417
+ super
418
+ @cache = Hash.new { |h,pid| h[pid] = {} }
419
+ end
420
+
421
+ def each(&block); cache.each(&block); end
422
+ def key?(key); cache.key?(key); end
423
+ def [](key); cache[key]; end
424
+ def length; cache.length; end
425
+
426
+ def []=(sql, key)
427
+ while @max <= cache.size
428
+ dealloc(cache.shift.last[:stmt])
429
+ end
430
+ cache[sql] = key
431
+ end
432
+
433
+ def clear
434
+ cache.values.each do |hash|
435
+ dealloc hash[:stmt]
436
+ end
437
+ cache.clear
438
+ end
439
+
440
+ private
441
+ def cache
442
+ @cache[$$]
443
+ end
444
+
445
+ def dealloc(stmt)
446
+ stmt.close unless stmt.closed?
447
+ end
448
+ end
449
+
450
+ class BindSubstitution < Arel::Visitors::SQLite # :nodoc:
451
+ include Arel::Visitors::BindVisitor
452
+ end
453
+
454
+ def initialize(connection, logger, config)
455
+ super(connection, logger)
456
+
457
+ @active = nil
458
+ @statements = StatementPool.new(@connection,
459
+ self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
460
+ @config = config
461
+
462
+ #if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
463
+ # @prepared_statements = true
464
+ # @visitor = Arel::Visitors::SQLite.new self
465
+ #else
466
+ #use the sql without prepraded statements, as I know BQ doesn't support them.
467
+ @visitor = unprepared_visitor
468
+ end
469
+
470
+ def adapter_name #:nodoc:
471
+ 'BigQuery'
472
+ end
473
+
474
+ def supports_ddl_transactions?
475
+ false
476
+ end
477
+
478
+ def supports_savepoints?
479
+ false
480
+ end
481
+
482
+ def supports_partial_index?
483
+ true
484
+ end
485
+
486
+ # Returns true, since this connection adapter supports prepared statement
487
+ # caching.
488
+ def supports_statement_cache?
489
+ false
490
+ end
491
+
492
+ # Returns true, since this connection adapter supports migrations.
493
+ def supports_migrations? #:nodoc:
494
+ true
495
+ end
496
+
497
+ def supports_primary_key? #:nodoc:
498
+ true
499
+ end
500
+
501
+ def requires_reloading?
502
+ false
503
+ end
504
+
505
+ def supports_add_column?
506
+ true
507
+ end
508
+
509
+ def active?
510
+ @active != false
511
+ end
512
+
513
+ # Disconnects from the database if already connected. Otherwise, this
514
+ # method does nothing.
515
+ def disconnect!
516
+ super
517
+ @active = false
518
+ @connection.close rescue nil
519
+ end
520
+
521
+ # Clears the prepared statements cache.
522
+ def clear_cache!
523
+ @statements.clear
524
+ end
525
+
526
+ def supports_index_sort_order?
527
+ true
528
+ end
529
+
530
+ # Returns true
531
+ def supports_count_distinct? #:nodoc:
532
+ true
533
+ end
534
+
535
+ # Returns false
536
+ def supports_autoincrement? #:nodoc:
537
+ false
538
+ end
539
+
540
+ def supports_index_sort_order?
541
+ false
542
+ end
543
+
544
+ # Returns 62. SQLite supports index names up to 64
545
+ # characters. The rest is used by rails internally to perform
546
+ # temporary rename operations
547
+ def allowed_index_name_length
548
+ index_name_length - 2
549
+ end
550
+
551
+ def default_primary_key_type
552
+ if supports_autoincrement?
553
+ 'STRING'
554
+ else
555
+ 'STRING'
556
+ end
557
+ end
558
+
559
+ def native_database_types #:nodoc:
560
+ {
561
+ :primary_key => default_primary_key_type,
562
+ :string => { :name => "STRING", :default=> nil },
563
+ #:text => { :name => "text" },
564
+ :integer => { :name => "INTEGER", :default=> nil },
565
+ :float => { :name => "FLOAT", :default=> 0.0 },
566
+ #:decimal => { :name => "decimal" },
567
+ :datetime => { :name => "TIMESTAMP" },
568
+ #:timestamp => { :name => "datetime" },
569
+ :timestamp => { name: "TIMESTAMP" },
570
+ #:time => { :name => "time" },
571
+ #:date => { :name => "date" },
572
+ :record => { :name => "RECORD" },
573
+ :boolean => { :name => "BOOLEAN" }
574
+ }
575
+ end
576
+
577
+ # Returns the current database encoding format as a string, eg: 'UTF-8'
578
+ def encoding
579
+ @connection.encoding.to_s
580
+ end
581
+
582
+ # Returns false.
583
+ def supports_explain?
584
+ false
585
+ end
586
+
587
+ def create_database(database)
588
+ result = GoogleBigquery::Dataset.create(@config[:project],
589
+ {"datasetReference"=> { "datasetId" => database }} )
590
+ result
591
+ end
592
+
593
+ def drop_database(database)
594
+ tables = GoogleBigquery::Table.list(@config[:project], database)["tables"]
595
+ unless tables.blank?
596
+ tables.map!{|o| o["tableReference"]["tableId"]}
597
+ tables.each do |table_id|
598
+ GoogleBigquery::Table.delete(@config[:project], database, table_id)
599
+ end
600
+ end
601
+ result = GoogleBigquery::Dataset.delete(@config[:project], database )
602
+ result
603
+ end
604
+
605
+ # QUOTING ==================================================
606
+
607
+ def quote(value, column = nil)
608
+ if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
609
+ s = column.class.string_to_binary(value).unpack("H*")[0]
610
+ "x'#{s}'"
611
+ else
612
+ super
613
+ end
614
+ end
615
+
616
+ def quote_table_name(name)
617
+ "#{@config[:database]}.#{name}"
618
+ end
619
+
620
+ def quote_table_name_for_assignment(table, attr)
621
+ quote_column_name(attr)
622
+ end
623
+
624
+ def quote_column_name(name) #:nodoc:
625
+ name
626
+ end
627
+
628
+ # Quote date/time values for use in SQL input. Includes microseconds
629
+ # if the value is a Time responding to usec.
630
+ def quoted_date(value) #:nodoc:
631
+ if value.respond_to?(:usec)
632
+ "#{super}.#{sprintf("%06d", value.usec)}"
633
+ else
634
+ super
635
+ end
636
+ end
637
+
638
+ def quoted_true
639
+ "1"
640
+ end
641
+
642
+ def quoted_false
643
+ "0"
644
+ end
645
+
646
+ def type_cast(value, column) # :nodoc:
647
+ return value.to_f if BigDecimal === value
648
+ return super unless String === value
649
+ return super unless column && value
650
+
651
+ value = super
652
+ if column.type == :string && value.encoding == Encoding::ASCII_8BIT
653
+ logger.error "Binary data inserted for `string` type on column `#{column.name}`" if logger
654
+ value = value.encode Encoding::UTF_8
655
+ end
656
+ value
657
+ end
658
+
659
+ # DATABASE STATEMENTS ======================================
660
+
661
+ def explain(arel, binds = [])
662
+ bypass_feature
663
+ end
664
+
665
+ class ExplainPrettyPrinter
666
+ # Pretty prints the result of a EXPLAIN QUERY PLAN in a way that resembles
667
+ # the output of the SQLite shell:
668
+ #
669
+ # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
670
+ # 0|1|1|SCAN TABLE posts (~100000 rows)
671
+ #
672
+ def pp(result) # :nodoc:
673
+ result.rows.map do |row|
674
+ row.join('|')
675
+ end.join("\n") + "\n"
676
+ end
677
+ end
678
+
679
+ def exec_query(sql, name = nil, binds = [])
680
+ log(sql, name, binds) do
681
+
682
+ # Don't cache statements if they are not prepared
683
+ if without_prepared_statement?(binds)
684
+ result = GoogleBigquery::Jobs.query(@config[:project], {"query"=> sql })
685
+ cols = result["schema"]["fields"].map{|o| o["name"] }
686
+ records = result["totalRows"].to_i.zero? ? [] : result["rows"].map{|o| o["f"].map{|k,v| k["v"]} }
687
+ stmt = records
688
+ else
689
+ #binding.pry
690
+ #BQ does not support prepared statements, yiak!
691
+ end
692
+
693
+ ActiveRecord::Result.new(cols, stmt)
694
+ end
695
+ end
696
+
697
+ def exec_delete(sql, name = 'SQL', binds = [])
698
+ exec_query(sql, name, binds)
699
+ @connection.changes
700
+ end
701
+
702
+ alias :exec_update :exec_delete
703
+
704
+ def last_inserted_id(result)
705
+ @connection.last_insert_row_id
706
+ end
707
+
708
+ def execute(sql, name = nil) #:nodoc:
709
+ log(sql, name) { @connection.execute(sql) }
710
+ end
711
+
712
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
713
+ super
714
+ id_value || @connection.last_insert_row_id
715
+ end
716
+ alias :create :insert_sql
717
+
718
+ def select_rows(sql, name = nil)
719
+ exec_query(sql, name).rows
720
+ end
721
+
722
+ def begin_db_transaction #:nodoc:
723
+ log('begin transaction',nil) { } #@connection.transaction
724
+ end
725
+
726
+ def commit_db_transaction #:nodoc:
727
+ log('commit transaction',nil) { } #@connection.commit
728
+ end
729
+
730
+ def rollback_db_transaction #:nodoc:
731
+ log('rollback transaction',nil) { } #@connection.rollback
732
+ end
733
+
734
+ # SCHEMA STATEMENTS ========================================
735
+
736
+ def tables(name = nil, table_name = nil) #:nodoc:
737
+ table = GoogleBigquery::Table.list(@config[:project], @config[:database])
738
+ return [] if table["tables"].blank?
739
+ table_names = table["tables"].map{|o| o["tableReference"]["tableId"]}
740
+ table_names = table_names.select{|o| o == table_name } if table_name
741
+ table_names
742
+ end
743
+
744
+ def table_exists?(table_name)
745
+ table_name && tables(nil, table_name).any?
746
+ end
747
+
748
+ # Returns an array of +SQLite3Column+ objects for the table specified by +table_name+.
749
+ def columns(table_name) #:nodoc:
750
+ schema = GoogleBigquery::Table.get(@config[:project], @config[:database], table_name)
751
+ schema["schema"]["fields"].map do |field|
752
+ mode = field['mode'].present? && field['mode'] == "REQUIRED" ? false : true
753
+ #column expects (name, default, sql_type = nil, null = true)
754
+ BigqueryColumn.new(field['name'], nil, field['type'], mode )
755
+ end
756
+ end
757
+
758
+ # Returns an array of indexes for the given table.
759
+ def indexes(table_name, name = nil) #:nodoc:
760
+ []
761
+ end
762
+
763
+ def primary_key(table_name) #:nodoc:
764
+ "id"
765
+ end
766
+
767
+ def remove_index!(table_name, index_name) #:nodoc:
768
+ #exec_query "DROP INDEX #{quote_column_name(index_name)}"
769
+ end
770
+
771
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
772
+ if supports_add_column? && valid_alter_table_options( type, options )
773
+ super(table_name, column_name, type, options)
774
+ else
775
+ alter_table(table_name) do |definition|
776
+ definition.column(column_name, type, options)
777
+ end
778
+ end
779
+ end
780
+
781
+ # See also TableDefinition#column for details on how to create columns.
782
+ def create_table(table_name, options = {})
783
+ td = create_table_definition table_name, options[:temporary], options[:options]
784
+
785
+ unless options[:id] == false
786
+ pk = options.fetch(:primary_key) {
787
+ Base.get_primary_key table_name.to_s.singularize
788
+ }
789
+
790
+ td.primary_key pk, options.fetch(:id, :primary_key), options
791
+ end
792
+
793
+ yield td if block_given?
794
+
795
+ if options[:force] && table_exists?(table_name)
796
+ drop_table(table_name, options)
797
+ end
798
+
799
+
800
+ hsh = td.columns.map { |c| {"name"=> c[:name], "type"=> c[:type] } }
801
+
802
+ @table_body = { "tableReference"=> {
803
+ "projectId"=> @config[:project],
804
+ "datasetId"=> @config[:database],
805
+ "tableId"=> td.name},
806
+ "schema"=> [fields: hsh]
807
+ }
808
+
809
+ res = GoogleBigquery::Table.create(@config[:project], @config[:database], @table_body )
810
+
811
+ raise res["error"]["errors"].map{|o| "[#{o['domain']}]: #{o['reason']} #{o['message']}" }.join(", ") if res["error"].present?
812
+ end
813
+
814
+ # See also Table for details on all of the various column transformation.
815
+ def change_table(table_name, options = {})
816
+ if supports_bulk_alter? && options[:bulk]
817
+ recorder = ActiveRecord::Migration::CommandRecorder.new(self)
818
+ yield update_table_definition(table_name, recorder)
819
+ bulk_change_table(table_name, recorder.commands)
820
+ else
821
+ yield update_table_definition(table_name, self)
822
+ end
823
+ end
824
+ # Renames a table.
825
+ #
826
+ # Example:
827
+ # rename_table('octopuses', 'octopi')
828
+ def rename_table(table_name, new_name)
829
+ raise Error::PendingFeature
830
+ end
831
+
832
+ # See: http://www.sqlite.org/lang_altertable.html
833
+ # SQLite has an additional restriction on the ALTER TABLE statement
834
+ def valid_alter_table_options( type, options)
835
+ type.to_sym != :primary_key
836
+ end
837
+
838
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
839
+
840
+ if supports_add_column? && valid_alter_table_options( type, options )
841
+
842
+ hsh = table_name.classify.constantize.columns.map { |c| {"name"=> c.name, "type"=> c.type } }
843
+ hsh << {"name"=> column_name, :type=> type}
844
+ fields = [ fields: hsh ]
845
+
846
+ res = GoogleBigquery::Table.patch(@config[:project], @config[:database], table_name,
847
+ {"tableReference"=> {
848
+ "projectId" => @config[:project],
849
+ "datasetId" =>@config[:database],
850
+ "tableId" => table_name },
851
+ "schema" => fields,
852
+ "description"=> "added from migration"} )
853
+
854
+ else
855
+ bypass_feature
856
+ end
857
+ end
858
+
859
+ def bypass_feature
860
+ begin
861
+ raise Error::NotImplementedColumnOperation
862
+ rescue => e
863
+ puts e.message
864
+ logger.warn(e.message)
865
+ end
866
+ end
867
+
868
+ def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
869
+ bypass_feature
870
+ end
871
+
872
+ def change_column_default(table_name, column_name, default) #:nodoc:
873
+ bypass_feature
874
+ end
875
+
876
+ def change_column_null(table_name, column_name, null, default = nil)
877
+ bypass_feature
878
+ end
879
+
880
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
881
+ bypass_feature
882
+ end
883
+
884
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
885
+ bypass_feature
886
+ end
887
+
888
+ def add_reference(table_name, ref_name, options = {})
889
+ polymorphic = options.delete(:polymorphic)
890
+ index_options = options.delete(:index)
891
+ add_column(table_name, "#{ref_name}_id", :string, options)
892
+ add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
893
+ add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
894
+ end
895
+
896
+ def drop_table(table_name)
897
+ GoogleBigquery::Table.delete(@config[:project], @config[:database], table_name )
898
+ end
899
+
900
+ def dump_schema_information #:nodoc:
901
+ bypass_feature
902
+ end
903
+
904
+ def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths)
905
+ bypass_feature
906
+ end
907
+
908
+
909
+ protected
910
+ def select(sql, name = nil, binds = []) #:nodoc:
911
+ exec_query(sql, name, binds)
912
+ end
913
+
914
+ def table_structure(table_name)
915
+ structure = GoogleBigquery::Table.get(@config[:project], @config[:database], table_name)["schema"]["fields"]
916
+ raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
917
+ structure
918
+ end
919
+
920
+ def alter_table(table_name, options = {}) #:nodoc:
921
+
922
+ end
923
+
924
+ def move_table(from, to, options = {}, &block) #:nodoc:
925
+ copy_table(from, to, options, &block)
926
+ drop_table(from)
927
+ end
928
+
929
+ def copy_table(from, to, options = {}) #:nodoc:
930
+
931
+ end
932
+
933
+ def copy_table_indexes(from, to, rename = {}) #:nodoc:
934
+
935
+ end
936
+
937
+ def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
938
+
939
+ end
940
+
941
+ def create_table_definition(name, temporary, options)
942
+ TableDefinition.new native_database_types, name, temporary, options
943
+ end
944
+
945
+ end
946
+
947
+ end
948
+
949
+ end