dparnell / cirro_connect

An Elixir websocket-based SQL connector for Cirro

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

CirroConnect

An Elixir websocket-based SQL connector for Cirro

CirroConnect allows Elixir programs to connect to Cirro (http://www.cirro.com) using its websocket API and issue federated queries.

Installation

Add the following to mix.exs to include it in your project:

def deps do
  [
    {:cirro_connect, ">= 0.1.9"},
  ]
end

Usage

###Connecting to Cirro Here, and in all subsequent examples, cirro.host.com refers to your Cirro installation's exposed websocket address. The ws:// protocol is currently active as the default. wss:// support will become the default in an upcoming release.

To connect to Cirro, use connect() or connect!()

# return connection status
CirroConnect.connect("cirro.host.com","cirro_user","password")
{:ok, {#PID<0.209.0>, "347456094e6885c5-6a83-48e0-a0d7-f08326c720af2141803887"}}
# return connection for use in other CirroConnect calls
CirroConnect.connect!("cirro.host.com","cirro_user","password")
{#PID<0.218.0>, "1289850004088516e1-0f84-4e00-9988-691a4ffc22852010714971"}

Connectivity check

You can determine if your connection is currently active using the is_connected() function.

c=CirroConnect.connect!("cirro.host.com","cirro_user","password")
CirroConnect.is_connected(c)
true
CirroConnect.close(c)
CirroConnect.is_connected(c)
false

Queries

Basic selection

conn=CirroConnect.connect!("cirro.host.com","cirro_user","password")
"SELECT * from dbbox_postgres.oxon.public.person"
|> CirroConnect.query(conn)
{:ok,
 %{"complete" => true, "count" => 3, "end" => 1513853929898, "error" => false,
   "meta" => [%{"name" => "id", "type" => "INTEGER"},
    %{"name" => "name", "type" => "VARCHAR"},
    %{"name" => "age", "type" => "INTEGER"}],
   "rows" => [["1", "Foople Smith", "23"], ["3", "Procras Tinatus", "54"],
    ["2", "オーマー・マズリ", "54"]], "start" => 1513853929747,
   "task" => %{"authtoken" => "21364823203979e007-c7af-4518-a396-fe5fb827c81b374209238",
     "cancelled" => false, "command" => "query", "id" => "675",
     "options" => %{},
     "statement" => "SELECT * from dbbox_postgres.oxon.public.person"}}}

Selection as a 'table' with a header row

conn=CirroConnect.connect!("cirro.host.com","cirro_user","password")
"SELECT * from dbbox_postgres.oxon.public.person"
|> CirroConnect.query(conn)
|> CirroConnect.table()
[["id", "name", "age"],
["1", "Foople Smith", "23"],["3", "Procras Tinatus", "54"],["2", "オーマー・マズリ", "54"]]

Selection returning a 'table' with just the rows

conn=CirroConnect.connect!("cirro.host.com","cirro_user","password")
"SELECT * from dbbox_postgres.oxon.public.person"
|> CirroConnect.query(conn)
|> CirroConnect.rows()
[["1", "Foople Smith", "23"],["3", "Procras Tinatus", "54"],["2", "オーマー・マズリ", "54"]]

Selection returning each row as a map of column name to value

conn=CirroConnect.connect!("cirro.host.com","cirro_user","password")
"SELECT * from dbbox_postgres.oxon.public.person"
|> CirroConnect.query(conn)
|> CirroConnect.map()
[%{age: "23", id: "1", name: "Foople Smith"},
 %{age: "54", id: "3", name: "Procras Tinatus"},
 %{age: "54", id: "2", name: "オーマー・マズリ"}]

Execute non-row-returning queries

The results should contain the count of the number of affected rows.

conn=CirroConnect.connect!("cirro.host.com","cirro_user","password")
"DELETE from dbbox_postgres.oxon.public.personcopy"
|> CirroConnect.exec(conn)
{:ok,
 %{"complete" => true, "count" => 2, "end" => 1513855015278, "error" => false,
   "meta" => [], "rows" => [], "start" => 1513855015101,
   "task" => %{"authtoken" => "82424824185de34cd-1eb7-492e-9de5-75315a97e6372099360753",
     "cancelled" => false, "command" => "execute", "id" => "676",
     "options" => %{},
     "statement" => "DELETE from dbbox_postgres.oxon.public.personcopy"}}}

Connection handling

Queries are run on arbitrary connections maintained by a server-side connection pool. To run several commands on a specific connection using multiple calls, you must give it a name

conn=CirroConnect.connect!("cirro.host.com","cirro_user","password")

# execute a query on a named connection
"SELECT * from dbbox_postgres.oxon.public.person"
|> CirroConnect.query(conn,%{name: "my_specific_connection"})

# the following is guaranteed to execute on the same internal connection as the above statement
"SELECT * from dbbox_oracle.oxon.public.person"
|> CirroConnect.query(conn,%{name: "my_specific_connection"})

You should explicitly close a named connection. However, it will be automatically cleaned up after 60 seconds of inactivity.

conn=CirroConnect.connect!("cirro.host.com","cirro_user","password")
CirroConnect.close(conn,%{name: "my_specific_connection"})

Fetching batches of rows

It is possible to fetch a limited number of rows per call. This currently involves making an initial query with an optional fetchsize value. This returns up to fetchsize rows. The value of complete is false if there are more rows to retrieve. These can be fetched by calling next() with the id from the original result's task. If you don't require further results, you can cancel the query with CirroConnect.cancel(conn,id) using the same id. This calling convention will hopefully be made easier to handle when wrapped in a stream (TODO).

conn=CirroConnect.connect!("cirro.host.com","cirro_user","password")
"SELECT * FROM dbbox_postgres.oxon.public.person"
|> CirroConnect.query(conn,%{fetchsize: 2})
{:ok,
 %{"complete" => false, "count" => 3, "end" => 1513858116713, "error" => false,
   "meta" => [%{"name" => "id", "type" => "INTEGER"},
    %{"name" => "name", "type" => "VARCHAR"},
    %{"name" => "age", "type" => "INTEGER"}],
   "rows" => [["1", "Foople Smith", "23"], ["3", "Procras Tinatus", "54"]],
   "start" => 1513858116158,
   "task" => %{"authtoken" => "262905974c2922bf5-f28e-4c35-a2bf-a97fe93075d22088770177",
     "cancelled" => false, "command" => "query", "id" => "669",
     "options" => %{"fetchsize" => "2"},
     "statement" => "SELECT * FROM dbbox_postgres.oxon.public.person"}}}
CirroConnect.next(conn,"669")
{:ok,
 %{"complete" => true, "count" => 0, "end" => 1513858125766, "error" => false,
   "meta" => [%{"name" => "id", "type" => "INTEGER"},
    %{"name" => "name", "type" => "VARCHAR"},
    %{"name" => "age", "type" => "INTEGER"}],
   "rows" => [["2", "オーマー・マズリ", "54"]],
   "start" => 1513858116158,
   "task" => %{"authtoken" => "262905974c2922bf5-f28e-4c35-a2bf-a97fe93075d22088770177",
     "cancelled" => false, "command" => "query", "id" => "669",
     "options" => %{"fetchsize" => "2"},
     "statement" => "SELECT * FROM dbbox_postgres.oxon.public.person"}}}
# don't issue another next here, or the call will block

Multiple statements

You can run multiple statements in the one request. The results of SELECTS are concatenated unless the multi option is true in which case the results will be sent back in response to calls to next(). The default delimiter is \; but can be overriden using the delimiter parameter.

conn=CirroConnect.connect!("cirro.host.com","cirro_user","password")
"SELECT * from dbbox_postgres.oxon.public.person ; SELECT * from dbbox_oracle.dbo.public.person"
|> CirroConnect.query(conn,%{delimiter: ";"})

# speed up UNION ALL queries
"SELECT * from dbbox_postgres.oxon.public.person UNION ALL SELECT * from dbbox_oracle.dbo.public.person"
|> CirroConnect.query(conn,%{delimiter: "UNION ALL"})

Listing active connections

It is possible to get list of the current Cirro connections your websocket connection is using

conn=CirroConnect.connect!("cirro.host.com","cirro_user","password")
CirroConnect.connections(conn)
[["*", "com.cirro.jdbc.client.net.security.NetConnection40@2cedebbd",
  "Tue Jan 02 16:06:42 UTC 2018", "2979", "Idle"]]

Listing tasks

You can get a list of the current tasks you have initiated that have not yet completed

conn=CirroConnect.connect!("cirro.host.com","cirro_user","password")
CirroConnect.tasks(conn)
[]

Monitoring Cirro

To listen to Cirro monitoring events, bind a process or function via the monitor function. A process will then receive {:cirro_monitor, event} messages, whereas a function will just be called with these messages. The event_type and session_id can be filtered using the optional options. CirroConnect.watch_events is provided as an example.

CirroConnect.connect!("cirro.host.com","cirro_user","password") |>
CirroConnect.monitor(%{event_type: "query"}, &CirroConnect.dump_message/1)

Disconnecting

To close all of your connections, and disconnect your connection to Cirro

connection=CirroConnect.connect!("cirro.host.com","cirro_user","password")
CirroConnect.close(connection)

Custom message handling

By default, responses from Cirro are received by the current process. Therefore each call will usually block until the results are returned. You can provide custom recipient function or process to handle incoming messages. Processes will receive messages, whereas functions will be supplied the message as an argument. The process or function argument is an optional parameter after the options parameter.

Some examples

# Function example
CirroConnect.query("SELECT * FROM SYS.SYSROLES",conn,%{delimiter: ";"},&MyThing.handle_results/1)
# Process example
pid=spawn(MyThing,:wait_for_cirro_messages,[])
CirroConnect.query("SELECT * FROM SYS.SYSROLES",conn,%{delimiter: ";"},pid)

The default process message handler looks like this:

 def await_results() do
    receive do
      {:cirro_connect, %{"error" => true, "message" => error_message}} -> {:error, error_message}
      {:cirro_connect, %{"cancelled" => true, "message" => error_message}} -> {:error, error_message}
      {:cirro_connect, %{"error" => false} = response} -> {:ok, response}
      {:cirro_monitor, event} -> {:ok, event}
      {:error, error} -> {:error, error}
      {:error} -> {:error, "Unknown error"}
    end
  end

About

An Elixir websocket-based SQL connector for Cirro

License:MIT License


Languages

Language:Elixir 100.0%