Last Updated: February 25, 2016
·
2.965K
· haskellcamargo

A simple PEG.js grammar to validate SQL selects

Validating simple SELECT statements with a parsing expression grammar with precedence of logical operations and easy to translate and prevent security holes.

/**
 * Grammar for validating simple SQL selects.
 * @author Marcelo Camargo
 * @since 2015/08/26
 */

{
  var Sql = {
    listToString: function(x, xs) {
      return [x].concat(xs).join("");
    }
  };
}

Start
  = Stmt

Stmt
  = SelectStmt

/* Statements */

SelectStmt
  = _ SelectToken
    _ x:SelectField xs:SelectFieldRest*
    _ FromToken
    _ from:Identifier
    _ where:WhereExpr? {
    return {
      type: "SELECT",
      fields: [x].concat(xs),
      from: from,
      where: where
    };
  }

SelectField "select valid field"
  = Identifier
  / "*"

SelectFieldRest
  = _ SeparatorToken _ s:SelectField {
    return s;
  }

WhereExpr "where expression"
  = WhereToken x:LogicExpr xs:LogicExprRest* {
    return {
      conditions: [x].concat(xs)
    };
  }

LogicExpr
  = _ "(" _ x:LogicExpr xs:LogicExprRest* _ ")" _ {
    return [x].concat(xs);
  }
  / _ left:Expr _ op:Operator _ right:Expr _ {
    return {
      left: left,
      op: op,
      right: right
    };
  }

LogicExprRest
  = _ j:Joiner _ l:LogicExpr {
    return {
      joiner: j,
      expression: l
    };
  }

Joiner "joiner"
  = OrToken  { return "Or";  }
  / AndToken { return "And"; }

Operator
  = "<>"       { return "Different"; }
  / "="        { return "Equal";     }
  / LikeToken  { return "Like";      }

/* Expressions */

Expr
  = Float
  / Integer
  / Identifier
  / String

Integer "integer"
  = n:[0-9]+ {
    return parseInt(n.join(""));
  }

Float "float"
  = left:Integer "." right:Integer {
    return parseFloat([
      left.toString(),
      right.toString()
    ].join("."));
  }

String "string"
  = "'" str:ValidStringChar* "'" {
    return str.join("");
  }

ValidStringChar
  = !"'" c:. {
    return c;
  }

/* Tokens */

SelectToken
  = "SELECT"i !IdentRest

SeparatorToken
  = ","

FromToken
  = "FROM"i !IdentRest

WhereToken
  = "WHERE"i !IdentRest

LikeToken
  = "LIKE"i !IdentRest

OrToken
  = "OR"i !IdentRest

AndToken
  = "AND"i !IdentRest

/* Identifier */

Identifier "identifier"
  = x:IdentStart xs:IdentRest* {
    return Sql.listToString(x, xs);
  }

IdentStart
  = [a-z_]i

IdentRest
  = [a-z0-9_]i

/* Skip */
_
  = ( WhiteSpace / NewLine )*

NewLine "newline"
  = "\r\n"
  / "\r"
  / "\n"
  / "\u2028"
  / "\u2029"

WhiteSpace "whitespace"
  = " "
  / "\t"
  / "\v"
  / "\f"