questdb 0.1.0-x64-mingw-ucrt
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +22 -0
- data/LICENSE.txt +201 -0
- data/README.md +170 -0
- data/SECURITY.md +19 -0
- data/THIRD_PARTY_NOTICES.md +242 -0
- data/docs/arrow.md +89 -0
- data/docs/configuration.md +61 -0
- data/docs/design.md +46 -0
- data/docs/ingestion.md +96 -0
- data/docs/performance.md +36 -0
- data/docs/python-parity.md +34 -0
- data/docs/querying.md +54 -0
- data/docs/release-infrastructure.md +70 -0
- data/docs/releasing.md +47 -0
- data/docs/troubleshooting.md +41 -0
- data/examples/basic.rb +32 -0
- data/examples/callbacks.rb +46 -0
- data/examples/columnar.rb +41 -0
- data/examples/independent_buffer.rb +39 -0
- data/examples/query.rb +28 -0
- data/examples/standalone.rb +33 -0
- data/examples/tls_auth.rb +33 -0
- data/lib/questdb/arrow.rb +250 -0
- data/lib/questdb/client.rb +308 -0
- data/lib/questdb/column_vector.rb +91 -0
- data/lib/questdb/config.rb +161 -0
- data/lib/questdb/connection_event.rb +60 -0
- data/lib/questdb/direct_ingestion.rb +440 -0
- data/lib/questdb/event_inbox.rb +60 -0
- data/lib/questdb/ingress/buffer.rb +104 -0
- data/lib/questdb/ingress/error.rb +43 -0
- data/lib/questdb/ingress/float64_array.rb +60 -0
- data/lib/questdb/ingress/native.rb +40 -0
- data/lib/questdb/ingress/timestamp.rb +53 -0
- data/lib/questdb/ingress.rb +77 -0
- data/lib/questdb/native/3.1/questdb_ingress.so +0 -0
- data/lib/questdb/native/3.2/questdb_ingress.so +0 -0
- data/lib/questdb/native/3.3/questdb_ingress.so +0 -0
- data/lib/questdb/native/3.4/questdb_ingress.so +0 -0
- data/lib/questdb/pooled_sender.rb +215 -0
- data/lib/questdb/query.rb +429 -0
- data/lib/questdb/sender.rb +436 -0
- data/lib/questdb/sender_error.rb +49 -0
- data/lib/questdb/types.rb +198 -0
- data/lib/questdb/version.rb +27 -0
- data/lib/questdb.rb +22 -0
- data/questdb.gemspec +69 -0
- data/sig/questdb-arrow.rbs +46 -0
- data/sig/questdb.rbs +337 -0
- metadata +188 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# ___ _ ____ ____
|
|
2
|
+
# / _ \ _ _ ___ ___| |_| _ \| __ )
|
|
3
|
+
# | | | | | | |/ _ \/ __| __| | | | _ \
|
|
4
|
+
# | |_| | |_| | __/\__ \ |_| |_| | |_) |
|
|
5
|
+
# \__\_\\__,_|\___||___/\__|____/|____/
|
|
6
|
+
#
|
|
7
|
+
# Copyright (c) 2014-2019 Appsicle
|
|
8
|
+
# Copyright (c) 2019-2026 QuestDB
|
|
9
|
+
#
|
|
10
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
11
|
+
# you may not use this file except in compliance with the License.
|
|
12
|
+
# You may obtain a copy of the License at
|
|
13
|
+
#
|
|
14
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
15
|
+
#
|
|
16
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
17
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
18
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
19
|
+
# See the License for the specific language governing permissions and
|
|
20
|
+
# limitations under the License.
|
|
21
|
+
#
|
|
22
|
+
module QuestDB
|
|
23
|
+
module Ingress
|
|
24
|
+
class Float64Array
|
|
25
|
+
attr_reader :values, :shape
|
|
26
|
+
def initialize(values, shape:)
|
|
27
|
+
raise TypeError, "values and shape must be Arrays" unless values.is_a?(Array) && shape.is_a?(Array)
|
|
28
|
+
@shape = shape.map { |d| Integer(d) }
|
|
29
|
+
raise ArgumentError, "dimensions must be non-negative" unless @shape.all? { |dimension| dimension >= 0 }
|
|
30
|
+
expected = if @shape.empty?
|
|
31
|
+
0
|
|
32
|
+
else
|
|
33
|
+
@shape.reduce(1) do |n, d|
|
|
34
|
+
next 0 if d.zero? || n.zero?
|
|
35
|
+
raise RangeError, "shape is too large" if n > ((1 << 63) - 1) / d
|
|
36
|
+
n * d
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
raise ArgumentError, "shape requires #{expected} values, got #{values.length}" unless expected == values.length
|
|
40
|
+
@values = values.map { |v| Float(v) }.freeze
|
|
41
|
+
@shape.freeze
|
|
42
|
+
freeze
|
|
43
|
+
end
|
|
44
|
+
def self.from_native(values, shape) = new(values, shape: shape)
|
|
45
|
+
def self.from_nested(value)
|
|
46
|
+
raise TypeError, "nested value must be an Array" unless value.is_a?(Array)
|
|
47
|
+
walk = lambda do |node|
|
|
48
|
+
return [[], [Float(node)]] unless node.is_a?(Array)
|
|
49
|
+
return [[0], []] if node.empty?
|
|
50
|
+
children = node.map { |child| walk.call(child) }
|
|
51
|
+
child_shape = children.first.first
|
|
52
|
+
raise ArgumentError, "ragged nested array" unless children.all? { |shape, _| shape == child_shape }
|
|
53
|
+
[[node.length, *child_shape], children.flat_map(&:last)]
|
|
54
|
+
end
|
|
55
|
+
shape, values = walk.call(value)
|
|
56
|
+
new(values, shape: shape)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# ___ _ ____ ____
|
|
2
|
+
# / _ \ _ _ ___ ___| |_| _ \| __ )
|
|
3
|
+
# | | | | | | |/ _ \/ __| __| | | | _ \
|
|
4
|
+
# | |_| | |_| | __/\__ \ |_| |_| | |_) |
|
|
5
|
+
# \__\_\\__,_|\___||___/\__|____/|____/
|
|
6
|
+
#
|
|
7
|
+
# Copyright (c) 2014-2019 Appsicle
|
|
8
|
+
# Copyright (c) 2019-2026 QuestDB
|
|
9
|
+
#
|
|
10
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
11
|
+
# you may not use this file except in compliance with the License.
|
|
12
|
+
# You may obtain a copy of the License at
|
|
13
|
+
#
|
|
14
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
15
|
+
#
|
|
16
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
17
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
18
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
19
|
+
# See the License for the specific language governing permissions and
|
|
20
|
+
# limitations under the License.
|
|
21
|
+
#
|
|
22
|
+
# Source installs place the extension on RubyGems' extension load path.
|
|
23
|
+
# Precompiled gems carry one binary per supported Ruby minor because MRI's C
|
|
24
|
+
# ABI and libruby soname are not stable across minor releases.
|
|
25
|
+
begin
|
|
26
|
+
require "questdb_ingress"
|
|
27
|
+
rescue LoadError => source_error
|
|
28
|
+
require "rbconfig"
|
|
29
|
+
ruby_abi = RUBY_VERSION[/\A\d+\.\d+/]
|
|
30
|
+
native = File.expand_path("../native/#{ruby_abi}/questdb_ingress", __dir__)
|
|
31
|
+
if File.file?("#{native}.#{RbConfig::CONFIG.fetch('DLEXT')}")
|
|
32
|
+
require native
|
|
33
|
+
else
|
|
34
|
+
begin
|
|
35
|
+
require_relative "../../../ext/questdb_ingress/questdb_ingress"
|
|
36
|
+
rescue LoadError
|
|
37
|
+
raise source_error
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# ___ _ ____ ____
|
|
2
|
+
# / _ \ _ _ ___ ___| |_| _ \| __ )
|
|
3
|
+
# | | | | | | |/ _ \/ __| __| | | | _ \
|
|
4
|
+
# | |_| | |_| | __/\__ \ |_| |_| | |_) |
|
|
5
|
+
# \__\_\\__,_|\___||___/\__|____/|____/
|
|
6
|
+
#
|
|
7
|
+
# Copyright (c) 2014-2019 Appsicle
|
|
8
|
+
# Copyright (c) 2019-2026 QuestDB
|
|
9
|
+
#
|
|
10
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
11
|
+
# you may not use this file except in compliance with the License.
|
|
12
|
+
# You may obtain a copy of the License at
|
|
13
|
+
#
|
|
14
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
15
|
+
#
|
|
16
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
17
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
18
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
19
|
+
# See the License for the specific language governing permissions and
|
|
20
|
+
# limitations under the License.
|
|
21
|
+
#
|
|
22
|
+
module QuestDB
|
|
23
|
+
module Ingress
|
|
24
|
+
class Timestamp
|
|
25
|
+
MIN = -(1 << 63)
|
|
26
|
+
MAX = (1 << 63) - 1
|
|
27
|
+
attr_reader :value
|
|
28
|
+
def initialize(value)
|
|
29
|
+
raise TypeError, "timestamp must be an Integer" unless value.is_a?(Integer)
|
|
30
|
+
raise RangeError, "timestamp must fit signed 64-bit" unless value.between?(MIN, MAX)
|
|
31
|
+
@value = value
|
|
32
|
+
freeze
|
|
33
|
+
end
|
|
34
|
+
def ==(other) = other.class == self.class && other.value == value
|
|
35
|
+
end
|
|
36
|
+
class TimestampNanos < Timestamp
|
|
37
|
+
def self.now = new(Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond))
|
|
38
|
+
def self.from_time(time)
|
|
39
|
+
raise TypeError, "expected Time" unless time.is_a?(Time)
|
|
40
|
+
new(time.to_i * 1_000_000_000 + time.nsec)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
class TimestampMicros < Timestamp
|
|
44
|
+
def self.now = new(Process.clock_gettime(Process::CLOCK_REALTIME, :microsecond))
|
|
45
|
+
def self.from_time(time)
|
|
46
|
+
raise TypeError, "expected Time" unless time.is_a?(Time)
|
|
47
|
+
new(time.to_i * 1_000_000 + time.usec)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
SERVER_TIMESTAMP = Object.new.freeze
|
|
51
|
+
ServerTimestamp = SERVER_TIMESTAMP
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ___ _ ____ ____
|
|
4
|
+
# / _ \ _ _ ___ ___| |_| _ \| __ )
|
|
5
|
+
# | | | | | | |/ _ \/ __| __| | | | _ \
|
|
6
|
+
# | |_| | |_| | __/\__ \ |_| |_| | |_) |
|
|
7
|
+
# \__\_\\__,_|\___||___/\__|____/|____/
|
|
8
|
+
#
|
|
9
|
+
# Copyright (c) 2014-2019 Appsicle
|
|
10
|
+
# Copyright (c) 2019-2026 QuestDB
|
|
11
|
+
#
|
|
12
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
# you may not use this file except in compliance with the License.
|
|
14
|
+
# You may obtain a copy of the License at
|
|
15
|
+
#
|
|
16
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
#
|
|
18
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
# See the License for the specific language governing permissions and
|
|
22
|
+
# limitations under the License.
|
|
23
|
+
#
|
|
24
|
+
require "bigdecimal"
|
|
25
|
+
require_relative "version"
|
|
26
|
+
require_relative "ingress/error"
|
|
27
|
+
require_relative "ingress/timestamp"
|
|
28
|
+
require_relative "ingress/float64_array"
|
|
29
|
+
require_relative "types"
|
|
30
|
+
require_relative "column_vector"
|
|
31
|
+
require_relative "direct_ingestion"
|
|
32
|
+
require_relative "query"
|
|
33
|
+
require_relative "ingress/native"
|
|
34
|
+
require_relative "config"
|
|
35
|
+
require_relative "sender_error"
|
|
36
|
+
require_relative "connection_event"
|
|
37
|
+
require_relative "event_inbox"
|
|
38
|
+
require_relative "ingress/buffer"
|
|
39
|
+
require_relative "pooled_sender"
|
|
40
|
+
require_relative "sender"
|
|
41
|
+
require_relative "client"
|
|
42
|
+
|
|
43
|
+
module QuestDB
|
|
44
|
+
VERSION = Ingress::VERSION unless const_defined?(:VERSION, false)
|
|
45
|
+
C_CLIENT_VERSION = Ingress::C_CLIENT_VERSION unless const_defined?(:C_CLIENT_VERSION, false)
|
|
46
|
+
Error = Ingress::IngressError unless const_defined?(:Error, false)
|
|
47
|
+
QuestDBError = Ingress::IngressError unless const_defined?(:QuestDBError, false)
|
|
48
|
+
ServerRejectionError = Ingress::ServerRejectionError unless const_defined?(:ServerRejectionError, false)
|
|
49
|
+
QuestDBServerRejectionError = Ingress::ServerRejectionError unless const_defined?(:QuestDBServerRejectionError, false)
|
|
50
|
+
Timestamp = Ingress::Timestamp unless const_defined?(:Timestamp, false)
|
|
51
|
+
TimestampNanos = Ingress::TimestampNanos unless const_defined?(:TimestampNanos, false)
|
|
52
|
+
TimestampMicros = Ingress::TimestampMicros unless const_defined?(:TimestampMicros, false)
|
|
53
|
+
Float64Array = Ingress::Float64Array unless const_defined?(:Float64Array, false)
|
|
54
|
+
SERVER_TIMESTAMP = Ingress::SERVER_TIMESTAMP unless const_defined?(:SERVER_TIMESTAMP, false)
|
|
55
|
+
|
|
56
|
+
module_function
|
|
57
|
+
|
|
58
|
+
def connect(conf = nil, host: nil, port: nil, tls: nil, connection_listener: nil,
|
|
59
|
+
error_handler: nil,
|
|
60
|
+
connection_event_inbox_capacity: Client::DEFAULT_EVENT_INBOX_CAPACITY,
|
|
61
|
+
error_event_inbox_capacity: Client::DEFAULT_EVENT_INBOX_CAPACITY, **settings)
|
|
62
|
+
configuration, effective = Config.build(conf, host: host, port: port, tls: tls, settings: settings)
|
|
63
|
+
client = Client.new(
|
|
64
|
+
configuration, effective_settings: effective,
|
|
65
|
+
connection_listener: connection_listener, error_handler: error_handler,
|
|
66
|
+
connection_event_inbox_capacity: connection_event_inbox_capacity,
|
|
67
|
+
error_event_inbox_capacity: error_event_inbox_capacity
|
|
68
|
+
)
|
|
69
|
+
return client unless block_given?
|
|
70
|
+
|
|
71
|
+
begin
|
|
72
|
+
yield client
|
|
73
|
+
ensure
|
|
74
|
+
client.close
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ___ _ ____ ____
|
|
4
|
+
# / _ \ _ _ ___ ___| |_| _ \| __ )
|
|
5
|
+
# | | | | | | |/ _ \/ __| __| | | | _ \
|
|
6
|
+
# | |_| | |_| | __/\__ \ |_| |_| | |_) |
|
|
7
|
+
# \__\_\\__,_|\___||___/\__|____/|____/
|
|
8
|
+
#
|
|
9
|
+
# Copyright (c) 2014-2019 Appsicle
|
|
10
|
+
# Copyright (c) 2019-2026 QuestDB
|
|
11
|
+
#
|
|
12
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
13
|
+
# you may not use this file except in compliance with the License.
|
|
14
|
+
# You may obtain a copy of the License at
|
|
15
|
+
#
|
|
16
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
17
|
+
#
|
|
18
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
# See the License for the specific language governing permissions and
|
|
22
|
+
# limitations under the License.
|
|
23
|
+
#
|
|
24
|
+
|
|
25
|
+
module QuestDB
|
|
26
|
+
class PooledSender
|
|
27
|
+
ACK_LEVELS = { ok: 0, durable: 1 }.freeze
|
|
28
|
+
EMPTY_HASH = {}.freeze
|
|
29
|
+
private_constant :EMPTY_HASH
|
|
30
|
+
|
|
31
|
+
attr_reader :auto_flush_rows, :auto_flush_bytes, :auto_flush_interval
|
|
32
|
+
|
|
33
|
+
def initialize(client, native, auto_flush:)
|
|
34
|
+
@client = client
|
|
35
|
+
@native = native
|
|
36
|
+
@owner_thread = Thread.current
|
|
37
|
+
@closed = false
|
|
38
|
+
@buffer = Ingress::Buffer.send(:from_native, @native.new_buffer)
|
|
39
|
+
@auto_flush_rows = auto_flush.fetch(:rows)
|
|
40
|
+
@auto_flush_bytes = auto_flush.fetch(:bytes)
|
|
41
|
+
@auto_flush_interval = auto_flush.fetch(:interval)
|
|
42
|
+
@dynamic_auto_flush_bytes = auto_flush.fetch(:dynamic_bytes)
|
|
43
|
+
@batch_started = nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def row(table, symbols: EMPTY_HASH, columns: EMPTY_HASH, at:)
|
|
47
|
+
ensure_open!
|
|
48
|
+
appended = @buffer.__send__(:append, table, symbols, columns, at)
|
|
49
|
+
return self unless appended
|
|
50
|
+
@batch_started ||= monotonic
|
|
51
|
+
flush if should_auto_flush?
|
|
52
|
+
self
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def row_count = (@buffer&.row_count || 0)
|
|
56
|
+
alias length row_count
|
|
57
|
+
def empty? = row_count.zero?
|
|
58
|
+
def closed? = @closed || @native.closed?
|
|
59
|
+
|
|
60
|
+
def flush(wait: false, ack: :ok)
|
|
61
|
+
ensure_open!
|
|
62
|
+
level = normalize_ack(ack)
|
|
63
|
+
raise ArgumentError, "ack: :durable requires wait: true" if level == ACK_LEVELS[:durable] && !wait
|
|
64
|
+
return self if empty? && !wait
|
|
65
|
+
@native.flush(@buffer.send(:native), !!wait, level)
|
|
66
|
+
@batch_started = nil if @buffer.empty?
|
|
67
|
+
self
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def wait(timeout_millis: 0, ack: :ok)
|
|
71
|
+
ensure_open!
|
|
72
|
+
timeout = non_negative_integer(timeout_millis, "timeout_millis")
|
|
73
|
+
@native.wait(normalize_ack(ack), timeout)
|
|
74
|
+
self
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def flush_and_get_fsn
|
|
78
|
+
ensure_open!
|
|
79
|
+
fsn = @native.flush_fsn(@buffer.send(:native), false)
|
|
80
|
+
@batch_started = nil if @buffer.empty?
|
|
81
|
+
fsn
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def flush_and_keep_and_get_fsn
|
|
85
|
+
ensure_open!
|
|
86
|
+
@native.flush_fsn(@buffer.send(:native), true)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def published_fsn
|
|
90
|
+
ensure_open!
|
|
91
|
+
@native.published_fsn
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def acked_fsn
|
|
95
|
+
ensure_open!
|
|
96
|
+
@native.acked_fsn
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def await_acked_fsn(fsn, timeout_millis: 0)
|
|
100
|
+
ensure_open!
|
|
101
|
+
fsn = non_negative_integer(fsn, "fsn")
|
|
102
|
+
timeout = non_negative_integer(timeout_millis, "timeout_millis")
|
|
103
|
+
current = @native.acked_fsn
|
|
104
|
+
return true if current && current >= fsn
|
|
105
|
+
begin
|
|
106
|
+
@native.wait(ACK_LEVELS[:ok], timeout)
|
|
107
|
+
rescue Ingress::IngressError => error
|
|
108
|
+
raise unless error.code == :failover_retry
|
|
109
|
+
end
|
|
110
|
+
current = @native.acked_fsn
|
|
111
|
+
!!(current && current >= fsn)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Convenience parity with Client#ingest_columns. This uses a separate
|
|
115
|
+
# direct connection and therefore has no ordering relationship with this
|
|
116
|
+
# lease's buffered row stream.
|
|
117
|
+
def ingest_columns(table, columns:, symbols: EMPTY_HASH, at:, max_rows_per_batch: 16_384, ack: :ok)
|
|
118
|
+
ensure_open!
|
|
119
|
+
@client.ingest_columns(table, columns: columns, symbols: symbols, at: at,
|
|
120
|
+
max_rows_per_batch: max_rows_per_batch, ack: ack)
|
|
121
|
+
self
|
|
122
|
+
end
|
|
123
|
+
alias write_columns ingest_columns
|
|
124
|
+
|
|
125
|
+
def poll_error
|
|
126
|
+
ensure_open!
|
|
127
|
+
values = @native.poll_error
|
|
128
|
+
values && SenderError.from_native(values)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def error_events_dropped
|
|
132
|
+
ensure_open!
|
|
133
|
+
@native.error_events_dropped
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def close(flush: true, wait: false)
|
|
137
|
+
return self if @closed
|
|
138
|
+
ensure_thread!
|
|
139
|
+
begin
|
|
140
|
+
if flush && !empty?
|
|
141
|
+
self.flush(wait: wait)
|
|
142
|
+
elsif wait
|
|
143
|
+
self.wait
|
|
144
|
+
end
|
|
145
|
+
ensure
|
|
146
|
+
@buffer&.clear
|
|
147
|
+
@native.close(false)
|
|
148
|
+
@closed = true
|
|
149
|
+
@client.send(:sender_released, self)
|
|
150
|
+
end
|
|
151
|
+
self
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def open
|
|
155
|
+
raise ArgumentError, "a block is required" unless block_given?
|
|
156
|
+
begin
|
|
157
|
+
result = yield self
|
|
158
|
+
close
|
|
159
|
+
result
|
|
160
|
+
rescue Exception
|
|
161
|
+
close(flush: false)
|
|
162
|
+
raise
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def initialize_copy(*) = raise(TypeError, "QuestDB pooled senders cannot be copied")
|
|
167
|
+
|
|
168
|
+
private
|
|
169
|
+
|
|
170
|
+
def ensure_thread!
|
|
171
|
+
return if Thread.current.equal?(@owner_thread)
|
|
172
|
+
raise Ingress::IngressError.new("pooled sender must be used on the thread that borrowed it", code: :invalid_api_call)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def ensure_open!
|
|
176
|
+
ensure_thread!
|
|
177
|
+
raise Ingress::IngressError.new("pooled sender is closed", code: :invalid_api_call) if closed?
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def normalize_ack(value)
|
|
181
|
+
value = value.to_sym if value.respond_to?(:to_sym)
|
|
182
|
+
ACK_LEVELS.fetch(value) do
|
|
183
|
+
raise ArgumentError, "ack must be :ok or :durable"
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def non_negative_integer(value, name)
|
|
188
|
+
value = Integer(value)
|
|
189
|
+
raise ArgumentError, "#{name} must be non-negative" if value.negative?
|
|
190
|
+
value
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def should_auto_flush?
|
|
194
|
+
return false unless @batch_started
|
|
195
|
+
return true if @auto_flush_rows && row_count >= @auto_flush_rows
|
|
196
|
+
if @dynamic_auto_flush_bytes || @auto_flush_bytes
|
|
197
|
+
encoded_cap = @native.effective_frame_cap
|
|
198
|
+
server_cap_known = encoded_cap.positive?
|
|
199
|
+
hard_cap = encoded_cap.abs
|
|
200
|
+
soft_cap = (hard_cap / 10) * 9 + ((hard_cap % 10) * 9) / 10
|
|
201
|
+
byte_limit = if @dynamic_auto_flush_bytes
|
|
202
|
+
server_cap_known ? soft_cap : [soft_cap, 8 * 1024 * 1024].min
|
|
203
|
+
else
|
|
204
|
+
[@auto_flush_bytes, soft_cap].min
|
|
205
|
+
end
|
|
206
|
+
return true if @buffer.bytesize >= byte_limit
|
|
207
|
+
end
|
|
208
|
+
@auto_flush_interval && monotonic - @batch_started >= @auto_flush_interval
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def monotonic
|
|
212
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|