From a4703542e9e5f83963ae8aa1e96425ce8a71feb6 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Tue, 2 Jul 2019 08:30:27 -0700 Subject: [PATCH 1/2] validate number of args built-in funcs --- checks.go | 43 ++++++++++++++++++++++++ checks_test.go | 8 +++++ parser.go | 3 ++ postgres/funcs.go | 48 ++++++++++++++++++++++++++ soup.go | 86 ++++++++++++++++++++++++++++++++++++----------- 5 files changed, 168 insertions(+), 20 deletions(-) create mode 100644 postgres/funcs.go diff --git a/checks.go b/checks.go index 9fbdc53769..498a565cf9 100644 --- a/checks.go +++ b/checks.go @@ -2,13 +2,16 @@ package dinosql import ( "fmt" + "strings" + "github.com/kyleconroy/dinosql/postgres" nodes "github.com/lfittl/pg_query_go/nodes" ) type Error struct { Message string Code string + Hint string } func (e Error) Error() string { @@ -42,3 +45,43 @@ func validateParamRef(n nodes.Node) error { return nil } + +type funcCallVisitor struct { + err error +} + +func (v *funcCallVisitor) Visit(node nodes.Node) Visitor { + if v.err != nil { + return nil + } + + funcCall, ok := node.(nodes.FuncCall) + if !ok { + return v + } + + name := join(funcCall.Funcname, "") + args := len(funcCall.Args.Items) + if _, ok := postgres.Functions[name][args]; ok { + return v + } + + var sig []string + for _, _ = range funcCall.Args.Items { + sig = append(sig, "unknown") + } + + v.err = Error{ + Code: "42883", + Message: fmt.Sprintf("function %s(%s) does not exist", name, strings.Join(sig, ", ")), + Hint: "No function matches the given name and argument types. You might need to add explicit type casts.", + } + + return nil +} + +func validateFuncCall(n nodes.Node) error { + visitor := funcCallVisitor{} + Walk(&visitor, n) + return visitor.err +} diff --git a/checks_test.go b/checks_test.go index 56ce0159b2..55fad6a7a7 100644 --- a/checks_test.go +++ b/checks_test.go @@ -36,6 +36,14 @@ func TestParserErrors(t *testing.T) { `, Error{Code: "42703", Message: "column \"foo\" does not exist"}, }, + { + "SELECT random(1);", + Error{ + Code: "42883", + Message: "function random(unknown) does not exist", + Hint: "No function matches the given name and argument types. You might need to add explicit type casts.", + }, + }, } { test := tc t.Run(test.query, func(t *testing.T) { diff --git a/parser.go b/parser.go index 82879b77c6..f457a890b7 100644 --- a/parser.go +++ b/parser.go @@ -69,6 +69,9 @@ func ParseSchmea(dir string) (*postgres.Schema, error) { func parse(s *postgres.Schema, tree pg.ParsetreeList) error { for _, stmt := range tree.Statements { + if err := validateFuncCall(stmt); err != nil { + return err + } raw, ok := stmt.(nodes.RawStmt) if !ok { continue diff --git a/postgres/funcs.go b/postgres/funcs.go new file mode 100644 index 0000000000..e1f5befd48 --- /dev/null +++ b/postgres/funcs.go @@ -0,0 +1,48 @@ +package postgres + +func args(a ...int) map[int]struct{} { + m := map[int]struct{}{} + for _, arg := range a { + m[arg] = struct{}{} + } + return m +} + +var Functions = map[string]map[int]struct{}{ + + // https://www.postgresql.org/docs/current/functions-math.html + "abs": args(1), // (x) (same as input) absolute value abs(-17.4) 17.4 + "cbrt": args(1), // (dp) dp cube root cbrt(27.0) 3 + "ceil": args(1), // (dp or numeric) (same as input) nearest integer greater than or equal to argument ceil(-42.8) -42 + "ceiling": args(1), // (dp or numeric) (same as input) nearest integer greater than or equal to argument (same as ceil) ceiling(-95.3) -95 + "degrees": args(1), // (dp) dp radians to degrees degrees(0.5) 28.6478897565412 + "div": args(2), // (y numeric, x numeric) numeric integer quotient of y/x div(9,4) 2 + "exp": args(1), // (dp or numeric) (same as input) exponential exp(1.0) 2.71828182845905 + "floor": args(1), // (dp or numeric) (same as input) nearest integer less than or equal to argument floor(-42.8) -43 + "ln": args(1), // (dp or numeric) (same as input) natural logarithm ln(2.0) 0.693147180559945 + "log": args(1, 2), + // (dp or numeric) (same as input) base 10 logarithm log(100.0) 2 + // (b numeric, x numeric) numeric logarithm to base b log(2.0, 64.0) 6.0000000000 + "mod": args(2), // (y, x) (same as argument types) remainder of y/x mod(9,4) 1 + "pi": args(0), // () dp “π” constant pi() 3.14159265358979 + "power": args(2), + // (a dp, b dp) dp a raised to the power of b power(9.0, 3.0) 729 + // power(a numeric, b numeric) numeric a raised to the power of b power(9.0, 3.0) 729 + "radians": args(1), // (dp) dp degrees to radians radians(45.0) 0.785398163397448 + "round": args(1, 2), // (dp or numeric) (same as input) round to nearest integer round(42.4) 42 + // round(v numeric, s int) numeric round to s decimal places round(42.4382, 2) 42.44 + "scale": args(1), // (numeric) integer scale of the argument (the number of decimal digits in the fractional part) scale(8.41) 2 + "sign": args(1), // (dp or numeric) (same as input) sign of the argument (-1, 0, +1) sign(-8.4) -1 + "sqrt": args(1), // (dp or numeric) (same as input) square root sqrt(2.0) 1.4142135623731 + "trunc": args(1, 2), // (dp or numeric) (same as input) truncate toward zero trunc(42.8) 42 + // trunc(v numeric, s int) numeric truncate to s decimal places trunc(42.4382, 2) 42.43 + "width_bucket": args(2, 4), // (operand dp, b1 dp, b2 dp, count int) int return the bucket number to which operand would be assigned in a histogram having count equal-width buckets spanning the range b1 to b2; returns 0 or count+1 for an input outside the range width_bucket(5.35, 0.024, 10.06, 5) 3 + // width_bucket(operand numeric, b1 numeric, b2 numeric, count int) int return the bucket number to which operand would be assigned in a histogram having count equal-width buckets spanning the range b1 to b2; returns 0 or count+1 for an input outside the range width_bucket(5.35, 0.024, 10.06, 5) 3 + // width_bucket(operand anyelement, thresholds anyarray) int return the bucket number to which operand would be assigned given an array listing the lower bounds of the buckets; returns 0 for an input less than the first lower bound; the thresholds array must be sorted, smallest first, or unexpected results will be obtained width_bucket(now(), array['yesterday', 'today', 'tomorrow']::timestamptz[]) 2 + + // https://www.postgresql.org/docs/current/functions-datetime.html + "now": args(0), + + // https://www.postgresql.org/docs/current/functions-aggregate.html + "count": args(0, 1), +} diff --git a/soup.go b/soup.go index fb649476e2..e0996433cf 100644 --- a/soup.go +++ b/soup.go @@ -158,11 +158,15 @@ func Walk(f Visitor, node nodes.Node) { walkn(f, n.Tables) case nodes.AlterRoleSetStmt: - walkn(f, n.Role) + if n.Role != nil { + walkn(f, *n.Role) + } walkn(f, n.Setstmt) case nodes.AlterRoleStmt: - walkn(f, n.Role) + if n.Role != nil { + walkn(f, *n.Role) + } walkn(f, n.Options) case nodes.AlterSeqStmt: @@ -188,7 +192,9 @@ func Walk(f Visitor, node nodes.Node) { walkn(f, n.Options) case nodes.AlterTableCmd: - walkn(f, n.Newowner) + if n.Newowner != nil { + walkn(f, *n.Newowner) + } walkn(f, n.Def) case nodes.AlterTableMoveAllStmt: @@ -204,7 +210,9 @@ func Walk(f Visitor, node nodes.Node) { walkn(f, n.Cmds) case nodes.AlterUserMappingStmt: - walkn(f, n.User) + if n.User != nil { + walkn(f, *n.User) + } walkn(f, n.Options) case nodes.AlternativeSubPlan: @@ -447,7 +455,9 @@ func Walk(f Visitor, node nodes.Node) { walkn(f, n.Options) case nodes.CreateSchemaStmt: - walkn(f, n.Authrole) + if n.Authrole != nil { + walkn(f, *n.Authrole) + } walkn(f, n.SchemaElts) case nodes.CreateSeqStmt: @@ -488,6 +498,12 @@ func Walk(f Visitor, node nodes.Node) { walkn(f, n.Query) walkn(f, n.Into) + case nodes.CreateTableSpaceStmt: + if n.Owner != nil { + walkn(f, *n.Owner) + } + walkn(f, n.Options) + case nodes.CreateTransformStmt: if n.TypeName != nil { walkn(f, *n.TypeName) @@ -513,7 +529,9 @@ func Walk(f Visitor, node nodes.Node) { } case nodes.CreateUserMappingStmt: - walkn(f, n.User) + if n.User != nil { + walkn(f, *n.User) + } walkn(f, n.Options) case nodes.CreatedbStmt: @@ -569,7 +587,9 @@ func Walk(f Visitor, node nodes.Node) { // pass case nodes.DropUserMappingStmt: - walkn(f, n.User) + if n.User != nil { + walkn(f, *n.User) + } case nodes.DropdbStmt: // pass @@ -626,7 +646,9 @@ func Walk(f Visitor, node nodes.Node) { case nodes.GrantRoleStmt: walkn(f, n.GrantedRoles) walkn(f, n.GranteeRoles) - walkn(f, n.Grantor) + if n.Grantor != nil { + walkn(f, *n.Grantor) + } case nodes.GrantStmt: walkn(f, n.Objects) @@ -652,7 +674,9 @@ func Walk(f Visitor, node nodes.Node) { walkn(f, n.Opclass) case nodes.IndexStmt: - walkn(f, n.Relation) + if n.Relation != nil { + walkn(f, *n.Relation) + } walkn(f, n.IndexParams) walkn(f, n.Options) walkn(f, n.WhereClause) @@ -686,7 +710,9 @@ func Walk(f Visitor, node nodes.Node) { // pass case nodes.IntoClause: - walkn(f, n.Rel) + if n.Rel != nil { + walkn(f, *n.Rel) + } walkn(f, n.ColNames) walkn(f, n.Options) walkn(f, n.ViewQuery) @@ -782,8 +808,12 @@ func Walk(f Visitor, node nodes.Node) { walkn(f, n.Upperdatums) case nodes.PartitionCmd: - walkn(f, n.Name) - walkn(f, n.Bound) + if n.Name != nil { + walkn(f, *n.Name) + } + if n.Bound != nil { + walkn(f, *n.Bound) + } case nodes.PartitionElem: walkn(f, n.Expr) @@ -880,20 +910,28 @@ func Walk(f Visitor, node nodes.Node) { case nodes.ReassignOwnedStmt: walkn(f, n.Roles) - walkn(f, n.Newrole) + if n.Newrole != nil { + walkn(f, *n.Newrole) + } case nodes.RefreshMatViewStmt: - walkn(f, n.Relation) + if n.Relation != nil { + walkn(f, *n.Relation) + } case nodes.ReindexStmt: - walkn(f, n.Relation) + if n.Relation != nil { + walkn(f, *n.Relation) + } case nodes.RelabelType: walkn(f, n.Xpr) walkn(f, n.Arg) case nodes.RenameStmt: - walkn(f, n.Relation) + if n.Relation != nil { + walkn(f, *n.Relation) + } walkn(f, n.Object) case nodes.ReplicaIdentityStmt: @@ -923,7 +961,9 @@ func Walk(f Visitor, node nodes.Node) { // pass case nodes.RuleStmt: - walkn(f, n.Relation) + if n.Relation != nil { + walkn(f, *n.Relation) + } walkn(f, n.WhereClause) walkn(f, n.Actions) @@ -1015,7 +1055,9 @@ func Walk(f Visitor, node nodes.Node) { walkn(f, n.Coldefexprs) case nodes.TableLikeClause: - walkn(f, n.Relation) + if n.Relation != nil { + walkn(f, *n.Relation) + } case nodes.TableSampleClause: walkn(f, n.Args) @@ -1061,7 +1103,9 @@ func Walk(f Visitor, node nodes.Node) { } case nodes.VacuumStmt: - walkn(f, n.Relation) + if n.Relation != nil { + walkn(f, *n.Relation) + } walkn(f, n.VaCols) case nodes.Var: @@ -1074,7 +1118,9 @@ func Walk(f Visitor, node nodes.Node) { // pass case nodes.ViewStmt: - walkn(f, n.View) + if n.View != nil { + walkn(f, *n.View) + } walkn(f, n.Aliases) walkn(f, n.Query) walkn(f, n.Options) From d263432bf43a2dfe1659c3a9ceccd48139a35456 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Wed, 3 Jul 2019 13:47:13 -0700 Subject: [PATCH 2/2] Validate more functions --- checks.go | 7 +- checks_test.go | 18 ++++ parser.go | 8 +- postgres/funcs.go | 204 ++++++++++++++++++++++++++++++++++------ testdata/funcs/math.sql | 24 +++++ 5 files changed, 230 insertions(+), 31 deletions(-) create mode 100644 testdata/funcs/math.sql diff --git a/checks.go b/checks.go index 498a565cf9..8398500d54 100644 --- a/checks.go +++ b/checks.go @@ -60,7 +60,12 @@ func (v *funcCallVisitor) Visit(node nodes.Node) Visitor { return v } - name := join(funcCall.Funcname, "") + // Do not validate unknown functions + name := join(funcCall.Funcname, ".") + if _, ok := postgres.Functions[name]; !ok { + return v + } + args := len(funcCall.Args.Items) if _, ok := postgres.Functions[name][args]; ok { return v diff --git a/checks_test.go b/checks_test.go index 55fad6a7a7..57a4df8c58 100644 --- a/checks_test.go +++ b/checks_test.go @@ -1,11 +1,21 @@ package dinosql import ( + "path/filepath" "testing" "github.com/google/go-cmp/cmp" + "github.com/kyleconroy/dinosql/postgres" ) +func TestFuncs(t *testing.T) { + _, err := ParseQueries(&postgres.Schema{}, filepath.Join("testdata", "funcs")) + if err != nil { + t.Fatal(err) + } + +} + func TestParserErrors(t *testing.T) { for _, tc := range []struct { query string @@ -44,6 +54,14 @@ func TestParserErrors(t *testing.T) { Hint: "No function matches the given name and argument types. You might need to add explicit type casts.", }, }, + { + "SELECT position()", + Error{ + Code: "42883", + Message: "function pg_catalog.position() does not exist", + Hint: "No function matches the given name and argument types. You might need to add explicit type casts.", + }, + }, } { test := tc t.Run(test.query, func(t *testing.T) { diff --git a/parser.go b/parser.go index f457a890b7..89b823a793 100644 --- a/parser.go +++ b/parser.go @@ -302,7 +302,9 @@ func ParseQueries(s *postgres.Schema, dir string) (*Result, error) { if err != nil { return nil, err } - parseFuncs(s, &r, string(blob), tree) + if err := parseFuncs(s, &r, string(blob), tree); err != nil { + return nil, err + } q = append(q, r.Queries...) } return &Result{Schema: s, Queries: q}, nil @@ -361,6 +363,10 @@ func parseFuncs(s *postgres.Schema, r *Result, source string, tree pg.ParsetreeL continue } + if err := validateFuncCall(raw); err != nil { + return err + } + rvs := rangeVars(raw.Stmt) t := tableName(raw.Stmt) c := columnNames(s, t) diff --git a/postgres/funcs.go b/postgres/funcs.go index e1f5befd48..278309f17d 100644 --- a/postgres/funcs.go +++ b/postgres/funcs.go @@ -11,37 +11,183 @@ func args(a ...int) map[int]struct{} { var Functions = map[string]map[int]struct{}{ // https://www.postgresql.org/docs/current/functions-math.html - "abs": args(1), // (x) (same as input) absolute value abs(-17.4) 17.4 - "cbrt": args(1), // (dp) dp cube root cbrt(27.0) 3 - "ceil": args(1), // (dp or numeric) (same as input) nearest integer greater than or equal to argument ceil(-42.8) -42 - "ceiling": args(1), // (dp or numeric) (same as input) nearest integer greater than or equal to argument (same as ceil) ceiling(-95.3) -95 - "degrees": args(1), // (dp) dp radians to degrees degrees(0.5) 28.6478897565412 - "div": args(2), // (y numeric, x numeric) numeric integer quotient of y/x div(9,4) 2 - "exp": args(1), // (dp or numeric) (same as input) exponential exp(1.0) 2.71828182845905 - "floor": args(1), // (dp or numeric) (same as input) nearest integer less than or equal to argument floor(-42.8) -43 - "ln": args(1), // (dp or numeric) (same as input) natural logarithm ln(2.0) 0.693147180559945 - "log": args(1, 2), - // (dp or numeric) (same as input) base 10 logarithm log(100.0) 2 - // (b numeric, x numeric) numeric logarithm to base b log(2.0, 64.0) 6.0000000000 - "mod": args(2), // (y, x) (same as argument types) remainder of y/x mod(9,4) 1 - "pi": args(0), // () dp “π” constant pi() 3.14159265358979 - "power": args(2), - // (a dp, b dp) dp a raised to the power of b power(9.0, 3.0) 729 - // power(a numeric, b numeric) numeric a raised to the power of b power(9.0, 3.0) 729 - "radians": args(1), // (dp) dp degrees to radians radians(45.0) 0.785398163397448 - "round": args(1, 2), // (dp or numeric) (same as input) round to nearest integer round(42.4) 42 - // round(v numeric, s int) numeric round to s decimal places round(42.4382, 2) 42.44 - "scale": args(1), // (numeric) integer scale of the argument (the number of decimal digits in the fractional part) scale(8.41) 2 - "sign": args(1), // (dp or numeric) (same as input) sign of the argument (-1, 0, +1) sign(-8.4) -1 - "sqrt": args(1), // (dp or numeric) (same as input) square root sqrt(2.0) 1.4142135623731 - "trunc": args(1, 2), // (dp or numeric) (same as input) truncate toward zero trunc(42.8) 42 - // trunc(v numeric, s int) numeric truncate to s decimal places trunc(42.4382, 2) 42.43 - "width_bucket": args(2, 4), // (operand dp, b1 dp, b2 dp, count int) int return the bucket number to which operand would be assigned in a histogram having count equal-width buckets spanning the range b1 to b2; returns 0 or count+1 for an input outside the range width_bucket(5.35, 0.024, 10.06, 5) 3 - // width_bucket(operand numeric, b1 numeric, b2 numeric, count int) int return the bucket number to which operand would be assigned in a histogram having count equal-width buckets spanning the range b1 to b2; returns 0 or count+1 for an input outside the range width_bucket(5.35, 0.024, 10.06, 5) 3 - // width_bucket(operand anyelement, thresholds anyarray) int return the bucket number to which operand would be assigned given an array listing the lower bounds of the buckets; returns 0 for an input less than the first lower bound; the thresholds array must be sorted, smallest first, or unexpected results will be obtained width_bucket(now(), array['yesterday', 'today', 'tomorrow']::timestamptz[]) 2 + // Table 9.5. Mathematical Functions + "abs": args(1), + "cbrt": args(1), + "ceil": args(1), + "ceiling": args(1), + "degrees": args(1), + "div": args(2), + "exp": args(1), + "floor": args(1), + "ln": args(1), + "log": args(1, 2), + "mod": args(2), + "pi": args(0), + "power": args(2), + "radians": args(1), + "round": args(1, 2), + "scale": args(1), + "sign": args(1), + "sqrt": args(1), + "trunc": args(1, 2), + "width_bucket": args(2, 4), + + // Table 9.6. Random Functions + "random": args(0), + "setseed": args(1), + + // Table 9.7. Trigonometric Functions + "acos": args(1), + "acosd": args(1), + "asin": args(1), + "asind": args(1), + "atan": args(1), + "atan2": args(2), + "atan2d": args(2), + "atand": args(1), + "cos": args(1), + "cosd": args(1), + "cot": args(1), + "cotd": args(1), + "sin": args(1), + "sind": args(1), + "tan": args(1), + "tand": args(1), + + // https://www.postgresql.org/docs/current/functions-string.html + // Table 9.8. SQL String Functions and Operators + "bit_length": args(1), + "char_length": args(1), + "character_length": args(1), + "lower": args(1), + "octet_length": args(1), + "overlay": args(3, 4), + "pg_catalog.position": args(2), + "substring": args(1, 2, 3), + "trim": args(2, 3), + "upper": args(1), + + // Table 9.9. Other String Functions + "ascii": args(1), + "btrim": args(1, 2), + "chr": args(1), + "convert": args(3), + "convert_from": args(2), + "convert_to": args(2), + "decode": args(2), + "encode": args(2), + "initcap": args(1), + "left": args(2), + "length": args(1, 2), + "lpad": args(2, 3), + "ltrim": args(1, 2), + "md5": args(1), + "parse_ident": args(1, 2), + "pg_client_encoding": args(0), + "quote_ident": args(1), + "quote_literal": args(1), + "quote_nullable": args(1), + "regexp_match": args(2, 3), + "regexp_matches": args(2, 3), + "regexp_replace": args(3, 4), + "regexp_split_to_array": args(2, 3), + "regexp_split_to_table": args(2, 3), + "repeat": args(2), + "replace": args(3), + "reverse": args(1), + "right": args(2), + "rpad": args(2, 3), + "rtrim": args(1, 2), + "split_part": args(3), + "strpos": args(2), + "substr": args(2, 3), + "starts_with": args(2), + "to_ascii": args(1, 2), + "to_hex": args(1), + "translate": args(3), + + // https://www.postgresql.org/docs/current/functions-binarystring.html + // Table 9.12. Other Binary String Functions + "get_bit": args(2), + "get_byte": args(2), + "set_bit": args(3), + "set_byte": args(3), + "sha224": args(1), + "sha256": args(1), + "sha384": args(1), + "sha512": args(1), + + // https://www.postgresql.org/docs/current/functions-formatting.html + // Table 9.23. Formatting Functions + "to_char": args(2), + "to_date": args(2), + "to_number": args(2), + "to_timestamp": args(1, 2), // https://www.postgresql.org/docs/current/functions-datetime.html - "now": args(0), + "age": args(1, 2), + "clock_timestamp": args(0), + "date_part": args(2), + "date_trunc": args(2), + "extract": args(2), + "isfinite": args(1), + "justify_days": args(1), + "justify_hours": args(1), + "justify_interval": args(1), + "make_date": args(3), + "make_time": args(3), + "make_timestamp": args(6), + "make_timestampz": args(6), + "now": args(0), + "statement_timestamp": args(0), + "timeofday": args(0), + "transaction_timestamp": args(0), + + // https://www.postgresql.org/docs/current/functions-enum.html + // Table 9.32. Enum Support Functions + "enum_first": args(1), + "enum_last": args(1), + "enum_range": args(1, 2), + + // https://www.postgresql.org/docs/current/functions-geometry.html + // Table 9.34. Geometric Functions + "area": args(1), + "center": args(1), + "diameter": args(1), + "height": args(1), + "isclosed": args(1), + "isopen": args(1), + "npoints": args(1), + "pclose": args(1), + "popen": args(1), + "radius": args(1), + "width": args(1), + + // Table 9.35. Geometric Type Conversion Functions + "box": args(1, 2), + "bound_box": args(2), + "circle": args(1, 2), + "line": args(2), + "lseg": args(1, 2), + "path": args(1), + "point": args(1, 2), + "polygon": args(1, 2), + + // https://www.postgresql.org/docs/current/functions-net.html + // Table 9.37. cidr and inet Functions + "abbrev": args(1), + "broadcast": args(1), + "family": args(1), + "host": args(1), + "hostmask": args(1), + "masklen": args(1), + "netmask": args(1), + "network": args(1), + "set_masklen": args(1), + "text": args(1), + "inet_same_family": args(1), + "inet_merge": args(1), // https://www.postgresql.org/docs/current/functions-aggregate.html "count": args(0, 1), diff --git a/testdata/funcs/math.sql b/testdata/funcs/math.sql new file mode 100644 index 0000000000..e760f49bed --- /dev/null +++ b/testdata/funcs/math.sql @@ -0,0 +1,24 @@ +SELECT abs(-17.4); +SELECT cbrt(27.0); +SELECT ceil(-42.8); +SELECT ceiling(-95.3); +SELECT degrees(0.5); +SELECT div(9,4); +SELECT exp(1.0); +SELECT floor(-42.8); +SELECT ln(2.0); +SELECT log(100.0); +SELECT log(2.0, 64.0); +SELECT mod(9,4); +SELECT pi(); +SELECT power(9.0, 3.0); +SELECT radians(45.0); +SELECT round(42.4); +SELECT round(42.4382, 2); +SELECT scale(8.41); +SELECT sign(-8.4); +SELECT sqrt(2.0); +SELECT trunc(42.8); +SELECT trunc(42.4382, 2); +SELECT width_bucket(5.35, 0.024, 10.06, 5); +SELECT width_bucket(now(), array['yesterday', 'today', 'tomorrow']::timestamptz[]);