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
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'arel/visitors/to_sql'
3
+ require "arel/visitors/to_sql"
4
4
 
5
5
  module ClickhouseRuby
6
6
  module ActiveRecord
@@ -42,18 +42,18 @@ module ClickhouseRuby
42
42
  table = o.relation
43
43
 
44
44
  # Build ClickHouse DELETE syntax
45
- collector << 'ALTER TABLE '
45
+ collector << "ALTER TABLE "
46
46
  collector = visit(table, collector)
47
- collector << ' DELETE'
47
+ collector << " DELETE"
48
48
 
49
49
  # Add WHERE clause (required for ClickHouse DELETE)
50
50
  if o.wheres.any?
51
- collector << ' WHERE '
52
- collector = inject_join(o.wheres, collector, ' AND ')
51
+ collector << " WHERE "
52
+ collector = inject_join(o.wheres, collector, " AND ")
53
53
  else
54
54
  # ClickHouse requires WHERE clause for DELETE
55
55
  # Use 1=1 to delete all rows
56
- collector << ' WHERE 1=1'
56
+ collector << " WHERE 1=1"
57
57
  end
58
58
 
59
59
  collector
@@ -70,22 +70,79 @@ module ClickhouseRuby
70
70
  table = o.relation
71
71
 
72
72
  # Build ClickHouse UPDATE syntax
73
- collector << 'ALTER TABLE '
73
+ collector << "ALTER TABLE "
74
74
  collector = visit(table, collector)
75
- collector << ' UPDATE '
75
+ collector << " UPDATE "
76
76
 
77
77
  # Add SET assignments
78
- unless o.values.empty?
79
- collector = inject_join(o.values, collector, ', ')
80
- end
78
+ collector = inject_join(o.values, collector, ", ") unless o.values.empty?
81
79
 
82
80
  # Add WHERE clause (required for ClickHouse UPDATE)
83
81
  if o.wheres.any?
84
- collector << ' WHERE '
85
- collector = inject_join(o.wheres, collector, ' AND ')
82
+ collector << " WHERE "
83
+ collector = inject_join(o.wheres, collector, " AND ")
86
84
  else
87
85
  # ClickHouse requires WHERE clause for UPDATE
88
- collector << ' WHERE 1=1'
86
+ collector << " WHERE 1=1"
87
+ end
88
+
89
+ collector
90
+ end
91
+
92
+ # Visit a SELECT statement
93
+ # Ensures proper ordering of ClickHouse-specific clauses
94
+ #
95
+ # Clause ordering for ClickHouse:
96
+ # SELECT ... FROM table [FINAL] [SAMPLE n] [PREWHERE ...] [WHERE ...] [GROUP BY ...] [ORDER BY ...] [LIMIT n]
97
+ #
98
+ # @param o [Arel::Nodes::SelectStatement] the select node
99
+ # @param collector [Arel::Collectors::SQLString] SQL collector
100
+ # @return [Arel::Collectors::SQLString] the collector with SQL
101
+ def visit_Arel_Nodes_SelectStatement(o, collector)
102
+ collector = visit_Arel_Nodes_SelectCore(o.cores[0], collector)
103
+
104
+ # FROM clause
105
+ collector = visit(o.cores[0].source, collector) if o.cores[0].source
106
+
107
+ # Get ClickHouse-specific state from the Arel node (set by RelationExtensions#build_arel)
108
+ use_final = o.instance_variable_get(:@clickhouse_final)
109
+ sample_value = o.instance_variable_get(:@clickhouse_sample_value)
110
+ sample_offset = o.instance_variable_get(:@clickhouse_sample_offset)
111
+ prewhere_values = o.instance_variable_get(:@clickhouse_prewhere_values)
112
+ query_settings = o.instance_variable_get(:@clickhouse_query_settings)
113
+
114
+ # FINAL clause (if set)
115
+ collector << " FINAL" if use_final
116
+
117
+ # SAMPLE clause (if set)
118
+ if sample_value
119
+ collector << " SAMPLE "
120
+ collector << format_sample_value(sample_value)
121
+ if sample_offset
122
+ collector << " OFFSET "
123
+ collector << sample_offset.to_s
124
+ end
125
+ end
126
+
127
+ # PREWHERE clause (if set)
128
+ if prewhere_values&.any?
129
+ collector << " PREWHERE "
130
+ collector = visit_prewhere_conditions(prewhere_values, collector)
131
+ end
132
+
133
+ # WHERE clause
134
+ if o.cores[0].wheres.any?
135
+ collector << " WHERE "
136
+ collector = inject_join(o.cores[0].wheres, collector, " AND ")
137
+ end
138
+
139
+ # GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET
140
+ collector = visit_orders_and_limits(o, collector)
141
+
142
+ # SETTINGS clause (at the very end)
143
+ if query_settings&.any?
144
+ collector << " "
145
+ collector << build_settings_clause(query_settings)
89
146
  end
90
147
 
91
148
  collector
@@ -98,7 +155,7 @@ module ClickhouseRuby
98
155
  # @param collector [Arel::Collectors::SQLString] SQL collector
99
156
  # @return [Arel::Collectors::SQLString] the collector with SQL
100
157
  def visit_Arel_Nodes_Limit(o, collector)
101
- collector << 'LIMIT '
158
+ collector << "LIMIT "
102
159
  visit(o.expr, collector)
103
160
  end
104
161
 
@@ -109,7 +166,7 @@ module ClickhouseRuby
109
166
  # @param collector [Arel::Collectors::SQLString] SQL collector
110
167
  # @return [Arel::Collectors::SQLString] the collector with SQL
111
168
  def visit_Arel_Nodes_Offset(o, collector)
112
- collector << 'OFFSET '
169
+ collector << "OFFSET "
113
170
  visit(o.expr, collector)
114
171
  end
115
172
 
@@ -119,10 +176,6 @@ module ClickhouseRuby
119
176
  # @param o [Arel::Nodes::SelectStatement] the select node
120
177
  # @param collector [Arel::Collectors::SQLString] SQL collector
121
178
  # @return [Arel::Collectors::SQLString] the collector with SQL
122
- def visit_Arel_Nodes_SelectStatement(o, collector)
123
- # Use default behavior but ensure ClickHouse compatibility
124
- super
125
- end
126
179
 
127
180
  # Visit a table alias
128
181
  # ClickHouse uses AS keyword for table aliases
@@ -132,7 +185,7 @@ module ClickhouseRuby
132
185
  # @return [Arel::Collectors::SQLString] the collector with SQL
133
186
  def visit_Arel_Nodes_TableAlias(o, collector)
134
187
  collector = visit(o.relation, collector)
135
- collector << ' AS '
188
+ collector << " AS "
136
189
  collector << quote_table_name(o.name)
137
190
  end
138
191
 
@@ -157,8 +210,8 @@ module ClickhouseRuby
157
210
  # @param o [Arel::Nodes::True] the true node
158
211
  # @param collector [Arel::Collectors::SQLString] SQL collector
159
212
  # @return [Arel::Collectors::SQLString] the collector with SQL
160
- def visit_Arel_Nodes_True(o, collector)
161
- collector << '1'
213
+ def visit_Arel_Nodes_True(_o, collector)
214
+ collector << "1"
162
215
  end
163
216
 
164
217
  # Visit a False node
@@ -166,8 +219,8 @@ module ClickhouseRuby
166
219
  # @param o [Arel::Nodes::False] the false node
167
220
  # @param collector [Arel::Collectors::SQLString] SQL collector
168
221
  # @return [Arel::Collectors::SQLString] the collector with SQL
169
- def visit_Arel_Nodes_False(o, collector)
170
- collector << '0'
222
+ def visit_Arel_Nodes_False(_o, collector)
223
+ collector << "0"
171
224
  end
172
225
 
173
226
  # Visit a CASE statement
@@ -176,25 +229,25 @@ module ClickhouseRuby
176
229
  # @param collector [Arel::Collectors::SQLString] SQL collector
177
230
  # @return [Arel::Collectors::SQLString] the collector with SQL
178
231
  def visit_Arel_Nodes_Case(o, collector)
179
- collector << 'CASE '
232
+ collector << "CASE "
180
233
 
181
234
  if o.case
182
235
  visit(o.case, collector)
183
- collector << ' '
236
+ collector << " "
184
237
  end
185
238
 
186
239
  o.conditions.each do |condition|
187
240
  visit(condition, collector)
188
- collector << ' '
241
+ collector << " "
189
242
  end
190
243
 
191
244
  if o.default
192
- collector << 'ELSE '
245
+ collector << "ELSE "
193
246
  visit(o.default, collector)
194
- collector << ' '
247
+ collector << " "
195
248
  end
196
249
 
197
- collector << 'END'
250
+ collector << "END"
198
251
  end
199
252
 
200
253
  # Handle INSERT statements
@@ -204,23 +257,23 @@ module ClickhouseRuby
204
257
  # @param collector [Arel::Collectors::SQLString] SQL collector
205
258
  # @return [Arel::Collectors::SQLString] the collector with SQL
206
259
  def visit_Arel_Nodes_InsertStatement(o, collector)
207
- collector << 'INSERT INTO '
260
+ collector << "INSERT INTO "
208
261
  collector = visit(o.relation, collector)
209
262
 
210
263
  if o.columns.any?
211
- collector << ' ('
264
+ collector << " ("
212
265
  o.columns.each_with_index do |column, i|
213
- collector << ', ' if i > 0
266
+ collector << ", " if i.positive?
214
267
  collector << quote_column_name(column.name)
215
268
  end
216
- collector << ')'
269
+ collector << ")"
217
270
  end
218
271
 
219
272
  if o.values
220
- collector << ' VALUES '
273
+ collector << " VALUES "
221
274
  collector = visit(o.values, collector)
222
275
  elsif o.select
223
- collector << ' '
276
+ collector << " "
224
277
  collector = visit(o.select, collector)
225
278
  end
226
279
 
@@ -233,19 +286,19 @@ module ClickhouseRuby
233
286
  # @param collector [Arel::Collectors::SQLString] SQL collector
234
287
  # @return [Arel::Collectors::SQLString] the collector with SQL
235
288
  def visit_Arel_Nodes_Values(o, collector)
236
- collector << '('
289
+ collector << "("
237
290
  o.expressions.each_with_index do |expr, i|
238
- collector << ', ' if i > 0
291
+ collector << ", " if i.positive?
239
292
  case expr
240
293
  when Arel::Nodes::SqlLiteral
241
294
  collector << expr.to_s
242
295
  when nil
243
- collector << 'NULL'
296
+ collector << "NULL"
244
297
  else
245
298
  collector = visit(expr, collector)
246
299
  end
247
300
  end
248
- collector << ')'
301
+ collector << ")"
249
302
  end
250
303
 
251
304
  # Handle multiple VALUES rows for bulk insert
@@ -255,20 +308,20 @@ module ClickhouseRuby
255
308
  # @return [Arel::Collectors::SQLString] the collector with SQL
256
309
  def visit_Arel_Nodes_ValuesList(o, collector)
257
310
  o.rows.each_with_index do |row, i|
258
- collector << ', ' if i > 0
259
- collector << '('
311
+ collector << ", " if i.positive?
312
+ collector << "("
260
313
  row.each_with_index do |value, j|
261
- collector << ', ' if j > 0
314
+ collector << ", " if j.positive?
262
315
  case value
263
316
  when Arel::Nodes::SqlLiteral
264
317
  collector << value.to_s
265
318
  when nil
266
- collector << 'NULL'
319
+ collector << "NULL"
267
320
  else
268
321
  collector = visit(value, collector)
269
322
  end
270
323
  end
271
- collector << ')'
324
+ collector << ")"
272
325
  end
273
326
  collector
274
327
  end
@@ -280,19 +333,17 @@ module ClickhouseRuby
280
333
  # @return [Arel::Collectors::SQLString] the collector with SQL
281
334
  def visit_Arel_Nodes_Assignment(o, collector)
282
335
  case o.left
283
- when Arel::Nodes::UnqualifiedColumn
284
- collector << quote_column_name(o.left.name)
285
- when Arel::Attributes::Attribute
336
+ when Arel::Nodes::UnqualifiedColumn, Arel::Attributes::Attribute
286
337
  collector << quote_column_name(o.left.name)
287
338
  else
288
339
  collector = visit(o.left, collector)
289
340
  end
290
341
 
291
- collector << ' = '
342
+ collector << " = "
292
343
 
293
344
  case o.right
294
345
  when nil
295
- collector << 'NULL'
346
+ collector << "NULL"
296
347
  else
297
348
  collector = visit(o.right, collector)
298
349
  end
@@ -307,21 +358,19 @@ module ClickhouseRuby
307
358
  # @return [Arel::Collectors::SQLString] the collector with SQL
308
359
  def visit_Arel_Nodes_NamedFunction(o, collector)
309
360
  collector << o.name
310
- collector << '('
361
+ collector << "("
311
362
 
312
- if o.distinct
313
- collector << 'DISTINCT '
314
- end
363
+ collector << "DISTINCT " if o.distinct
315
364
 
316
365
  o.expressions.each_with_index do |expr, i|
317
- collector << ', ' if i > 0
366
+ collector << ", " if i.positive?
318
367
  collector = visit(expr, collector)
319
368
  end
320
369
 
321
- collector << ')'
370
+ collector << ")"
322
371
 
323
372
  if o.alias
324
- collector << ' AS '
373
+ collector << " AS "
325
374
  collector << quote_column_name(o.alias)
326
375
  end
327
376
 
@@ -333,8 +382,8 @@ module ClickhouseRuby
333
382
  # @param o [Arel::Nodes::Distinct] the distinct node
334
383
  # @param collector [Arel::Collectors::SQLString] SQL collector
335
384
  # @return [Arel::Collectors::SQLString] the collector with SQL
336
- def visit_Arel_Nodes_Distinct(o, collector)
337
- collector << 'DISTINCT'
385
+ def visit_Arel_Nodes_Distinct(_o, collector)
386
+ collector << "DISTINCT"
338
387
  end
339
388
 
340
389
  # Handle GROUP BY
@@ -352,7 +401,7 @@ module ClickhouseRuby
352
401
  # @param collector [Arel::Collectors::SQLString] SQL collector
353
402
  # @return [Arel::Collectors::SQLString] the collector with SQL
354
403
  def visit_Arel_Nodes_Having(o, collector)
355
- collector << 'HAVING '
404
+ collector << "HAVING "
356
405
  visit(o.expr, collector)
357
406
  end
358
407
 
@@ -363,7 +412,7 @@ module ClickhouseRuby
363
412
  # @return [Arel::Collectors::SQLString] the collector with SQL
364
413
  def visit_Arel_Nodes_Ascending(o, collector)
365
414
  collector = visit(o.expr, collector)
366
- collector << ' ASC'
415
+ collector << " ASC"
367
416
  end
368
417
 
369
418
  # Handle descending order
@@ -373,7 +422,7 @@ module ClickhouseRuby
373
422
  # @return [Arel::Collectors::SQLString] the collector with SQL
374
423
  def visit_Arel_Nodes_Descending(o, collector)
375
424
  collector = visit(o.expr, collector)
376
- collector << ' DESC'
425
+ collector << " DESC"
377
426
  end
378
427
 
379
428
  # Handle NULLS FIRST
@@ -383,7 +432,7 @@ module ClickhouseRuby
383
432
  # @return [Arel::Collectors::SQLString] the collector with SQL
384
433
  def visit_Arel_Nodes_NullsFirst(o, collector)
385
434
  collector = visit(o.expr, collector)
386
- collector << ' NULLS FIRST'
435
+ collector << " NULLS FIRST"
387
436
  end
388
437
 
389
438
  # Handle NULLS LAST
@@ -393,7 +442,7 @@ module ClickhouseRuby
393
442
  # @return [Arel::Collectors::SQLString] the collector with SQL
394
443
  def visit_Arel_Nodes_NullsLast(o, collector)
395
444
  collector = visit(o.expr, collector)
396
- collector << ' NULLS LAST'
445
+ collector << " NULLS LAST"
397
446
  end
398
447
 
399
448
  # Handle COUNT function
@@ -402,7 +451,7 @@ module ClickhouseRuby
402
451
  # @param collector [Arel::Collectors::SQLString] SQL collector
403
452
  # @return [Arel::Collectors::SQLString] the collector with SQL
404
453
  def visit_Arel_Nodes_Count(o, collector)
405
- aggregate('count', o, collector)
454
+ aggregate("count", o, collector)
406
455
  end
407
456
 
408
457
  # Handle SUM function
@@ -411,7 +460,7 @@ module ClickhouseRuby
411
460
  # @param collector [Arel::Collectors::SQLString] SQL collector
412
461
  # @return [Arel::Collectors::SQLString] the collector with SQL
413
462
  def visit_Arel_Nodes_Sum(o, collector)
414
- aggregate('sum', o, collector)
463
+ aggregate("sum", o, collector)
415
464
  end
416
465
 
417
466
  # Handle AVG function
@@ -420,7 +469,7 @@ module ClickhouseRuby
420
469
  # @param collector [Arel::Collectors::SQLString] SQL collector
421
470
  # @return [Arel::Collectors::SQLString] the collector with SQL
422
471
  def visit_Arel_Nodes_Avg(o, collector)
423
- aggregate('avg', o, collector)
472
+ aggregate("avg", o, collector)
424
473
  end
425
474
 
426
475
  # Handle MIN function
@@ -429,7 +478,7 @@ module ClickhouseRuby
429
478
  # @param collector [Arel::Collectors::SQLString] SQL collector
430
479
  # @return [Arel::Collectors::SQLString] the collector with SQL
431
480
  def visit_Arel_Nodes_Min(o, collector)
432
- aggregate('min', o, collector)
481
+ aggregate("min", o, collector)
433
482
  end
434
483
 
435
484
  # Handle MAX function
@@ -438,7 +487,7 @@ module ClickhouseRuby
438
487
  # @param collector [Arel::Collectors::SQLString] SQL collector
439
488
  # @return [Arel::Collectors::SQLString] the collector with SQL
440
489
  def visit_Arel_Nodes_Max(o, collector)
441
- aggregate('max', o, collector)
490
+ aggregate("max", o, collector)
442
491
  end
443
492
 
444
493
  # Helper to generate aggregate functions
@@ -449,20 +498,100 @@ module ClickhouseRuby
449
498
  # @return [Arel::Collectors::SQLString] the collector with SQL
450
499
  def aggregate(name, o, collector)
451
500
  collector << "#{name}("
452
- if o.distinct
453
- collector << 'DISTINCT '
454
- end
501
+ collector << "DISTINCT " if o.distinct
455
502
  o.expressions.each_with_index do |expr, i|
456
- collector << ', ' if i > 0
503
+ collector << ", " if i.positive?
457
504
  collector = visit(expr, collector)
458
505
  end
459
- collector << ')'
506
+ collector << ")"
460
507
  if o.alias
461
- collector << ' AS '
508
+ collector << " AS "
462
509
  collector << quote_column_name(o.alias)
463
510
  end
464
511
  collector
465
512
  end
513
+
514
+ # Visit PREWHERE conditions
515
+ #
516
+ # @param conditions [Array] array of prewhere condition nodes
517
+ # @param collector [Arel::Collectors::SQLString] SQL collector
518
+ # @return [Arel::Collectors::SQLString] the collector with SQL
519
+ def visit_prewhere_conditions(conditions, collector)
520
+ conditions.each_with_index do |condition, i|
521
+ collector << " AND " if i.positive?
522
+ collector = visit(condition, collector)
523
+ end
524
+ collector
525
+ end
526
+
527
+ # Build SETTINGS clause for SQL generation
528
+ #
529
+ # @param settings [Hash] the settings hash
530
+ # @return [String] the SETTINGS clause
531
+ def build_settings_clause(settings)
532
+ pairs = settings.map do |key, value|
533
+ formatted = case value
534
+ when String then "'#{value}'"
535
+ when true then "1"
536
+ when false then "0"
537
+ else value.to_s
538
+ end
539
+ "#{key} = #{formatted}"
540
+ end
541
+
542
+ "SETTINGS #{pairs.join(", ")}"
543
+ end
544
+
545
+ # Visit orders and limits
546
+ #
547
+ # @param o [Arel::Nodes::SelectStatement] the select node
548
+ # @param collector [Arel::Collectors::SQLString] SQL collector
549
+ # @return [Arel::Collectors::SQLString] the collector with SQL
550
+ def visit_orders_and_limits(o, collector)
551
+ # GROUP BY
552
+ if o.cores[0].groups.any?
553
+ collector << " GROUP BY "
554
+ collector = inject_join(o.cores[0].groups, collector, ", ")
555
+ end
556
+
557
+ # HAVING
558
+ if o.cores[0].havings.any?
559
+ collector << " HAVING "
560
+ collector = inject_join(o.cores[0].havings, collector, " AND ")
561
+ end
562
+
563
+ # ORDER BY
564
+ if o.orders.any?
565
+ collector << " ORDER BY "
566
+ collector = inject_join(o.orders, collector, ", ")
567
+ end
568
+
569
+ # LIMIT
570
+ if o.limit
571
+ collector << " "
572
+ collector = visit(o.limit, collector)
573
+ end
574
+
575
+ # OFFSET
576
+ if o.offset
577
+ collector << " "
578
+ collector = visit(o.offset, collector)
579
+ end
580
+
581
+ collector
582
+ end
583
+
584
+ # Format a sample value for SQL generation
585
+ #
586
+ # Handles differentiation between Integer (absolute row count) and Float (fractional).
587
+ # Ruby's to_s preserves the distinction: Integer 1 becomes "1", Float 1.0 becomes "1.0"
588
+ # This matters because SAMPLE 1 means "at least 1 row" while SAMPLE 1.0 means "100% of data".
589
+ #
590
+ # @param value [Float, Integer] the sample value
591
+ # @return [String] the formatted sample value
592
+ def format_sample_value(value)
593
+ value.to_s
594
+ end
466
595
  end
467
596
  end
468
597
  end