SQL Relay Client API's

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
}