The SQL Relay client API's are ultimately based on the C++ API and the Rudiments library. A more portable approach would be to implement the API natively in a language like Java, Python or Perl. A more stripped down and potentially higher performance version of the API could be written in any language.
SQL Relay has always had an open-protocol by virtue of being open-source, but reverse-engineering it from the API code can be a daunting task. If it were documented in plainer terms, it would be "more open".
Born of these motivations, the following pseudocode demonstrates the protocol and its capabilities. This code should provide enough detail about the protocol to enable a developer to write his or her own API.
Note that this specification will likely change a bit as new features are added to SQL Relay.
client() {
start a new session or resume a suspended session
}
newsession() {
connect to the sqlr-listener using an inet or unix datagram socket
send an authentication string()
get a result string()
if (successful authentication) {
get inet and unix ports()
disconnect from the sqlr-listener
connect to the sqlr-connection-"dbase" using an inet or unix datagram socket
engage in a session() with the server
} else if (unsuccessful authentication) {
receive and parse the error()
}
}
send an authentication string() {
sendLong() the length of the user name
sendString() the user name
sendLong() the length of the password
sendString() the password
}
get a result string() {
success=getShort(): whether authentication was successful or not
if (success is 0) {
there has been an authentication error
close the connection
}
}
get inet and unix ports() {
size=getShort(): the size of the result string
read "size" 8 bit characters from the socket: the unix port
inetport=getShort(): the inet port
}
resume a suspended session() {
connect to the sqlr-connection-"dbase" using a provided inet or unix datagram socket
resume any suspended result sets...
loop {
sendShort() a 4 to tell the server that we want to resume a result set
sendShort() the cursor of the result set that we want to resume
getLong() the index of the row that the sqlr-connection-"dbase" is currently on
process rows() to deal with the suspended result set
}
engage in a session() with the server
}
session() {
loop {
send a query, ping or identify()
process rows()
end or suspend the session() or keep going
}
close the connection
}
send a query, ping or identify() {
sendShort() a 0 to tell the server that we're sending a query
sendShort() a 0 to tell the server that we need a cursor
if (query) {
send the query and related info...
send query()
send bind variables and values()
send whether to get column info or not()
process the result set...
check for an error()
if (there has been an error) {
receive and parse the error()
return
}
get the cursor() associated with this result set
handle a suspended result set()
receive and parse the header()
receive and parse the output bind values()
} else if (ping) {
sendLong() a 4
sendString() "ping"
sendShort() a 0 to fake input binds
sendShort() a 0 to fake output binds
sendShort() a 0 to tell the server not to send column info
getShort() 1 or 0; the result of the ping
} else if (identify) {
sendLong() a 8
sendString() "identify"
sendShort() a 0 to fake input binds
sendShort() a 0 to fake output binds
sendShort() a 0 to tell the server not to send column info
size=getShort(): the size of the string
read "size" 8 bit characters from the socket: the identification string
}
}
end or suspend the session() {
if (end) {
sendString() a ~
break out of loop
} else if (suspend) {
sendString() a !
break out of loop
}
}
send query() {
if (this not a re-execution of a previously prepared query) {
sendShort() a 0
sendLong() the length of the query
sendString() the query
} else {
sendShort() a 1
}
}
send bind variables and values() {
Note: the connection daemon will put the colon (or other marker)
on the front of the bind variables, you shouldn't send one
sendShort() the number of input bind variables
loop through the input bind variables {
sendShort() the size of a bind variable
sendString() the bind variable
if (the bind variable is a string) {
sendShort() a 1
sendShort() the size of a bind value
sendString() the bind value
} else if (the bind variable is a long) {
sendShort() a 2
sendLong() the bind value
} else if (the bind variable is a double) {
sendShort() a 3
sendDouble() the bind value
sendShort() the precision
sendShort() the scale
if (the bind variable is a NULL) {
sendShort() a 0
}
}
sendShort() the number of output bind variables
loop through the output bind variables {
sendShort() the size of a bind variable
sendString() the bind variable
sendShort() the size of a bind value
}
}
send whether to get column info or not() {
if (we want to get column info) {
sendShort() a 1
} else {
sendShort() a 0
}
}
check for error() {
status=getShort() whether or not an error has occurred
if (status is 0) {
there was an error in the query
return
}
}
get the cursor() {
getShort() the cursor that this result set will be attached to
}
handle a suspended result set() {
suspended=getShort() whether or not this result set is suspended
if (suspended is 1) {
the result set was suspended in a prior query
getLong() the index of the last row from the previous result set
}
}
receive and parse the header() {
knowsactual=getShort() whether the server knows the
actual number of rows in the result set or not
if (knowsactual is 1) {
totalrows=getLong(): the total number of rows in the result set
}
knowsaffected=getShort() whether the server knows the
affected number of rows in the result set or not
if (knowsaffected is 1) {
affectedrows=getLong(): the number of rows affected by the query
}
columns=getShort(): the number of columns
if (we told the server to send column info) {
for (each column) {
size=getShort(): the size of the column name
read "size" 8 bit characters from the socket: the column name
type=getShort(): the column type
length=getLong(): the column length
(refer to src/common/datatypes.h of the distribution)
datatypestring[type] is the string corresponding to the column type
}
}
}
receive and parse the output bind values() {
loop {
type=getShort(): the type of the value
if (type is 4) {
break out of loop
} else if (type is 0) {
the bind value is NULL
} else {
size=getShort(): the size of the value
read "size" 8 bit characters from the socket: the value
}
}
}
process rows() {
loop {
receive rows, suspend the result set or abort the result set
if (receive rows) {
sendShort() a 1 to tell the server to fetch the result set
sendShort() which cursor to fetch from
sendLong() the number of rows to skip
sendLong() the number of rows to fetch
receive and parse the rows()
} else if (suspend result set) {
sendShort() a 3 to tell the server to suspend the result set
sendShort() which cursor to abort
break out of loop
} else if (abort result set) {
sendShort() a 2 to tell the server to abort the result set
sendShort() which cursor to abort
break out of loop
}
if (got end of result set) {
break out of loop
}
}
}
receive and parse the rows() {
loop {
type=getShort(): the type of the field
if (type is 3) {
break out of the loop
} else if (type is 0) {
the field is NULL
} else if (type is 1) {
the field is normal data...
size=getLong(): the size of the field data
read "size" 8 bit characters from the socket: the field data
} else if (type is 2) {
getLongField(); the field is a long/LOB datatype
}
if (we've read as many rows as we asked for) {
break out of the loop
}
}
}
getLongField() {
loop {
type=getShort(): the type of the chunk
if (type is 3) {
break out of the loop
}
size=getLong(): the size of this chunk
read "size" 8 bit characters from the socket: a chunk of data
}
}
receive and parse the error() {
size=getShort(): the size of the error message
read "size" 8 bit characters from the socket: the error message
}
sendString() {
write the string to the socket
}
sendShort() {
write the 16 bit number to the socket
}
sendLong() {
write the 32 bit number to the socket
}
sendDouble() {
write the double precision floating point number to the socket
}
getShort() {
read a 16 bit number from the socket
}
getLong() {
read a 32 bit number from the socket
}