influxdb-client 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -73,7 +73,7 @@ module InfluxDB2
73
73
  uri = URI.parse(File.join(@options[:url], '/api/v2/query'))
74
74
  uri.query = URI.encode_www_form(org: org_param)
75
75
 
76
- _post(payload.to_body.to_json, uri)
76
+ _post_json(payload.to_body.to_json, uri)
77
77
  end
78
78
 
79
79
  def _generate_payload(query, dialect)
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module InfluxDB2
22
- VERSION = '1.1.0'.freeze
22
+ VERSION = '1.2.0'.freeze
23
23
  end
@@ -33,24 +33,36 @@ module InfluxDB2
33
33
 
34
34
  @thread_flush = Thread.new do
35
35
  until api_client.closed
36
- sleep @write_options.flush_interval / 1_000
37
- check_background_queue
36
+ sleep @write_options.flush_interval.to_f / 1_000
37
+ _check_background_queue
38
38
  end
39
39
  end
40
40
 
41
41
  @thread_size = Thread.new do
42
42
  until api_client.closed
43
- check_background_queue(size: true) if @queue.length >= @write_options.batch_size
43
+ _check_background_queue(size: true) if @queue.length >= @write_options.batch_size
44
44
  sleep 0.01
45
45
  end
46
46
  end
47
47
  end
48
48
 
49
49
  def push(payload)
50
- @queue.push(payload)
50
+ if payload.respond_to? :each
51
+ payload.each do |item|
52
+ push(item)
53
+ end
54
+ else
55
+ @queue.push(payload)
56
+ end
51
57
  end
52
58
 
53
- def check_background_queue(size: false, flush_all: false)
59
+ def flush_all
60
+ _check_background_queue until @queue.empty?
61
+ end
62
+
63
+ private
64
+
65
+ def _check_background_queue(size: false)
54
66
  @queue_event.pop
55
67
  data = {}
56
68
  points = 0
@@ -60,7 +72,7 @@ module InfluxDB2
60
72
  return
61
73
  end
62
74
 
63
- while (flush_all || points < @write_options.batch_size) && !@queue.empty?
75
+ while (points < @write_options.batch_size) && !@queue.empty?
64
76
  begin
65
77
  item = @queue.pop(true)
66
78
  key = item.key
@@ -68,22 +80,36 @@ module InfluxDB2
68
80
  data[key] << item.data
69
81
  points += 1
70
82
  rescue ThreadError
83
+ @queue_event.push(true)
71
84
  return
72
85
  end
73
86
  end
74
87
 
75
- write(data) unless data.values.flatten.empty?
76
- @queue_event.push(true)
88
+ begin
89
+ _write(data) unless data.values.flatten.empty?
90
+ ensure
91
+ @queue_event.push(true)
92
+ end
77
93
  end
78
94
 
79
- def flush_all
80
- check_background_queue(flush_all: true) unless @queue.empty?
95
+ def _write(data)
96
+ data.each do |key, points|
97
+ _write_raw(key, points)
98
+ end
81
99
  end
82
100
 
83
- def write(data)
84
- data.each do |key, points|
85
- @api_client.write_raw(points.join("\n"), precision: key.precision, bucket: key.bucket, org: key.org)
101
+ def _write_raw(key, points)
102
+ if @write_options.jitter_interval.positive?
103
+ jitter_delay = (@write_options.jitter_interval.to_f / 1_000) * rand
104
+ sleep jitter_delay
86
105
  end
106
+ @api_client.write_raw(points.join("\n"), precision: key.precision, bucket: key.bucket, org: key.org)
107
+ rescue InfluxError => e
108
+ raise e if e.code.nil? || !(%w[429 503].include? e.code)
109
+
110
+ timeout = e.retry_after.empty? ? @write_options.retry_interval.to_f / 1_000 : e.retry_after.to_f
111
+ sleep timeout
112
+ _write_raw(key, points)
87
113
  end
88
114
  end
89
115
  end
@@ -27,17 +27,36 @@ module InfluxDB2
27
27
 
28
28
  # Creates write api configuration.
29
29
  #
30
- # @param write_type: methods of write (batching, asynchronous, synchronous)
31
- # @param batch_size: the number of data point to collect in batch
32
- # @param flush_interval: flush data at least in this interval
33
30
  class WriteOptions
34
- def initialize(write_type: WriteType::SYNCHRONOUS, batch_size: 1_000, flush_interval: 1_000)
31
+ # @param [WriteType] write_type: methods of write (batching, synchronous)
32
+ # @param [Integer] batch_size: the number of data point to collect in batch
33
+ # @param [Integer] flush_interval: flush data at least in this interval
34
+ # @param [Integer] retry_interval: number of milliseconds to retry unsuccessful write.
35
+ # The retry interval is used when the InfluxDB server does not specify "Retry-After" header.
36
+ # @param [Integer] jitter_interval: the number of milliseconds to increase the batch flush interval
37
+ # by a random amount
38
+ def initialize(write_type: WriteType::SYNCHRONOUS, batch_size: 1_000, flush_interval: 1_000, retry_interval: 1_000,
39
+ jitter_interval: 0)
40
+ _check_not_negative('batch_size', batch_size)
41
+ _check_not_negative('flush_interval', flush_interval)
42
+ _check_not_negative('retry_interval', retry_interval)
43
+ _check_positive('jitter_interval', jitter_interval)
35
44
  @write_type = write_type
36
45
  @batch_size = batch_size
37
46
  @flush_interval = flush_interval
47
+ @retry_interval = retry_interval
48
+ @jitter_interval = jitter_interval
38
49
  end
39
50
 
40
- attr_reader :write_type, :batch_size, :flush_interval
51
+ attr_reader :write_type, :batch_size, :flush_interval, :retry_interval, :jitter_interval
52
+
53
+ def _check_not_negative(key, value)
54
+ raise ArgumentError, "The '#{key}' should be positive or zero, but is: #{value}" if value <= 0
55
+ end
56
+
57
+ def _check_positive(key, value)
58
+ raise ArgumentError, "The '#{key}' should be positive number, but is: #{value}" if value < 0
59
+ end
41
60
  end
42
61
 
43
62
  SYNCHRONOUS = InfluxDB2::WriteOptions.new(write_type: WriteType::SYNCHRONOUS)
@@ -144,7 +163,7 @@ module InfluxDB2
144
163
  uri = URI.parse(File.join(@options[:url], '/api/v2/write'))
145
164
  uri.query = URI.encode_www_form(bucket: bucket_param, org: org_param, precision: precision_param.to_s)
146
165
 
147
- _post(payload, uri)
166
+ _post_text(payload, uri)
148
167
  end
149
168
 
150
169
  # Item for batching queue
@@ -210,9 +229,14 @@ module InfluxDB2
210
229
  elsif data.is_a?(Hash)
211
230
  _generate_payload(Point.from_hash(data), bucket: bucket, org: org, precision: precision)
212
231
  elsif data.respond_to? :map
213
- data.map do |item|
232
+ payloads = data.map do |item|
214
233
  _generate_payload(item, bucket: bucket, org: org, precision: precision)
215
- end.reject(&:nil?).join("\n".freeze)
234
+ end.reject(&:nil?)
235
+ if @write_options.write_type == WriteType::BATCHING
236
+ payloads
237
+ else
238
+ payloads.join("\n".freeze)
239
+ end
216
240
  end
217
241
  end
218
242
  end
@@ -0,0 +1,100 @@
1
+ # The MIT License
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'test_helper'
22
+
23
+ class DeleteApiIntegrationTest < MiniTest::Test
24
+ def setup
25
+ WebMock.allow_net_connect!
26
+
27
+ @client = InfluxDB2::Client.new('http://localhost:9999', 'my-token',
28
+ bucket: 'my-bucket',
29
+ org: 'my-org',
30
+ precision: InfluxDB2::WritePrecision::NANOSECOND,
31
+ use_ssl: false)
32
+
33
+ now = Time.now.utc
34
+ @measurement = 'h2o_delete_' + now.to_i.to_s + +now.nsec.to_s
35
+
36
+ data = [InfluxDB2::Point.new(name: @measurement)
37
+ .add_tag('location', 'europe')
38
+ .add_field('level', 2)
39
+ .time(Time.utc(2015, 10, 15, 8, 20, 15), InfluxDB2::WritePrecision::MILLISECOND),
40
+ InfluxDB2::Point.new(name: @measurement)
41
+ .add_tag('location', 'us')
42
+ .add_field('level', 2)
43
+ .time(Time.utc(2016, 10, 15, 8, 20, 15), InfluxDB2::WritePrecision::MILLISECOND),
44
+ InfluxDB2::Point.new(name: @measurement)
45
+ .add_tag('location', 'india')
46
+ .add_field('level', 2)
47
+ .time(Time.utc(2017, 10, 15, 8, 20, 15), InfluxDB2::WritePrecision::MILLISECOND),
48
+ InfluxDB2::Point.new(name: @measurement)
49
+ .add_tag('location', 'europe')
50
+ .add_field('level', 2)
51
+ .time(Time.utc(2018, 10, 15, 8, 20, 15), InfluxDB2::WritePrecision::MILLISECOND)]
52
+
53
+ @client.create_write_api.write(data: data, precision: InfluxDB2::WritePrecision::MILLISECOND)
54
+
55
+ assert_equal 4, _query_count
56
+ end
57
+
58
+ def test_delete
59
+ @client.create_delete_api.delete(Time.utc(2015, 10, 16, 8, 20, 15), Time.utc(2020, 10, 16, 8, 20, 15),
60
+ predicate: 'location="europe"')
61
+
62
+ assert_equal 3, _query_count
63
+ end
64
+
65
+ def test_delete_without_predicate
66
+ @client.create_delete_api.delete(Time.utc(2016, 10, 15, 7, 20, 15), Time.utc(2018, 10, 14, 8, 20, 15))
67
+
68
+ assert_equal 2, _query_count
69
+ end
70
+
71
+ def test_delete_all
72
+ @client.create_delete_api.delete(Time.utc(2010, 10, 15, 7, 20, 15), Time.utc(2020, 10, 14, 8, 20, 15))
73
+
74
+ assert_equal 0, _query_count
75
+ end
76
+
77
+ def test_delete_without_interval
78
+ error = assert_raises InfluxDB2::InfluxError do
79
+ @client.create_delete_api.delete(nil, nil)
80
+ end
81
+
82
+ assert error.message.include?('invalid request'),
83
+ "Error message: '#{error.message}' doesn't contains 'invalid request'"
84
+ end
85
+
86
+ private
87
+
88
+ def _query_count
89
+ query = 'from(bucket: "my-bucket") |> range(start: 0) |> ' \
90
+ "filter(fn: (r) => r._measurement == \"#{@measurement}\") " \
91
+ '|> drop(columns: ["location"]) |> count()'
92
+
93
+ table = @client.create_query_api.query(query: InfluxDB2::Query.new(query: query,
94
+ dialect: InfluxDB2::QueryApi::DEFAULT_DIALECT,
95
+ type: nil))[0]
96
+ return 0 if table.nil?
97
+
98
+ table.records[0].value
99
+ end
100
+ end
@@ -0,0 +1,121 @@
1
+ # The MIT License
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'test_helper'
22
+
23
+ class DeleteApiTest < MiniTest::Test
24
+ def setup
25
+ WebMock.disable_net_connect!
26
+ end
27
+
28
+ def test_delete
29
+ stub_request(:any, 'http://localhost:9999/api/v2/delete?bucket=my-bucket&org=my-org')
30
+ .to_return(status: 204)
31
+ client = InfluxDB2::Client.new('http://localhost:9999', 'my-token',
32
+ bucket: 'my-bucket',
33
+ org: 'my-org',
34
+ precision: InfluxDB2::WritePrecision::NANOSECOND,
35
+ use_ssl: false)
36
+
37
+ client.create_delete_api.delete(Time.utc(2019, 10, 15, 8, 20, 15), Time.utc(2019, 11, 15, 8, 20, 15),
38
+ predicate: 'key1="value1" AND key2="value"', bucket: 'my-bucket', org: 'my-org')
39
+
40
+ body = '{"start":"2019-10-15T08:20:15+00:00","stop":"2019-11-15T08:20:15+00:00","predicate":"key1=\"value1\" ' \
41
+ 'AND key2=\"value\""}'
42
+
43
+ assert_requested(:post, 'http://localhost:9999/api/v2/delete?bucket=my-bucket&org=my-org', times: 1, body: body)
44
+ end
45
+
46
+ def test_delete_time_as_date_time
47
+ stub_request(:any, 'http://localhost:9999/api/v2/delete?bucket=my-bucket&org=my-org')
48
+ .to_return(status: 204)
49
+ client = InfluxDB2::Client.new('http://localhost:9999', 'my-token',
50
+ bucket: 'my-bucket',
51
+ org: 'my-org',
52
+ precision: InfluxDB2::WritePrecision::NANOSECOND,
53
+ use_ssl: false)
54
+
55
+ client.create_delete_api.delete(DateTime.rfc3339('2019-02-03T04:05:06+07:00'),
56
+ DateTime.rfc3339('2019-03-03T04:05:06+07:00'),
57
+ predicate: 'key1="value1" AND key2="value"', bucket: 'my-bucket', org: 'my-org')
58
+
59
+ body = '{"start":"2019-02-03T04:05:06+07:00","stop":"2019-03-03T04:05:06+07:00","predicate":"key1=\"value1\" ' \
60
+ 'AND key2=\"value\""}'
61
+
62
+ assert_requested(:post, 'http://localhost:9999/api/v2/delete?bucket=my-bucket&org=my-org', times: 1, body: body)
63
+ end
64
+
65
+ def test_delete_time_as_string
66
+ stub_request(:any, 'http://localhost:9999/api/v2/delete?bucket=my-bucket&org=my-org')
67
+ .to_return(status: 204)
68
+ client = InfluxDB2::Client.new('http://localhost:9999', 'my-token',
69
+ bucket: 'my-bucket',
70
+ org: 'my-org',
71
+ precision: InfluxDB2::WritePrecision::NANOSECOND,
72
+ use_ssl: false)
73
+
74
+ client.create_delete_api.delete('2019-02-03T04:05:06+07:00', '2019-04-03T04:05:06+07:00',
75
+ predicate: 'key1="value1" AND key2="value"', bucket: 'my-bucket', org: 'my-org')
76
+
77
+ body = '{"start":"2019-02-03T04:05:06+07:00","stop":"2019-04-03T04:05:06+07:00","predicate":"key1=\"value1\" ' \
78
+ 'AND key2=\"value\""}'
79
+
80
+ assert_requested(:post, 'http://localhost:9999/api/v2/delete?bucket=my-bucket&org=my-org', times: 1, body: body)
81
+ end
82
+
83
+ def test_without_predicate
84
+ stub_request(:any, 'http://localhost:9999/api/v2/delete?bucket=my-bucket&org=my-org')
85
+ .to_return(status: 204)
86
+ client = InfluxDB2::Client.new('http://localhost:9999', 'my-token',
87
+ bucket: 'my-bucket',
88
+ org: 'my-org',
89
+ precision: InfluxDB2::WritePrecision::NANOSECOND,
90
+ use_ssl: false)
91
+
92
+ client.create_delete_api.delete('2019-02-03T04:05:06+07:00', '2019-04-03T04:05:06+07:00',
93
+ bucket: 'my-bucket', org: 'my-org')
94
+
95
+ body = '{"start":"2019-02-03T04:05:06+07:00","stop":"2019-04-03T04:05:06+07:00"}'
96
+
97
+ assert_requested(:post, 'http://localhost:9999/api/v2/delete?bucket=my-bucket&org=my-org', times: 1, body: body)
98
+ end
99
+
100
+ def test_user_agent_header
101
+ stub_request(:any, 'http://localhost:9999/api/v2/delete?bucket=my-bucket&org=my-org')
102
+ .to_return(status: 204)
103
+ client = InfluxDB2::Client.new('http://localhost:9999', 'my-token',
104
+ bucket: 'my-bucket',
105
+ org: 'my-org',
106
+ precision: InfluxDB2::WritePrecision::NANOSECOND,
107
+ use_ssl: false)
108
+
109
+ client.create_delete_api.delete('2019-02-03T04:05:06+07:00', '2019-04-03T04:05:06+07:00',
110
+ bucket: 'my-bucket', org: 'my-org')
111
+
112
+ body = '{"start":"2019-02-03T04:05:06+07:00","stop":"2019-04-03T04:05:06+07:00"}'
113
+ headers = {
114
+ 'Authorization' => 'Token my-token',
115
+ 'User-Agent' => "influxdb-client-ruby/#{InfluxDB2::VERSION}",
116
+ 'Content-Type' => 'application/json'
117
+ }
118
+ assert_requested(:post, 'http://localhost:9999/api/v2/delete?bucket=my-bucket&org=my-org',
119
+ times: 1, body: body, headers: headers)
120
+ end
121
+ end
@@ -323,4 +323,79 @@ class FluxCsvParserErrorTest < MiniTest::Test
323
323
 
324
324
  assert_equal 'Unable to parse CSV response. FluxTable definition was not found.', error.message
325
325
  end
326
+
327
+ def test_multiple_queries
328
+ data = "#datatype,string,long,string,string,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string\n" \
329
+ "#group,false,false,true,true,true,true,false,false,true\n" \
330
+ "#default,t1,,,,,,,,\n" \
331
+ ",result,table,_field,_measurement,_start,_stop,_time,_value,tag\n" \
332
+ ",,0,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:20:00Z,2,test1\n" \
333
+ ",,0,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:21:40Z,2,test1\n" \
334
+ ",,0,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:23:20Z,2,test1\n" \
335
+ ",,0,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:25:00Z,2,test1\n" \
336
+ ",,0,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:26:40Z,2,test1\n" \
337
+ ",,0,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:28:20Z,2,test1\n" \
338
+ ",,0,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:30:00Z,2,test1\n" \
339
+ ",,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:20:00Z,2,test2\n" \
340
+ ",,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:21:40Z,2,test2\n" \
341
+ ",,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:23:20Z,2,test2\n" \
342
+ ",,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:25:00Z,2,test2\n" \
343
+ ",,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:26:40Z,2,test2\n" \
344
+ ",,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:28:20Z,2,test2\n" \
345
+ ",,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:30:00Z,2,test2\n" \
346
+ "\n" \
347
+ "#datatype,string,long,string,string,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string\n" \
348
+ "#group,false,false,true,true,true,true,false,false,true\n" \
349
+ "#default,t2,,,,,,,,\n" \
350
+ ",result,table,_field,_measurement,_start,_stop,_time,_value,tag\n" \
351
+ ",,0,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:20:00Z,2,test1\n" \
352
+ ",,0,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:21:40Z,2,test1\n" \
353
+ ",,0,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:23:20Z,2,test1\n" \
354
+ ",,0,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:25:00Z,2,test1\n" \
355
+ ",,0,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:26:40Z,2,test1\n" \
356
+ ",,0,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:28:20Z,2,test1\n" \
357
+ ",,0,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:30:00Z,2,test1\n" \
358
+ ",,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:20:00Z,2,test2\n" \
359
+ ",,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:21:40Z,2,test2\n" \
360
+ ",,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:23:20Z,2,test2\n" \
361
+ ",,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:25:00Z,2,test2\n" \
362
+ ",,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:26:40Z,2,test2\n" \
363
+ ",,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:28:20Z,2,test2\n" \
364
+ ',,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:30:00Z,2,test2'
365
+
366
+ tables = InfluxDB2::FluxCsvParser.new(data).parse.tables
367
+
368
+ assert_equal 4, tables.size
369
+ assert_equal 7, tables[0].records.size
370
+ assert_equal 7, tables[1].records.size
371
+ assert_equal 7, tables[2].records.size
372
+ assert_equal 7, tables[3].records.size
373
+ end
374
+
375
+ def test_table_not_start_at_zero
376
+ data = "#datatype,string,long,string,string,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string\n" \
377
+ "#group,false,false,true,true,true,true,false,false,true\n" \
378
+ "#default,t1,,,,,,,,\n" \
379
+ ",result,table,_field,_measurement,_start,_stop,_time,_value,tag\n" \
380
+ ",,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:20:00Z,2,test1\n" \
381
+ ",,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:21:40Z,2,test1\n" \
382
+ ",,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:23:20Z,2,test1\n" \
383
+ ",,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:25:00Z,2,test1\n" \
384
+ ",,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:26:40Z,2,test1\n" \
385
+ ",,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:28:20Z,2,test1\n" \
386
+ ",,1,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:30:00Z,2,test1\n" \
387
+ ",,2,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:20:00Z,2,test2\n" \
388
+ ",,2,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:21:40Z,2,test2\n" \
389
+ ",,2,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:23:20Z,2,test2\n" \
390
+ ",,2,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:25:00Z,2,test2\n" \
391
+ ",,2,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:26:40Z,2,test2\n" \
392
+ ",,2,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:28:20Z,2,test2\n" \
393
+ ',,2,value,pct,2010-02-27T04:48:32.752600083Z,2020-02-27T16:48:32.752600083Z,2020-02-27T16:30:00Z,2,test2\n'
394
+
395
+ tables = InfluxDB2::FluxCsvParser.new(data).parse.tables
396
+
397
+ assert_equal 2, tables.size
398
+ assert_equal 7, tables[0].records.size
399
+ assert_equal 7, tables[1].records.size
400
+ end
326
401
  end