Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Postgrest Support #96

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
55 changes: 55 additions & 0 deletions cmd/api/postgrest.go
@@ -0,0 +1,55 @@
package api

import (
"errors"
"scratchdata/models"
"scratchdata/models/postgrest"
"scratchdata/pkg/destinations"

"github.com/gofiber/fiber/v2"
"github.com/rs/zerolog/log"
)

func (i *API) PostgrestQuery(c *fiber.Ctx) error {

apiKey := c.Locals("apiKey").(models.APIKey)

// TODO: read-only vs read-write connections
connectionSetting := i.db.GetDatabaseConnection(apiKey.DestinationID)

if connectionSetting.ID == "" {
return errors.New("No DB Connections set up")
}

// TODO: need some sort of local connection pool or storage
connection := destinations.GetDestination(connectionSetting)
if connection == nil {
return errors.New("Destination " + connectionSetting.Type + " does not exist")
}

queryString := c.Context().URI().QueryString()

parser := &postgrest.PostgrestParser{Buffer: string(queryString)}
err := parser.Init()
if err != nil {
return err
}

err = parser.Parse()
if err != nil {
return err
}

log.Print(parser.SprintSyntaxTree())

root := &postgrest.Node{}
postgrest.PopulateAST(string(queryString), root, parser.AST())

postgrestQuery := &postgrest.Postgrest{}
postgrestQuery.FromAST(root)

log.Trace().Interface("postgrest", postgrestQuery).Send()

err = connection.QueryPostgrest(*postgrestQuery, c.Context().Response.BodyWriter())
return err
}
3 changes: 3 additions & 0 deletions cmd/api/routes.go
Expand Up @@ -28,6 +28,9 @@ func (a *API) InitializeAPIServer() error {
a.app.Get("/tables", a.AuthMiddleware, a.Tables)
a.app.Post("/data", a.AuthMiddleware, a.Insert)

postgrest := a.app.Group("/postgrest")
postgrest.Get("/:table", a.AuthMiddleware, a.PostgrestQuery)

err := app.Listen(fmt.Sprintf(":%d", a.config.Port))
if err != nil {
return err
Expand Down
72 changes: 72 additions & 0 deletions models/postgrest/parser.go
@@ -0,0 +1,72 @@
package postgrest

import "fmt"

type Node struct {
Type pegRule
Value string
Parent *Node
Children []*Node
}

func CompactTree(node *Node) *Node {
if node == nil {
return nil
}

// oldNode := node
// If the current node is part of a linear chain, skip to the last node in the chain
for len(node.Children) == 1 {
node = node.Children[0]
}

// Create a new node instance
newNode := &Node{
// Type: oldNode.Type,
Type: node.Type,
Value: node.Value,
}

// Recursively compact the children
for _, child := range node.Children {
newNode.Children = append(newNode.Children, CompactTree(child))
}

return newNode
}

func PrintTree(node *Node, level int) {
if node == nil {
return
}

fmt.Printf("%*s%s %s\n", level, "", node.TypeString(), node.Value)
for _, child := range node.Children {
PrintTree(child, level+1)
}
}

func (n *Node) TypeString() string {
return rul3s[n.Type]
}

func PopulateAST(expression string, parent *Node, ast *node32) {
for {
if ast == nil {
break
}

child := &Node{}
child.Type = ast.pegRule
child.Value = expression[ast.begin:ast.end]

// Remove when printing
// child.Parent = parent

parent.Children = append(parent.Children, child)

PopulateAST(expression, child, ast.up)

ast = ast.next
}
}
81 changes: 81 additions & 0 deletions models/postgrest/postgrest.go
@@ -0,0 +1,81 @@
package postgrest

import "strconv"

type Postgrest struct {
Filters []*Filter
Limit int
Offset int
Order []*Order
// LogicalQuery
}

func (p *Postgrest) FromAST(node *Node) {
switch node.Type {
case ruleFilter:
filter := &Filter{}
filter.FromAST(node)
p.Filters = append(p.Filters, filter)
default:
for _, child := range node.Children {
p.FromAST(child)
}
}
}

type Order struct {
Column string
Direction string
NullDirection string
}

type Filter struct {
Field string
Not string
Operator string
Operands []string
AnyAll string
}

func (filter *Filter) FromAST(n *Node) {

for _, child := range n.Children {

if child.Type == ruleColumnName {
filter.Field = child.Value
}

if child.Type == rulePredicate {
for _, pred := range child.Children {
if pred.Type == ruleNot {
filter.Not = "NOT"
}
if pred.Type == ruleOperator {
filter.Operator = pred.Value
}

if pred.Type == ruleOperand {
if pred.Children[0].Type == ruleVectorOperand {
for _, operand := range pred.Children[0].Children {
unquoted, err := strconv.Unquote(operand.Value)
if err == nil {
filter.Operands = append(filter.Operands, unquoted)
} else {
filter.Operands = append(filter.Operands, operand.Value)
}
}

} else if pred.Children[0].Type == ruleScalarOperand {
unquoted, err := strconv.Unquote(pred.Children[0].Value)
if err == nil {
filter.Operands = append(filter.Operands, unquoted)
} else {
filter.Operands = append(filter.Operands, pred.Children[0].Value)
}
}

}
}
}
}
}
81 changes: 81 additions & 0 deletions models/postgrest/postgrest.peg
@@ -0,0 +1,81 @@
package postgrest

type PostgrestParser Peg {}

QueryString <- QueryParam? ('&' QueryParam)* END

QueryParam <- Limit / Offset / Order / Select / LogicalQuery / Filter

LogicalQuery <- LogicalOperator '=' FilterList
FilterList <- '(' FilterExpression (',' FilterExpression)* ')'
FilterExpression <- LogicalFilter / ColumnName '.' Predicate
LogicalFilter <- LogicalOperator FilterList
LogicalOperator <- Not? BinaryOperator
BinaryOperator <- 'and' / 'or'

Select <- 'select' '=' SelectOptions
SelectOptions <- SelectOption (',' SelectOption)*
SelectOption <- Renamed ':' (SelectCount / SelectColumn) '::' Cast
/ Renamed ':' (SelectCount / SelectColumn)
/ (SelectCount / SelectColumn) '::' Cast
/ (SelectCount / SelectColumn)

SelectColumn <- ColumnName ('::' Cast)? '.' Aggregation '(' AggregationOption? ')'
/ ColumnName

Aggregation <- [^=&,.:()]+
AggregationOption <- [^=&,.:()]+

SelectCount <- 'count()'
Renamed <- ColumnName
Cast <- [^=&,.:]+

Limit <- 'limit' '=' Integer
Offset <- 'offset' '=' Integer
Order <- 'order' '=' OrderOptions
Filter <- ColumnName '=' Predicate

# Order by clause is a list of columns, along with an optional direction (asc, desc)
# and an optional treatment of nulls (nullsfirst, nullslast)
OrderOptions <- OrderOption (',' OrderOption)*

OrderOption <- ColumnName '.' OrderDirection '.' NullDirection
/ ColumnName '.' (OrderDirection / NullDirection)
/ ColumnName

ColumnName <- QuotedString / [^=&,.:()]+
OrderDirection <- 'asc' / 'desc'
NullDirection <- 'nullsfirst' / 'nullslast'

# A filter on a column. Has many components:
# Starts with an optional "not" for negation
# Then an operator by an operator (eq, neq, ...)
# Optional (any) or (all)
# Then an operand: either a scalar, a vector (1,2,3), or a list {1,2,3}
Predicate <- Not? ((Operator '.' Operand) / (Operator '(' AnyAll ')' '.' ListOperand))

Not <- 'not.'

Operator <- [a-zA-Z]+

AnyAll <- 'any' / 'all'

# An operand can either be a list of values (1,2,3) or a scalar
Operand <- VectorOperand / ScalarOperand

ListOperand <- '{' ListOperandItem (',' ListOperandItem)* '}'
ListOperandItem <- QuotedString / [^,}&=]+

VectorOperand <- '(' VectorOperandItem (',' VectorOperandItem)* ')'

# A list item can either be a quoted string with escape characters, or unquoted
VectorOperandItem <- QuotedString / [^,)&=]+

QuotedString <- '"' (EscapedChar / [^"&=] )* '"'
EscapedChar <- '\\' .

ScalarOperand <- [^&=,(){}]+

Integer <- [0-9]+

END <- !.