Example Queries
This guide shows a variety of example queries using the DB gem.
Setup
require 'async'
require 'db/client'
require 'db/postgres'
client = DB::Client.new(DB::Postgres::Adapter.new(
database: 'test',
host: '172.17.0.3',
password: 'test',
username: 'postgres',
))
A simple CREATE, INSERT and SELECT, with raw SQL
Sync do
session = client.session
create = "CREATE TABLE IF NOT EXISTS my_table (a_timestamp TIMESTAMP NOT NULL)"
session.query(create).call
insert = "INSERT INTO my_table VALUES (NOW()), ('2022-12-12 12:13:14')"
session.query(insert).call
result = session.query("SELECT * FROM my_table WHERE a_timestamp > NOW()").call
Console.info result.field_types.to_s
Console.info result.field_names.to_s
Console.info result.to_a.to_s
ensure
session&.close
end
Output
0.01s info: [#<DB::Postgres::Native::Types::DateTime:0x00007eff3b13e688 @name="TIMESTAMP">]
0.01s info: ["a_timestamp"]
0.01s info: [[2022-12-12 12:13:14 UTC]]
Parameterized CREATE, INSERT and SELECT
The same process as before, but parameterized. Always use the parameterized form when dealing with untrusted data.
Sync do
session = client.session
session.clause("CREATE TABLE IF NOT EXISTS")
.identifier(:my_table)
.clause("(")
.identifier(:a_timestamp).clause("TIMESTAMP NOT NULL")
.clause(")")
.call
session.clause("INSERT INTO")
.identifier(:my_table)
.clause("VALUES (")
.literal("NOW()")
.clause("), (")
.literal("2022-12-12 12:13:14")
.clause(")")
.call
result = session.clause("SELECT * FROM")
.identifier(:my_table)
.clause("WHERE")
.identifier(:a_timestamp).clause(">").literal("NOW()")
.call
Console.info result.field_types.to_s
Console.info result.field_names.to_s
Console.info result.to_a.to_s
ensure
session&.close
end
Output
0.01s info: [#<DB::Postgres::Native::Types::DateTime:0x00007eff3b13e688 @name="TIMESTAMP">]
0.01s info: ["a_timestamp"]
0.01s info: [[2022-12-12 12:13:14 UTC]]
A parameterized SELECT
Sync do |task|
session = client.session
result = session
.clause("SELECT")
.identifier(:column_one)
.clause(",")
.identifier(:column_two)
.clause("FROM")
.identifier(:another_table)
.clause("WHERE")
.identifier(:id)
.clause("=")
.literal(42)
.call
Console.info "#{result.field_names}"
Console.info "#{result.to_a}"
end
Output
0.01s info: ["column_one", "column_two"]
0.01s info: [["foo", "bar"], ["baz", "qux"]]
Concurrent queries
(Simulating slow queries with PG_SLEEP
)
Sync do |task|
start = Time.now
tasks = 10.times.map do
task.async do
session = client.session
result = session.query("SELECT PG_SLEEP(10)").call
result.to_a
ensure
session&.close
end
end
results = tasks.map(&:wait)
Console.info "Elapsed time: #{Time.now - start}s"
end
Output
10.05s info: Elapsed time: 10.049756222s
Limited to 3 connections
(Simulating slow queries with PG_SLEEP
)
require 'async/semaphore'
Sync do
semaphore = Async::Semaphore.new(3)
tasks = 10.times.map do |i|
semaphore.async do
session = client.session
Console.info "Starting task #{i}"
result = session.query("SELECT PG_SLEEP(10)").call
result.to_a
ensure
session&.close
end
end
results = tasks.map(&:wait)
Console.info "Done"
end
Output
0.0s info: Starting task 0
0.0s info: Starting task 1
0.0s info: Starting task 2
10.02s info: Completed task 0 after 10.017388464s
10.02s info: Starting task 3
10.02s info: Completed task 1 after 10.02111175s
10.02s info: Starting task 4
10.03s info: Completed task 2 after 10.027889587s
10.03s info: Starting task 5
20.03s info: Completed task 3 after 10.011089096s
20.03s info: Starting task 6
20.03s info: Completed task 4 after 10.008169111s
20.03s info: Starting task 7
20.04s info: Completed task 5 after 10.007644749s
20.04s info: Starting task 8
30.04s info: Completed task 6 after 10.011244562s
30.04s info: Starting task 9
30.04s info: Completed task 7 after 10.011565997s
30.04s info: Completed task 8 after 10.004611464s
40.05s info: Completed task 9 after 10.008239803s
40.05s info: Done
Sequential vs Concurrent INSERTs
DATA = 1_000_000.times.map { SecureRandom.hex }
def setup_tables(client)
session = client.session
create = "CREATE TABLE IF NOT EXISTS salts (salt CHAR(32))"
session.query(create).call
truncate = "TRUNCATE TABLE salts"
session.query(truncate).call
session.close
end
def chunked_insert(rows, client, task=Async::Task.current)
task.async do
session = client.session
rows.each_slice(1000) do |slice|
insert = "INSERT INTO salts VALUES " + slice.map { |x| "('#{x}')" }.join(",")
session.query(insert).call
end
ensure
session&.close
end
end
Sync do
Console.info "Setting up tables"
setup_tables(client)
Console.info "Done"
start = Time.now
Console.info "Starting sequential insert"
chunked_insert(DATA, client).wait
Console.info "Completed sequential insert in #{Time.now - start}s"
start = Time.now
Console.info "Starting concurrent insert"
DATA.each_slice(10_000).map do |slice|
chunked_insert(slice, client)
end.each(&:wait)
Console.info "Completed concurrent insert in #{Time.now - start}s"
end
Output
1.45s info: Setting up tables
1.49s info: Done
1.49s info: Starting sequential insert
8.49s info: Completed sequential insert in 7.006533933s
8.49s info: Starting concurrent insert
9.92s info: Completed concurrent insert in 1.431470847s