diff --git a/go.mod b/go.mod
index 80e78d944461b0f8dc9c3d4ef19a6792589bba70..69763ef6b473d4838d74e781fb40d99dee269759 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module gfx.cafe/gfx/pggat
 go 1.19
 
 require (
-	gfx.cafe/ghalliday1/pg3p v0.0.13
+	gfx.cafe/ghalliday1/pg3p v0.0.15
 	gfx.cafe/ghalliday1/pgparser v0.0.9
 	gfx.cafe/util/go/bufpool v0.0.0-20220906091724-3a24b7f40ccf
 	gfx.cafe/util/go/generic v0.0.0-20220917152604-80373e5a2c51
diff --git a/go.sum b/go.sum
index 5f6824825855d4d830ec7f61e4fa826b736afeeb..bb086c90bd8ebc6d3dc835d2ba5f96a97e22dde5 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,7 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-gfx.cafe/ghalliday1/pg3p v0.0.13 h1:W5lk6ipc2FmVcjYtIev1W9U9Ha4SFl+qD340NuLe2Qo=
-gfx.cafe/ghalliday1/pg3p v0.0.13/go.mod h1:dVy94HSyywugiaUJm+4EyhC41TOL3SHSr+FUubKa7s8=
+gfx.cafe/ghalliday1/pg3p v0.0.15 h1:6n4wGfxcFHrFWiCurj0N2wC+/CV+AuVxfRTKmKW8HGQ=
+gfx.cafe/ghalliday1/pg3p v0.0.15/go.mod h1:dVy94HSyywugiaUJm+4EyhC41TOL3SHSr+FUubKa7s8=
 gfx.cafe/ghalliday1/pgparser v0.0.9 h1:mj1819zaEO964rFMr9lzGIoKLdZdjWAs6CqPvcSAhYg=
 gfx.cafe/ghalliday1/pgparser v0.0.9/go.mod h1:mte3a6XnIWVexcvJJrkYQ2ozbSx/soIJ4ivXf8pS1No=
 gfx.cafe/util/go/bufpool v0.0.0-20220906091724-3a24b7f40ccf h1:ya4IK1D+Kq0DrFdrrZ7tjmp3BgoO4v5sCAeUytR6j1U=
diff --git a/lib/config/config.go b/lib/config/config.go
index 3bffbd6d088d0a51ee1f2a1b5405404270f80d40..a2ffd865f24529b71a3c9a98476b6aa3a14b6823 100644
--- a/lib/config/config.go
+++ b/lib/config/config.go
@@ -33,6 +33,19 @@ const (
 	USERROLE_READER UserRole = "reader"
 )
 
+func (r UserRole) CanUse(which ServerRole) bool {
+	switch r {
+	case USERROLE_ADMIN:
+		return true
+	case USERROLE_WRITER:
+		return true
+	case USERROLE_READER:
+		return which != SERVERROLE_PRIMARY
+	default:
+		return false
+	}
+}
+
 func Load(path string) (*Global, error) {
 	var g Global
 	ext := filepath.Ext(path)
diff --git a/lib/gat/database/query_router/query_router.go b/lib/gat/database/query_router/query_router.go
index 422568900c54ad96318247f2793dfa18359dc317..0e1a6e366e3383675b62873f0893d88dd97b95d6 100644
--- a/lib/gat/database/query_router/query_router.go
+++ b/lib/gat/database/query_router/query_router.go
@@ -5,6 +5,8 @@ import (
 	"gfx.cafe/gfx/pggat/lib/config"
 	"gfx.cafe/gfx/pggat/lib/gat"
 	"gfx.cafe/gfx/pggat/lib/util/cmux"
+	"gfx.cafe/ghalliday1/pg3p"
+	"gfx.cafe/ghalliday1/pg3p/lex"
 	"strconv"
 	"unicode"
 	"unicode/utf8"
@@ -12,6 +14,7 @@ import (
 
 type QueryRouter struct {
 	router cmux.Mux[gat.Client, error]
+	parser *pg3p.Parser
 }
 
 var DefaultRouter = func() *QueryRouter {
@@ -64,43 +67,24 @@ var DefaultRouter = func() *QueryRouter {
 	})
 	return &QueryRouter{
 		router: r,
+		parser: pg3p.NewParser(),
 	}
 }()
 
 // Try to infer the server role to try to  connect to
 // based on the contents of the query.
 // note that the user needs to be checked to see if they are allowed to access.
-// TODO: implement
 func (r *QueryRouter) InferRole(query string) (config.ServerRole, error) {
-	var active_role config.ServerRole
-	// by default it will hit a primary (for now)
-	active_role = config.SERVERROLE_PRIMARY
-	//// ok now parse the query
-	//wk := &walk.AstWalker{
-	//	Fn: func(ctx, node any) (stop bool) {
-	//		switch n := node.(type) {
-	//		case *tree.Update, *tree.UpdateExpr,
-	//			*tree.BeginTransaction, *tree.CommitTransaction, *tree.RollbackTransaction,
-	//			*tree.SetTransaction, *tree.ShowTransactionStatus, *tree.Delete, *tree.Insert:
-	//			//
-	//			active_role = config.SERVERROLE_PRIMARY
-	//			return true
-	//		default:
-	//			_ = n
-	//		}
-	//		return false
-	//	},
-	//}
-	//stmts, err := parser.Parse(query)
-	//if err != nil {
-	//	log.Println("failed to parse (%query), assuming primary required", err)
-	//	return config.SERVERROLE_PRIMARY, nil
-	//}
-	//_, err = wk.Walk(stmts, nil)
-	//if err != nil {
-	//	return config.SERVERROLE_PRIMARY, err
-	//}
-	return active_role, nil
+	// parse the query
+	tokens := r.parser.Lex(query)
+	var role = config.SERVERROLE_REPLICA
+	for _, token := range tokens {
+		switch token.Token {
+		case lex.KeywordUpdate, lex.KeywordDelete, lex.KeywordInsert:
+			role = config.SERVERROLE_PRIMARY
+		}
+	}
+	return role, nil
 }
 
 func (r *QueryRouter) TryHandle(client gat.Client, query string) (handled bool, err error) {
diff --git a/lib/gat/pool/transaction/worker.go b/lib/gat/pool/transaction/worker.go
index 082e5a8806bfbf9517246aa81a068ada321225e5..4df00b13423e4d20946bc751cfdc1a04ca0ec07c 100644
--- a/lib/gat/pool/transaction/worker.go
+++ b/lib/gat/pool/transaction/worker.go
@@ -2,6 +2,7 @@ package transaction
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"gfx.cafe/gfx/pggat/lib/config"
 	"gfx.cafe/gfx/pggat/lib/gat"
@@ -229,6 +230,9 @@ func (w *worker) z_actually_do_describe(ctx context.Context, client gat.Client,
 	// describe the portal
 	// we can use a replica because we are just describing what this query will return, query content doesn't matter
 	// because nothing is actually executed yet
+	if !w.w.user.Role.CanUse(config.SERVERROLE_REPLICA) {
+		return errors.New("permission denied")
+	}
 	target := srv.Choose(config.SERVERROLE_REPLICA)
 	if target == nil {
 		return fmt.Errorf("describe('%+v') fail: no server", payload)
@@ -266,6 +270,9 @@ func (w *worker) z_actually_do_execute(ctx context.Context, client gat.Client, p
 	if err != nil {
 		return err
 	}
+	if !w.w.user.Role.CanUse(which) {
+		return errors.New("permission denied")
+	}
 	target := srv.Choose(which)
 	w.setCurrentBinding(client, target)
 	defer w.unsetCurrentBinding(client, target)
@@ -280,6 +287,9 @@ func (w *worker) z_actually_do_fn(ctx context.Context, client gat.Client, payloa
 		return fmt.Errorf("fn('%+v') fail: no server", payload)
 	}
 	// call the function
+	if !w.w.user.Role.CanUse(config.SERVERROLE_PRIMARY) {
+		return errors.New("permission denied")
+	}
 	target := srv.GetPrimary()
 	if target == nil {
 		return fmt.Errorf("fn('%+v') fail: no target ", payload)
@@ -303,6 +313,9 @@ func (w *worker) z_actually_do_simple_query(ctx context.Context, client gat.Clie
 	if err != nil {
 		return fmt.Errorf("error parsing '%s': %w", payload, err)
 	}
+	if !w.w.user.Role.CanUse(which) {
+		return errors.New("permission denied")
+	}
 	// configures the server to run with a specific role
 	target := srv.Choose(which)
 	if target == nil {
@@ -328,6 +341,9 @@ func (w *worker) z_actually_do_transaction(ctx context.Context, client gat.Clien
 	if err != nil {
 		return fmt.Errorf("error parsing '%s': %w", payload, err)
 	}
+	if !w.w.user.Role.CanUse(which) {
+		return errors.New("permission denied")
+	}
 	// configures the server to run with a specific role
 	target := srv.Choose(which)
 	if target == nil {