questdb 0.1.0-aarch64-linux-gnu
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,46 @@
|
|
|
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
|
+
require "questdb"
|
|
26
|
+
|
|
27
|
+
client = nil
|
|
28
|
+
client = QuestDB.connect(
|
|
29
|
+
ENV.fetch("QDB_CLIENT_CONF", "ws::addr=localhost:9000;"),
|
|
30
|
+
connection_listener: lambda { |event|
|
|
31
|
+
warn "connection=#{event.kind} endpoint=#{event.host}:#{event.port}"
|
|
32
|
+
},
|
|
33
|
+
error_handler: lambda { |error|
|
|
34
|
+
warn "rejection=#{error.category} policy=#{error.applied_policy} fsn=#{error.from_fsn}-#{error.to_fsn}: #{error.message}"
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
begin
|
|
39
|
+
client.sender do |sender|
|
|
40
|
+
sender.row("ruby_callback_example", columns: { value: 1 }, at: QuestDB::SERVER_TIMESTAMP)
|
|
41
|
+
sender.flush(wait: true)
|
|
42
|
+
end
|
|
43
|
+
ensure
|
|
44
|
+
# A handler closure that captures client keeps it alive; always close explicitly.
|
|
45
|
+
client.close
|
|
46
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
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 "questdb"
|
|
25
|
+
|
|
26
|
+
conf = ENV.fetch("QDB_CLIENT_CONF", "ws::addr=localhost:9000;")
|
|
27
|
+
QuestDB.connect(conf) do |client|
|
|
28
|
+
now = QuestDB::TimestampNanos.now.value
|
|
29
|
+
client.ingest_columns(
|
|
30
|
+
"ruby_columnar_example",
|
|
31
|
+
columns: {
|
|
32
|
+
price: [2615.54, 2616.10, nil],
|
|
33
|
+
quantity: QuestDB::ColumnVector.int([3, 5, 8]),
|
|
34
|
+
venue_code: QuestDB::ColumnVector.varchar(["LDN", "NYC", "SGP"])
|
|
35
|
+
},
|
|
36
|
+
symbols: { symbol: ["ETH-USD", "BTC-USD", "ETH-USD"] },
|
|
37
|
+
at: 3.times.map { |index| QuestDB::TimestampNanos.new(now + index) },
|
|
38
|
+
max_rows_per_batch: 16_384,
|
|
39
|
+
ack: :ok
|
|
40
|
+
)
|
|
41
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
require "questdb"
|
|
23
|
+
|
|
24
|
+
QuestDB.connect("ws::addr=localhost:9000;sender_pool_max=4;") do |client|
|
|
25
|
+
workers = 2.times.map do |index|
|
|
26
|
+
Thread.new do
|
|
27
|
+
client.sender do |sender|
|
|
28
|
+
100.times do |row|
|
|
29
|
+
sender.row("parallel_metrics",
|
|
30
|
+
symbols: { worker: index.to_s },
|
|
31
|
+
columns: { value: row },
|
|
32
|
+
at: QuestDB::TimestampNanos.now)
|
|
33
|
+
end
|
|
34
|
+
sender.flush(wait: true)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
workers.each(&:join)
|
|
39
|
+
end
|
data/examples/query.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
require "questdb"
|
|
23
|
+
|
|
24
|
+
QuestDB.connect(ENV.fetch("QDB_CLIENT_CONF", "ws::addr=localhost:9000;")) do |client|
|
|
25
|
+
client.query("SELECT table_name FROM tables() LIMIT $1", binds: [10]) do |result|
|
|
26
|
+
result.each { |row| puts row["table_name"] }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
require "questdb"
|
|
23
|
+
|
|
24
|
+
conf = ENV.fetch("QDB_CLIENT_CONF", "ws::addr=localhost:9000;auto_flush=off;")
|
|
25
|
+
QuestDB::Sender.from_conf(conf).open do |sender|
|
|
26
|
+
sender.row(
|
|
27
|
+
"ruby_standalone_example",
|
|
28
|
+
symbols: { source: "ruby" },
|
|
29
|
+
columns: { value: 42 },
|
|
30
|
+
at: QuestDB::TimestampNanos.now
|
|
31
|
+
)
|
|
32
|
+
sender.flush(wait: true)
|
|
33
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
require "questdb"
|
|
23
|
+
|
|
24
|
+
conf = "wss::addr=#{ENV.fetch('QDB_HOST')}:#{ENV.fetch('QDB_PORT', '9000')};" \
|
|
25
|
+
"token=#{ENV.fetch('QDB_TOKEN')};" \
|
|
26
|
+
"tls_roots=#{ENV.fetch('QDB_TLS_ROOTS')};"
|
|
27
|
+
|
|
28
|
+
QuestDB.connect(conf) do |client|
|
|
29
|
+
client.sender do |sender|
|
|
30
|
+
sender.row("secure_metrics", columns: { value: 1 }, at: QuestDB::TimestampNanos.now)
|
|
31
|
+
sender.flush(wait: true)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,250 @@
|
|
|
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
|
+
require "questdb"
|
|
26
|
+
|
|
27
|
+
module QuestDB
|
|
28
|
+
# Optional Apache Arrow C Stream interoperability. Requiring this file adds
|
|
29
|
+
# adapters without loading red-arrow, nanoarrow, or polars-df.
|
|
30
|
+
module Arrow
|
|
31
|
+
ACK_LEVELS = { ok: 0, durable: 1 }.freeze
|
|
32
|
+
OVERRIDE_KINDS = {
|
|
33
|
+
symbol: 0,
|
|
34
|
+
ipv4: 1,
|
|
35
|
+
char: 2,
|
|
36
|
+
geohash: 3,
|
|
37
|
+
varchar: 4
|
|
38
|
+
}.freeze
|
|
39
|
+
private_constant :ACK_LEVELS, :OVERRIDE_KINDS
|
|
40
|
+
|
|
41
|
+
class PointerCapsule
|
|
42
|
+
attr_reader :name
|
|
43
|
+
|
|
44
|
+
def initialize(pointer, name: "arrow_array_stream")
|
|
45
|
+
@pointer = Integer(pointer)
|
|
46
|
+
@name = name.freeze
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def to_i = @pointer
|
|
50
|
+
end
|
|
51
|
+
private_constant :PointerCapsule
|
|
52
|
+
|
|
53
|
+
module_function
|
|
54
|
+
|
|
55
|
+
def with_stream(source)
|
|
56
|
+
if source.respond_to?(:arrow_c_stream)
|
|
57
|
+
capsule = source.arrow_c_stream
|
|
58
|
+
validate_capsule!(capsule)
|
|
59
|
+
return yield capsule
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
reader = red_arrow_reader(source)
|
|
63
|
+
unless reader
|
|
64
|
+
raise TypeError,
|
|
65
|
+
"Arrow source must respond to #arrow_c_stream or be an Arrow::Table, Arrow::RecordBatch, or Arrow::RecordBatchReader"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
pointer = reader.export
|
|
69
|
+
capsule = PointerCapsule.new(pointer)
|
|
70
|
+
begin
|
|
71
|
+
yield capsule
|
|
72
|
+
ensure
|
|
73
|
+
# Red Arrow allocates only the ArrowArrayStream struct shell here.
|
|
74
|
+
# Native ingestion consumes/releases its contents; the shell remains
|
|
75
|
+
# the exporter's responsibility.
|
|
76
|
+
GLib.free(pointer)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def validate_capsule!(capsule)
|
|
81
|
+
raise TypeError, "arrow_c_stream must return an object responding to #to_i" unless capsule.respond_to?(:to_i)
|
|
82
|
+
if capsule.respond_to?(:name) && capsule.name != "arrow_array_stream"
|
|
83
|
+
raise TypeError, "arrow_c_stream returned #{capsule.name.inspect}, expected \"arrow_array_stream\""
|
|
84
|
+
end
|
|
85
|
+
capsule
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def red_arrow_reader(source)
|
|
89
|
+
return nil unless defined?(::Arrow::RecordBatchReader)
|
|
90
|
+
return source if source.is_a?(::Arrow::RecordBatchReader)
|
|
91
|
+
if defined?(::Arrow::Table) && source.is_a?(::Arrow::Table)
|
|
92
|
+
return ::Arrow::TableBatchReader.new(source)
|
|
93
|
+
end
|
|
94
|
+
if defined?(::Arrow::RecordBatch) && source.is_a?(::Arrow::RecordBatch)
|
|
95
|
+
return ::Arrow::RecordBatchReader.new([source])
|
|
96
|
+
end
|
|
97
|
+
nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def normalize_ack(value)
|
|
101
|
+
value = value.to_sym if value.respond_to?(:to_sym)
|
|
102
|
+
ACK_LEVELS.fetch(value) { raise ArgumentError, "ack must be :ok or :durable" }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def normalize_at(value)
|
|
106
|
+
case value
|
|
107
|
+
when String, Symbol
|
|
108
|
+
[2, value]
|
|
109
|
+
when TimestampNanos
|
|
110
|
+
[1, non_negative_i64(value.value, "designated timestamp")]
|
|
111
|
+
when TimestampMicros
|
|
112
|
+
[1, non_negative_i64(value.value * 1_000, "designated timestamp")]
|
|
113
|
+
when Time
|
|
114
|
+
[1, non_negative_i64(value.to_i * 1_000_000_000 + value.nsec, "designated timestamp")]
|
|
115
|
+
else
|
|
116
|
+
return [0, nil] if value.equal?(SERVER_TIMESTAMP)
|
|
117
|
+
raise TypeError, "at must be a timestamp column name, TimestampNanos, TimestampMicros, Time, or SERVER_TIMESTAMP"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def normalize_overrides(symbols, overrides)
|
|
122
|
+
raise TypeError, "schema_overrides must be a Hash" unless overrides.is_a?(Hash)
|
|
123
|
+
descriptors = {}
|
|
124
|
+
case symbols
|
|
125
|
+
when :auto, nil
|
|
126
|
+
when Array
|
|
127
|
+
symbols.each { |name| descriptors[name.to_s] = [name, OVERRIDE_KINDS[:symbol], 0] }
|
|
128
|
+
else
|
|
129
|
+
raise ArgumentError, "symbols must be :auto or an Array of column names"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
overrides.each do |name, specification|
|
|
133
|
+
kind, argument = if specification.is_a?(Array)
|
|
134
|
+
raise ArgumentError, "override arrays must contain [kind, argument]" unless specification.length == 2
|
|
135
|
+
specification
|
|
136
|
+
else
|
|
137
|
+
[specification, 0]
|
|
138
|
+
end
|
|
139
|
+
kind = kind.to_sym if kind.respond_to?(:to_sym)
|
|
140
|
+
code = OVERRIDE_KINDS.fetch(kind) do
|
|
141
|
+
raise ArgumentError, "unsupported Arrow schema override #{kind.inspect}"
|
|
142
|
+
end
|
|
143
|
+
argument = Integer(argument)
|
|
144
|
+
if kind == :geohash
|
|
145
|
+
raise RangeError, "geohash precision must be between 1 and 60 bits" unless argument.between?(1, 60)
|
|
146
|
+
elsif argument != 0
|
|
147
|
+
raise ArgumentError, "only a geohash override accepts an argument"
|
|
148
|
+
end
|
|
149
|
+
key = name.to_s
|
|
150
|
+
raise ArgumentError, "duplicate Arrow override for #{key.inspect}" if descriptors.key?(key)
|
|
151
|
+
descriptors[key] = [name, code, argument]
|
|
152
|
+
end
|
|
153
|
+
descriptors.values.freeze
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def non_negative_i64(value, label)
|
|
157
|
+
value = Integer(value)
|
|
158
|
+
raise RangeError, "#{label} cannot be before the Unix epoch" if value.negative?
|
|
159
|
+
raise RangeError, "#{label} is outside signed 64-bit range" if value > (1 << 63) - 1
|
|
160
|
+
value
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
class Client
|
|
165
|
+
def ingest_arrow(table, source, at:, symbols: :auto, schema_overrides: {}, ack: :ok,
|
|
166
|
+
checkpoint_batches: 100)
|
|
167
|
+
at_kind, at_value = Arrow.normalize_at(at)
|
|
168
|
+
overrides = Arrow.normalize_overrides(symbols, schema_overrides)
|
|
169
|
+
ack_level = Arrow.normalize_ack(ack)
|
|
170
|
+
checkpoint_batches = Integer(checkpoint_batches)
|
|
171
|
+
raise ArgumentError, "checkpoint_batches must be positive" unless checkpoint_batches.positive?
|
|
172
|
+
@mutex.synchronize { ensure_open! }
|
|
173
|
+
Arrow.with_stream(source) do |capsule|
|
|
174
|
+
@native.ingest_arrow(
|
|
175
|
+
capsule, table, at_kind, at_value, overrides, ack_level, checkpoint_batches
|
|
176
|
+
)
|
|
177
|
+
end
|
|
178
|
+
self
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
class PooledSender
|
|
183
|
+
def ingest_arrow(table, source, at:, symbols: :auto, schema_overrides: {}, ack: :ok,
|
|
184
|
+
checkpoint_batches: 100)
|
|
185
|
+
ensure_open!
|
|
186
|
+
@client.ingest_arrow(
|
|
187
|
+
table, source, at: at, symbols: symbols,
|
|
188
|
+
schema_overrides: schema_overrides, ack: ack,
|
|
189
|
+
checkpoint_batches: checkpoint_batches
|
|
190
|
+
)
|
|
191
|
+
self
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
class Sender
|
|
196
|
+
def ingest_arrow(table, source, at:, symbols: :auto, schema_overrides: {}, ack: :ok,
|
|
197
|
+
checkpoint_batches: 100)
|
|
198
|
+
at_kind, at_value = Arrow.normalize_at(at)
|
|
199
|
+
overrides = Arrow.normalize_overrides(symbols, schema_overrides)
|
|
200
|
+
ack_level = Arrow.normalize_ack(ack)
|
|
201
|
+
checkpoint_batches = Integer(checkpoint_batches)
|
|
202
|
+
raise ArgumentError, "checkpoint_batches must be positive" unless checkpoint_batches.positive?
|
|
203
|
+
ensure_ws!
|
|
204
|
+
Arrow.with_stream(source) do |capsule|
|
|
205
|
+
@native.ingest_arrow(
|
|
206
|
+
@configuration, capsule, table, at_kind, at_value,
|
|
207
|
+
overrides, ack_level, checkpoint_batches
|
|
208
|
+
)
|
|
209
|
+
end
|
|
210
|
+
self
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
class QueryResult
|
|
215
|
+
# Return a one-shot Arrow C Stream capsule. The native query is fully
|
|
216
|
+
# materialized into Arrow-owned batches before this method returns, then
|
|
217
|
+
# the reader lease is closed. Consumers may retain the capsule independently.
|
|
218
|
+
def arrow_c_stream
|
|
219
|
+
start_consumption!
|
|
220
|
+
capsule = nil
|
|
221
|
+
begin
|
|
222
|
+
capsule = @native.materialize_arrow_stream
|
|
223
|
+
ensure
|
|
224
|
+
close_preserving_primary_error
|
|
225
|
+
end
|
|
226
|
+
capsule
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def to_nanoarrow
|
|
230
|
+
require "nanoarrow"
|
|
231
|
+
Nanoarrow::Array.new(self)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def to_polars
|
|
235
|
+
require "polars-df"
|
|
236
|
+
Polars::DataFrame.new(self)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def to_red_arrow_table
|
|
240
|
+
require "arrow"
|
|
241
|
+
capsule = arrow_c_stream
|
|
242
|
+
reader = ::Arrow::RecordBatchReader.import(capsule.to_i)
|
|
243
|
+
begin
|
|
244
|
+
reader.read_all
|
|
245
|
+
ensure
|
|
246
|
+
reader.unref
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|