clickhouse-ruby 0.1.0 → 0.2.0

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -1
  3. data/README.md +165 -79
  4. data/lib/clickhouse_ruby/active_record/arel_visitor.rb +205 -76
  5. data/lib/clickhouse_ruby/active_record/connection_adapter.rb +103 -98
  6. data/lib/clickhouse_ruby/active_record/railtie.rb +20 -15
  7. data/lib/clickhouse_ruby/active_record/relation_extensions.rb +398 -0
  8. data/lib/clickhouse_ruby/active_record/schema_statements.rb +90 -104
  9. data/lib/clickhouse_ruby/active_record.rb +24 -10
  10. data/lib/clickhouse_ruby/client.rb +181 -74
  11. data/lib/clickhouse_ruby/configuration.rb +51 -10
  12. data/lib/clickhouse_ruby/connection.rb +180 -64
  13. data/lib/clickhouse_ruby/connection_pool.rb +25 -19
  14. data/lib/clickhouse_ruby/errors.rb +13 -1
  15. data/lib/clickhouse_ruby/result.rb +11 -16
  16. data/lib/clickhouse_ruby/retry_handler.rb +172 -0
  17. data/lib/clickhouse_ruby/streaming_result.rb +309 -0
  18. data/lib/clickhouse_ruby/types/array.rb +11 -64
  19. data/lib/clickhouse_ruby/types/base.rb +59 -0
  20. data/lib/clickhouse_ruby/types/boolean.rb +28 -25
  21. data/lib/clickhouse_ruby/types/date_time.rb +10 -27
  22. data/lib/clickhouse_ruby/types/decimal.rb +173 -0
  23. data/lib/clickhouse_ruby/types/enum.rb +262 -0
  24. data/lib/clickhouse_ruby/types/float.rb +14 -28
  25. data/lib/clickhouse_ruby/types/integer.rb +21 -43
  26. data/lib/clickhouse_ruby/types/low_cardinality.rb +1 -1
  27. data/lib/clickhouse_ruby/types/map.rb +21 -36
  28. data/lib/clickhouse_ruby/types/null_safe.rb +81 -0
  29. data/lib/clickhouse_ruby/types/nullable.rb +2 -2
  30. data/lib/clickhouse_ruby/types/parser.rb +28 -18
  31. data/lib/clickhouse_ruby/types/registry.rb +40 -29
  32. data/lib/clickhouse_ruby/types/string.rb +9 -13
  33. data/lib/clickhouse_ruby/types/string_parser.rb +135 -0
  34. data/lib/clickhouse_ruby/types/tuple.rb +11 -68
  35. data/lib/clickhouse_ruby/types/uuid.rb +15 -22
  36. data/lib/clickhouse_ruby/types.rb +19 -15
  37. data/lib/clickhouse_ruby/version.rb +1 -1
  38. data/lib/clickhouse_ruby.rb +11 -11
  39. metadata +41 -6
@@ -26,8 +26,8 @@ module ClickhouseRuby
26
26
  ORDER BY name
27
27
  SQL
28
28
 
29
- result = execute(sql, 'SCHEMA')
30
- result.map { |row| row['name'] }
29
+ result = execute(sql, "SCHEMA")
30
+ result.map { |row| row["name"] }
31
31
  end
32
32
 
33
33
  # Returns list of views in the current database
@@ -42,8 +42,8 @@ module ClickhouseRuby
42
42
  ORDER BY name
43
43
  SQL
44
44
 
45
- result = execute(sql, 'SCHEMA')
46
- result.map { |row| row['name'] }
45
+ result = execute(sql, "SCHEMA")
46
+ result.map { |row| row["name"] }
47
47
  end
48
48
 
49
49
  # Check if a table exists
@@ -59,7 +59,7 @@ module ClickhouseRuby
59
59
  LIMIT 1
60
60
  SQL
61
61
 
62
- result = execute(sql, 'SCHEMA')
62
+ result = execute(sql, "SCHEMA")
63
63
  result.any?
64
64
  end
65
65
 
@@ -77,7 +77,7 @@ module ClickhouseRuby
77
77
  LIMIT 1
78
78
  SQL
79
79
 
80
- result = execute(sql, 'SCHEMA')
80
+ result = execute(sql, "SCHEMA")
81
81
  result.any?
82
82
  end
83
83
 
@@ -98,13 +98,13 @@ module ClickhouseRuby
98
98
  ORDER BY name
99
99
  SQL
100
100
 
101
- result = execute(sql, 'SCHEMA')
101
+ result = execute(sql, "SCHEMA")
102
102
  result.map do |row|
103
103
  {
104
- name: row['name'],
105
- type: row['type'],
106
- expression: row['expr'],
107
- granularity: row['granularity']
104
+ name: row["name"],
105
+ type: row["type"],
106
+ expression: row["expr"],
107
+ granularity: row["granularity"],
108
108
  }
109
109
  end
110
110
  end
@@ -130,14 +130,14 @@ module ClickhouseRuby
130
130
  ORDER BY position
131
131
  SQL
132
132
 
133
- result = execute(sql, 'SCHEMA')
133
+ result = execute(sql, "SCHEMA")
134
134
  result.map do |row|
135
135
  new_column(
136
- row['name'],
137
- row['default_expression'],
138
- fetch_type_metadata(row['type']),
139
- row['type'] =~ /^Nullable/i,
140
- row['comment']
136
+ row["name"],
137
+ row["default_expression"],
138
+ fetch_type_metadata(row["type"]),
139
+ row["type"] =~ /^Nullable/i,
140
+ row["comment"],
141
141
  )
142
142
  end
143
143
  end
@@ -156,8 +156,8 @@ module ClickhouseRuby
156
156
  ORDER BY position
157
157
  SQL
158
158
 
159
- result = execute(sql, 'SCHEMA')
160
- keys = result.map { |row| row['name'] }
159
+ result = execute(sql, "SCHEMA")
160
+ keys = result.map { |row| row["name"] }
161
161
  keys.empty? ? nil : keys
162
162
  end
163
163
 
@@ -173,15 +173,13 @@ module ClickhouseRuby
173
173
  # @yield [TableDefinition] the table definition block
174
174
  # @return [void]
175
175
  # @raise [ClickhouseRuby::QueryError] on error
176
- def create_table(table_name, **options, &block)
176
+ def create_table(table_name, **options)
177
177
  td = create_table_definition(table_name, **options)
178
178
 
179
- if block_given?
180
- yield td
181
- end
179
+ yield td if block_given?
182
180
 
183
181
  sql = schema_creation.accept(td)
184
- execute(sql, 'CREATE TABLE')
182
+ execute(sql, "CREATE TABLE")
185
183
  end
186
184
 
187
185
  # Drop a table
@@ -193,8 +191,8 @@ module ClickhouseRuby
193
191
  # @raise [ClickhouseRuby::QueryError] on error (unless if_exists: true)
194
192
  def drop_table(table_name, **options)
195
193
  if_exists = options.fetch(:if_exists, false)
196
- sql = "DROP TABLE #{if_exists ? 'IF EXISTS ' : ''}#{quote_table_name(table_name)}"
197
- execute(sql, 'DROP TABLE')
194
+ sql = "DROP TABLE #{if_exists ? "IF EXISTS " : ""}#{quote_table_name(table_name)}"
195
+ execute(sql, "DROP TABLE")
198
196
  end
199
197
 
200
198
  # Rename a table
@@ -205,7 +203,7 @@ module ClickhouseRuby
205
203
  # @raise [ClickhouseRuby::QueryError] on error
206
204
  def rename_table(old_name, new_name)
207
205
  sql = "RENAME TABLE #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
208
- execute(sql, 'RENAME TABLE')
206
+ execute(sql, "RENAME TABLE")
209
207
  end
210
208
 
211
209
  # Truncate a table (delete all data)
@@ -214,9 +212,9 @@ module ClickhouseRuby
214
212
  # @param options [Hash] truncate options
215
213
  # @return [void]
216
214
  # @raise [ClickhouseRuby::QueryError] on error
217
- def truncate_table(table_name, **options)
215
+ def truncate_table(table_name, **_options)
218
216
  sql = "TRUNCATE TABLE #{quote_table_name(table_name)}"
219
- execute(sql, 'TRUNCATE TABLE')
217
+ execute(sql, "TRUNCATE TABLE")
220
218
  end
221
219
 
222
220
  # Add a column to a table
@@ -234,23 +232,17 @@ module ClickhouseRuby
234
232
  sql_type = type_to_sql(type, **options)
235
233
 
236
234
  # Handle nullable
237
- if options[:null] != false && !sql_type.match?(/^Nullable/i)
238
- sql_type = "Nullable(#{sql_type})"
239
- end
235
+ sql_type = "Nullable(#{sql_type})" if options[:null] != false && !sql_type.match?(/^Nullable/i)
240
236
 
241
237
  sql = "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{sql_type}"
242
238
 
243
239
  # Add AFTER clause if specified
244
- if options[:after]
245
- sql += " AFTER #{quote_column_name(options[:after])}"
246
- end
240
+ sql += " AFTER #{quote_column_name(options[:after])}" if options[:after]
247
241
 
248
242
  # Add DEFAULT if specified
249
- if options.key?(:default)
250
- sql += " DEFAULT #{quote(options[:default])}"
251
- end
243
+ sql += " DEFAULT #{quote(options[:default])}" if options.key?(:default)
252
244
 
253
- execute(sql, 'ADD COLUMN')
245
+ execute(sql, "ADD COLUMN")
254
246
  end
255
247
 
256
248
  # Remove a column from a table
@@ -260,9 +252,9 @@ module ClickhouseRuby
260
252
  # @param options [Hash] options (unused)
261
253
  # @return [void]
262
254
  # @raise [ClickhouseRuby::QueryError] on error
263
- def remove_column(table_name, column_name, _type = nil, **options)
255
+ def remove_column(table_name, column_name, _type = nil, **_options)
264
256
  sql = "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
265
- execute(sql, 'DROP COLUMN')
257
+ execute(sql, "DROP COLUMN")
266
258
  end
267
259
 
268
260
  # Rename a column
@@ -274,7 +266,7 @@ module ClickhouseRuby
274
266
  # @raise [ClickhouseRuby::QueryError] on error
275
267
  def rename_column(table_name, old_name, new_name)
276
268
  sql = "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(old_name)} TO #{quote_column_name(new_name)}"
277
- execute(sql, 'RENAME COLUMN')
269
+ execute(sql, "RENAME COLUMN")
278
270
  end
279
271
 
280
272
  # Change a column's type
@@ -289,18 +281,14 @@ module ClickhouseRuby
289
281
  sql_type = type_to_sql(type, **options)
290
282
 
291
283
  # Handle nullable
292
- if options[:null] != false && !sql_type.match?(/^Nullable/i)
293
- sql_type = "Nullable(#{sql_type})"
294
- end
284
+ sql_type = "Nullable(#{sql_type})" if options[:null] != false && !sql_type.match?(/^Nullable/i)
295
285
 
296
286
  sql = "ALTER TABLE #{quote_table_name(table_name)} MODIFY COLUMN #{quote_column_name(column_name)} #{sql_type}"
297
287
 
298
288
  # Add DEFAULT if specified
299
- if options.key?(:default)
300
- sql += " DEFAULT #{quote(options[:default])}"
301
- end
289
+ sql += " DEFAULT #{quote(options[:default])}" if options.key?(:default)
302
290
 
303
- execute(sql, 'MODIFY COLUMN')
291
+ execute(sql, "MODIFY COLUMN")
304
292
  end
305
293
 
306
294
  # Add an index to a table
@@ -315,13 +303,13 @@ module ClickhouseRuby
315
303
  # @return [void]
316
304
  # @raise [ClickhouseRuby::QueryError] on error
317
305
  def add_index(table_name, column_name, **options)
318
- columns = Array(column_name).map { |c| quote_column_name(c) }.join(', ')
319
- index_name = options[:name] || "idx_#{Array(column_name).join('_')}"
320
- index_type = options[:type] || 'minmax'
306
+ columns = Array(column_name).map { |c| quote_column_name(c) }.join(", ")
307
+ index_name = options[:name] || "idx_#{Array(column_name).join("_")}"
308
+ index_type = options[:type] || "minmax"
321
309
  granularity = options[:granularity] || 1
322
310
 
323
311
  sql = "ALTER TABLE #{quote_table_name(table_name)} ADD INDEX #{quote_column_name(index_name)} (#{columns}) TYPE #{index_type} GRANULARITY #{granularity}"
324
- execute(sql, 'ADD INDEX')
312
+ execute(sql, "ADD INDEX")
325
313
  end
326
314
 
327
315
  # Remove an index from a table
@@ -336,11 +324,11 @@ module ClickhouseRuby
336
324
  elsif options[:name]
337
325
  options[:name]
338
326
  else
339
- "idx_#{Array(options_or_column).join('_')}"
327
+ "idx_#{Array(options_or_column).join("_")}"
340
328
  end
341
329
 
342
330
  sql = "ALTER TABLE #{quote_table_name(table_name)} DROP INDEX #{quote_column_name(index_name)}"
343
- execute(sql, 'DROP INDEX')
331
+ execute(sql, "DROP INDEX")
344
332
  end
345
333
 
346
334
  # Check if an index exists
@@ -358,7 +346,7 @@ module ClickhouseRuby
358
346
  LIMIT 1
359
347
  SQL
360
348
 
361
- result = execute(sql, 'SCHEMA')
349
+ result = execute(sql, "SCHEMA")
362
350
  result.any?
363
351
  end
364
352
 
@@ -377,12 +365,12 @@ module ClickhouseRuby
377
365
  LIMIT 1
378
366
  SQL
379
367
 
380
- result = execute(sql, 'SCHEMA')
368
+ result = execute(sql, "SCHEMA")
381
369
  return false if result.empty?
382
370
 
383
371
  if type
384
372
  # Check if type matches
385
- column_type = result.first['type']
373
+ column_type = result.first["type"]
386
374
  expected_type = type_to_sql(type, **options)
387
375
  column_type.downcase.include?(expected_type.downcase)
388
376
  else
@@ -394,16 +382,16 @@ module ClickhouseRuby
394
382
  #
395
383
  # @return [String] the database name
396
384
  def current_database
397
- result = execute('SELECT currentDatabase() AS db', 'SCHEMA')
398
- result.first['db']
385
+ result = execute("SELECT currentDatabase() AS db", "SCHEMA")
386
+ result.first["db"]
399
387
  end
400
388
 
401
389
  # List all databases
402
390
  #
403
391
  # @return [Array<String>] list of database names
404
392
  def databases
405
- result = execute('SELECT name FROM system.databases ORDER BY name', 'SCHEMA')
406
- result.map { |row| row['name'] }
393
+ result = execute("SELECT name FROM system.databases ORDER BY name", "SCHEMA")
394
+ result.map { |row| row["name"] }
407
395
  end
408
396
 
409
397
  # Create a database
@@ -415,8 +403,8 @@ module ClickhouseRuby
415
403
  # @raise [ClickhouseRuby::QueryError] on error
416
404
  def create_database(database_name, **options)
417
405
  if_not_exists = options.fetch(:if_not_exists, false)
418
- sql = "CREATE DATABASE #{if_not_exists ? 'IF NOT EXISTS ' : ''}`#{database_name}`"
419
- execute(sql, 'CREATE DATABASE')
406
+ sql = "CREATE DATABASE #{if_not_exists ? "IF NOT EXISTS " : ""}`#{database_name}`"
407
+ execute(sql, "CREATE DATABASE")
420
408
  end
421
409
 
422
410
  # Drop a database
@@ -428,8 +416,8 @@ module ClickhouseRuby
428
416
  # @raise [ClickhouseRuby::QueryError] on error
429
417
  def drop_database(database_name, **options)
430
418
  if_exists = options.fetch(:if_exists, false)
431
- sql = "DROP DATABASE #{if_exists ? 'IF EXISTS ' : ''}`#{database_name}`"
432
- execute(sql, 'DROP DATABASE')
419
+ sql = "DROP DATABASE #{if_exists ? "IF EXISTS " : ""}`#{database_name}`"
420
+ execute(sql, "DROP DATABASE")
433
421
  end
434
422
 
435
423
  private
@@ -444,25 +432,25 @@ module ClickhouseRuby
444
432
 
445
433
  case type
446
434
  when :primary_key
447
- 'UInt64'
435
+ "UInt64"
448
436
  when :string, :text
449
437
  if options[:limit]
450
438
  "FixedString(#{options[:limit]})"
451
439
  else
452
- 'String'
440
+ "String"
453
441
  end
454
442
  when :integer
455
443
  case options[:limit]
456
- when 1 then 'Int8'
457
- when 2 then 'Int16'
458
- when 3, 4 then 'Int32'
459
- when 5, 6, 7, 8 then 'Int64'
460
- else 'Int32'
444
+ when 1 then "Int8"
445
+ when 2 then "Int16"
446
+ when 3, 4 then "Int32"
447
+ when 5, 6, 7, 8 then "Int64"
448
+ else "Int32"
461
449
  end
462
450
  when :bigint
463
- 'Int64'
451
+ "Int64"
464
452
  when :float
465
- options[:limit] == 8 ? 'Float64' : 'Float32'
453
+ options[:limit] == 8 ? "Float64" : "Float32"
466
454
  when :decimal
467
455
  precision = options[:precision] || 10
468
456
  scale = options[:scale] || 0
@@ -471,22 +459,22 @@ module ClickhouseRuby
471
459
  if options[:precision]
472
460
  "DateTime64(#{options[:precision]})"
473
461
  else
474
- 'DateTime'
462
+ "DateTime"
475
463
  end
476
464
  when :timestamp
477
465
  "DateTime64(#{options[:precision] || 3})"
478
466
  when :time
479
- 'DateTime'
467
+ "DateTime"
480
468
  when :date
481
- 'Date'
469
+ "Date"
482
470
  when :binary
483
- 'String'
471
+ "String"
484
472
  when :boolean
485
- 'UInt8'
473
+ "UInt8"
486
474
  when :uuid
487
- 'UUID'
475
+ "UUID"
488
476
  when :json
489
- 'String'
477
+ "String"
490
478
  else
491
479
  # Return as-is if it's a ClickHouse type
492
480
  type.to_s
@@ -507,7 +495,7 @@ module ClickhouseRuby
507
495
  default,
508
496
  sql_type_metadata,
509
497
  null,
510
- comment: comment
498
+ comment: comment,
511
499
  )
512
500
  end
513
501
 
@@ -522,7 +510,7 @@ module ClickhouseRuby
522
510
  type: cast_type.type,
523
511
  limit: cast_type.limit,
524
512
  precision: cast_type.precision,
525
- scale: cast_type.scale
513
+ scale: cast_type.scale,
526
514
  )
527
515
  end
528
516
 
@@ -610,7 +598,7 @@ module ClickhouseRuby
610
598
  column_sql(col)
611
599
  end.join(",\n ")
612
600
 
613
- engine = table_definition.options[:engine] || 'MergeTree'
601
+ engine = table_definition.options[:engine] || "MergeTree"
614
602
  order_by = table_definition.options[:order_by]
615
603
  partition_by = table_definition.options[:partition_by]
616
604
  primary_key = table_definition.options[:primary_key]
@@ -639,9 +627,7 @@ module ClickhouseRuby
639
627
  sql = "#{@adapter.quote_column_name(col[:name])} #{type}"
640
628
 
641
629
  # Add DEFAULT if specified
642
- if col[:options].key?(:default)
643
- sql += " DEFAULT #{@adapter.quote(col[:options][:default])}"
644
- end
630
+ sql += " DEFAULT #{@adapter.quote(col[:options][:default])}" if col[:options].key?(:default)
645
631
 
646
632
  sql
647
633
  end
@@ -651,39 +637,39 @@ module ClickhouseRuby
651
637
 
652
638
  case type
653
639
  when :primary_key
654
- 'UInt64'
640
+ "UInt64"
655
641
  when :string, :text
656
- options[:limit] ? "FixedString(#{options[:limit]})" : 'String'
642
+ options[:limit] ? "FixedString(#{options[:limit]})" : "String"
657
643
  when :integer
658
644
  case options[:limit]
659
- when 1 then 'Int8'
660
- when 2 then 'Int16'
661
- when 3, 4 then 'Int32'
662
- when 5, 6, 7, 8 then 'Int64'
663
- else 'Int32'
645
+ when 1 then "Int8"
646
+ when 2 then "Int16"
647
+ when 3, 4 then "Int32"
648
+ when 5, 6, 7, 8 then "Int64"
649
+ else "Int32"
664
650
  end
665
651
  when :bigint
666
- 'Int64'
652
+ "Int64"
667
653
  when :float
668
- options[:limit] == 8 ? 'Float64' : 'Float32'
654
+ options[:limit] == 8 ? "Float64" : "Float32"
669
655
  when :decimal
670
656
  "Decimal(#{options[:precision] || 10}, #{options[:scale] || 0})"
671
657
  when :datetime
672
- options[:precision] ? "DateTime64(#{options[:precision]})" : 'DateTime'
658
+ options[:precision] ? "DateTime64(#{options[:precision]})" : "DateTime"
673
659
  when :timestamp
674
660
  "DateTime64(#{options[:precision] || 3})"
675
661
  when :time
676
- 'DateTime'
662
+ "DateTime"
677
663
  when :date
678
- 'Date'
664
+ "Date"
679
665
  when :binary
680
- 'String'
666
+ "String"
681
667
  when :boolean
682
- 'UInt8'
668
+ "UInt8"
683
669
  when :uuid
684
- 'UUID'
670
+ "UUID"
685
671
  when :json
686
- 'String'
672
+ "String"
687
673
  else
688
674
  type.to_s
689
675
  end
@@ -1,16 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_record'
4
- require 'active_record/connection_adapters/abstract_adapter'
3
+ require "active_record"
4
+ require "active_record/connection_adapters/abstract_adapter"
5
5
 
6
- require_relative 'active_record/arel_visitor'
7
- require_relative 'active_record/schema_statements'
8
- require_relative 'active_record/connection_adapter'
6
+ require_relative "active_record/arel_visitor"
7
+ require_relative "active_record/schema_statements"
8
+ require_relative "active_record/relation_extensions"
9
+ require_relative "active_record/connection_adapter"
9
10
 
10
11
  # Load Railtie if Rails is available
11
- if defined?(Rails::Railtie)
12
- require_relative 'active_record/railtie'
13
- end
12
+ require_relative "active_record/railtie" if defined?(Rails::Railtie)
14
13
 
15
14
  module ClickhouseRuby
16
15
  # ActiveRecord integration for ClickHouse
@@ -83,7 +82,7 @@ module ClickhouseRuby
83
82
  def registered?
84
83
  defined?(::ActiveRecord::ConnectionAdapters) &&
85
84
  ::ActiveRecord::ConnectionAdapters.respond_to?(:resolve) &&
86
- ::ActiveRecord::ConnectionAdapters.resolve('clickhouse').present?
85
+ ::ActiveRecord::ConnectionAdapters.resolve("clickhouse").present?
87
86
  rescue StandardError
88
87
  false
89
88
  end
@@ -95,6 +94,21 @@ module ClickhouseRuby
95
94
  ClickhouseRuby::VERSION
96
95
  end
97
96
  end
97
+
98
+ # Base class for ClickHouse models
99
+ #
100
+ # All ClickHouse models should inherit from this class or configure
101
+ # the connection manually.
102
+ #
103
+ # @example
104
+ # class Event < ClickhouseRuby::ActiveRecord::Base
105
+ # self.table_name = 'events'
106
+ # end
107
+ #
108
+ # Event.where(user_id: 123).count
109
+ class Base < ::ActiveRecord::Base
110
+ self.abstract_class = true
111
+ end
98
112
  end
99
113
  end
100
114
 
@@ -113,7 +127,7 @@ module ActiveRecord
113
127
  nil,
114
128
  logger,
115
129
  nil,
116
- config
130
+ config,
117
131
  )
118
132
  end
119
133
  end