samadhiweb

smalltalk programming for the web



PostgresV3 Protocol State Machine using GraphViz

28 April 2013

In the previous post I wrote about visualizing the PostgresV3 pure Smalltalk implementation of the PostgreSQL v3 wire protocol. That "visualization" was in text, which is not nearly as visual as seeing the protocol state machine graphically.

In this post, let's use GraphViz to do that. Building on the code previously written:

| skip states statesMap gv |

skip := #('PG3NoticeResponse' 'PG3NotificationResponse' 'PG3ParameterStatus').
states := OrderedCollection new.

PG3ServerState createStateGraph valuesDo: [ :inst |
  inst transitions keysAndValuesDo: [ :k :v |
    (skip includes: k asString) ifFalse: [
      states add: (Array with: inst with: k with: v first with: v second) ]]].

states do: [ :ea |
  Transcript show: 'From ', ea first name; 
    show: ' on ', ea second asString.
  ea fourth ifNotNil: [ Transcript show: ' perform #', ea fourth ].
  Transcript show: ' goto ', ea third name; cr; flush ].

statesMap := Dictionary new.
states do: [ :ea |
  (statesMap includesKey: ea first name) ifFalse: [
    statesMap at: ea first name put: OrderedCollection new ].
  (statesMap at: ea first name) add: ea ].

gv := GraphViz new.
gv beDirected;
  name: 'PostgresV3 Protocol State Machine';
  add: #graph with: { #overlap -> #scale. #concentrate -> #true };
  add: #edge with: { #arrowsize -> 0.5 }.
	
statesMap keysAndValuesDo: [ :k :v |
  gv add: k with: { #shape -> #box. #fontsize -> 10 }.
  v do: [ :ea |
    gv add: ea third name with: { #shape -> #box. #fontsize -> 10 }.
    gv add: k -> ea third name with: { #label -> ea second asString. #fontsize -> 8 } ]].

gv openInWindow.

PostgresV3 Protocol State Machine

28 April 2013

PostgresV3 is a pure Smalltalk implementation of the PostgreSQL v3 wire protocol (and a database access API) by Levente Uzonyi and Balazs Kosi.

In code, the protocol is implemented as a state machine, created by "PG3ServerState createStateGraph", which invokes methods such as this:

initializingBackendStateDescription

(self state: #InitializingBackend)
  on: PG3BackendKeyData
  connectionDo: #registerBackendKeyData:
  goto: #InitializingBackend;

  on: PG3ReadyForQuery
  connectionDo: #readyForQuery:
  goto: #WaitingForQuery

"on:connectionDo:goto:" looks like this:

on: aPG3MessageClass connectionDo: selector goto: aSymbol
  transitions at: aPG3MessageClass put: { (self state: aSymbol). selector }

I wanted to map out all state transitions, to better visualize the protocol flow. This is done by walking through the "transitions" inst-var of each instance of PG3ServerState:

| skip states |

skip := #('PG3NoticeResponse' 'PG3NotificationResponse' 'PG3ParameterStatus').
states := OrderedCollection new.

PG3ServerState createStateGraph valuesDo: [ :inst |
  inst transitions keysAndValuesDo: [ :k :v |
    (skip includes: k asString) ifFalse: [
      states add: (Array with: inst with: k with: v first with: v second) ]]].

states do: [ :ea |
  Transcript show: 'From ', ea first name; 
    show: ' on ', ea second asString.
  ea fourth ifNotNil: [ Transcript show: ' perform #', ea fourth ].
  Transcript show: ' goto ', ea third name; cr; flush ].

Here's the output:

From InitializingBackend on PG3ReadyForQuery perform #readyForQuery: goto WaitingForQuery
From InitializingBackend on PG3BackendKeyData perform #registerBackendKeyData: goto InitializingBackend
From GotDataRow on PG3ErrorResponse perform #handleError: goto GotErrorResponseDuringSimpleQuery
From GotDataRow on PG3CommandComplete perform #commandComplete: goto GotCommandComplete
From GotDataRow on PG3DataRow perform #dataRow: goto GotDataRow
From GotErrorResponseDuringSimpleQuery on PG3ReadyForQuery perform #readyForQuery: goto WaitingForQuery
From GotEmptyQueryResponse on PG3ReadyForQuery perform #readyForQuery: goto WaitingForQuery
From GotEmptyQueryResponse on PG3ErrorResponse perform #handleError: goto GotErrorResponseDuringSimpleQuery
From Querying on PG3ErrorResponse perform #handleError: goto GotErrorResponseDuringSimpleQuery
From Querying on PG3CommandComplete perform #commandComplete: goto GotCommandComplete
From Querying on PG3RowDescription perform #rowDescription: goto GotRowDescription
From Querying on PG3EmptyQueryResponse goto GotEmptyQueryResponse
From Authenticating on PG3AuthenticationMD5Password perform #respondToAuthenticationMD5PasswordRequest: goto AuthenticatingWithMD5
From Authenticating on PG3AuthenticationOkMessage goto InitializingBackend
From AuthenticatingWithMD5 on PG3AuthenticationOkMessage goto InitializingBackend
From GotCommandComplete on PG3ReadyForQuery perform #readyForQuery: goto WaitingForQuery
From GotCommandComplete on PG3ErrorResponse perform #handleError: goto GotErrorResponseDuringSimpleQuery
From GotCommandComplete on PG3CommandComplete perform #commandComplete: goto GotCommandComplete
From GotCommandComplete on PG3RowDescription perform #rowDescription: goto GotRowDescription
From GotRowDescription on PG3ErrorResponse perform #handleError: goto GotErrorResponseDuringSimpleQuery
From GotRowDescription on PG3CommandComplete perform #commandComplete: goto GotCommandComplete
From GotRowDescription on PG3DataRow perform #dataRow: goto GotDataRow