Skip to content

Commit

Permalink
tools for text encoding and SQL array encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
mitranim committed Nov 7, 2021
1 parent 5c6904a commit 5ed11d4
Show file tree
Hide file tree
Showing 13 changed files with 536 additions and 77 deletions.
41 changes: 30 additions & 11 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,26 @@ func Example_composition() {

## Changelog

### `v0.3.0`
### v0.4.0

Added low-level tools for text encoding and SQL array encoding:

* `ArrayAppender`
* `CommaAppender`
* `String`
* `TryString`
* `Append`
* `TryAppend`
* `TryAppendWith`
* `AppendWith`
* `AppenderString`

Breaking changes:

* Removed useless expression type `Int`.
* Renamed `Bui.TryExprs` to `Bui.CatchExprs`.

### v0.3.0

Revised AST-style expressions:

Expand All @@ -111,45 +130,45 @@ Revised AST-style expressions:
* `Update`
* `Delete`

### `v0.2.1`
### v0.2.1

Added `Sparse` and `Partial` to support "sparse" structs, allowing to implement HTTP PATCH semantics more easily, efficiently, and correctly.

### `v0.2.0`
### v0.2.0

Full API revision. Added many AST/DSL-like types for common expressions. Optimized parsing and expression building. Use caching and pooling to minimize redundant work. String-based query building now uses partial parsing with caching, and should no longer be a measurable expense. Ported JEL support from `github.com/mitranim/jel`.

### `v0.1.17`
### v0.1.17

Added `Ords.OrType`.

### `v0.1.16`
### v0.1.16

Added `NamedArg.Norm`. Improved `NamedArg.IsNil` and `NamedArgs.Conditions`. They now use the `driver.Valuer.Value` method, if present, to determine null-ness, which works for non-pointer "nullable" types.

### `v0.1.15`
### v0.1.15

`Ords.Or` is now a value method that returns a modified version, rather than a pointer method that mutated the original.

### `v0.1.14`
### v0.1.14

`StructMap` and `StructNamedArgs` now tolerate `nil` inputs. Previously, they tolerated non-nil interfaces where the underlying value was a nil struct pointer. Now they also allow nil interfaces.

### `v0.1.13`
### v0.1.13

Fixed the bug where the `Ords.Lax` mode was appending malformed ords, instead of skipping them entirely.

### `v0.1.12`
### v0.1.12

* `StrQuery` now interpolates directly, without invoking `(*Query).Append` on the provided query. This allows to interpolate `StrQuery` strings that contain parameter placeholders. Use at your own risk.

* `(*Query).Append` no longer has an argument length limit.

### `v0.1.11`
### v0.1.11

Added `Ords.Lax`: a boolean that causes `Ords` to skip unknown fields during parsing.

### `v0.1.10`
### v0.1.10

Breaking changes in the name of efficiency:

Expand Down
2 changes: 1 addition & 1 deletion sqlb.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ to the parameters in the SQL text. Different databases support different styles
of ordinal parameters. This package always generates Postgres-style ordinal
parameters such as "$1", renumerating them as necessary.
This method is allowed to panic. Use `(*Bui).TryExprs` to catch
This method is allowed to panic. Use `(*Bui).CatchExprs` to catch
expression-encoding panics and convert them to errors.
All `Expr` types in this package also implement `Appender` and `fmt.Stringer`.
Expand Down
59 changes: 59 additions & 0 deletions sqlb_array.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package sqlb

import "database/sql/driver"

/*
Intermediary tool for implementing SQL array encoding. Has the same behavior as
`CommaAppender`, but the text output is always enclosed in `{}`.
*/
type ArrayAppender []Appender

/*
Implement `fmt.Stringer`. Same as `CommaAppender.String`, but the output is
always enclosed in `{}`.
*/
func (self ArrayAppender) String() string { return AppenderString(&self) }

/*
Implement `Appender`. Same as `CommaAppender.Append`, but the output is always
enclosed in `{}`.
*/
func (self ArrayAppender) Append(buf []byte) []byte {
buf = append(buf, `{`...)
buf = CommaAppender(self).Append(buf)
buf = append(buf, `}`...)
return buf
}

func (self ArrayAppender) Get() interface{} { return self.String() }

func (self ArrayAppender) Value() (driver.Value, error) { return self.Get(), nil }

/*
Intermediary tool for implementing SQL array encoding. Combines multiple
arbitrary text encoders. On demand (on a call to `.Append` or `.String`),
combines their text representations, separating them with a comma, while
skipping any empty representations. The output will never contain a dangling
leading comma, double comma, or leading trailing comma, unless they were
explicitly generated by the inner encoders.
*/
type CommaAppender []Appender

// Implement `fmt.Stringer` by calling `.Append`.
func (self CommaAppender) String() string { return AppenderString(&self) }

/*
Implement `Appender`. Appends comma-separated text representations of the inner
encoders to the output buffer, skipping any empty representations.
*/
func (self CommaAppender) Append(buf []byte) []byte {
var found bool

for _, val := range self {
if (found && TryAppendWith(&buf, `,`, val)) || TryAppendWith(&buf, ``, val) {
found = true
}
}

return buf
}
2 changes: 1 addition & 1 deletion sqlb_bui.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func (self *Bui) Exprs(vals ...Expr) {
// Same as `(*Bui).Exprs` but catches panics. Since many functions in this
// package use panics, this should be used for final reification by apps that
// insist on errors-as-values.
func (self *Bui) TryExprs(vals ...Expr) (err error) {
func (self *Bui) CatchExprs(vals ...Expr) (err error) {
defer rec(&err)
self.Exprs(vals...)
return
Expand Down
8 changes: 8 additions & 0 deletions sqlb_err.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package sqlb

import (
"fmt"
r "reflect"
)

// All errors generated by this package have this type, usually wrapped into a
Expand Down Expand Up @@ -190,6 +191,13 @@ func errUnknownField(while, jsonPath, typeName string) ErrUnknownField {
}}
}

func errUnsupportedType(while string, typ r.Type) ErrInvalidInput {
return ErrInvalidInput{Err{
while,
fmt.Errorf(`unsupported type %q of kind %q`, typ, typ.Kind()),
}}
}

var errEmptyAssign = error(ErrEmptyExpr{Err{
`building SQL assignment expression`,
fmt.Errorf(`assignment must have at least one field`),
Expand Down
42 changes: 10 additions & 32 deletions sqlb_expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,6 @@ func (self Str) Append(text []byte) []byte {
// Implement the `fmt.Stringer` interface for debug purposes.
func (self Str) String() string { return string(self) }

/*
An expression that interpolates itself as text representing a literal integer,
instead of adding an ordinal parameter and an argument.
*/
type Int int

// Implement the `Expr` interface, making this a sub-expression.
func (self Int) AppendExpr(text []byte, args []interface{}) ([]byte, []interface{}) {
return self.Append(text), args
}

// Implement the `Appender` interface, sometimes allowing more efficient text
// encoding.
func (self Int) Append(text []byte) []byte {
return strconv.AppendInt(text, int64(self), 10)
}

// Implement the `fmt.Stringer` interface for debug purposes.
func (self Int) String() string {
return strconv.FormatInt(int64(self), 10)
}

// Represents an SQL identifier, always quoted.
type Ident string

Expand All @@ -70,7 +48,7 @@ func (self Ident) Append(text []byte) []byte {
}

// Implement the `fmt.Stringer` interface for debug purposes.
func (self Ident) String() string { return appenderToStr(&self) }
func (self Ident) String() string { return AppenderString(&self) }

/*
Represents a nested SQL identifier where all elements are quoted but not
Expand Down Expand Up @@ -100,7 +78,7 @@ func (self Identifier) Append(text []byte) []byte {
}

// Implement the `fmt.Stringer` interface for debug purposes.
func (self Identifier) String() string { return appenderToStr(&self) }
func (self Identifier) String() string { return AppenderString(&self) }

// Normalizes the expression, returning nil or a single `Ident` if the length
// allows this. Otherwise returns self as-is.
Expand Down Expand Up @@ -151,7 +129,7 @@ func (self Path) Append(text []byte) []byte {
}

// Implement the `fmt.Stringer` interface for debug purposes.
func (self Path) String() string { return appenderToStr(&self) }
func (self Path) String() string { return AppenderString(&self) }

// Normalizes the expression, returning nil or a single `Ident` if the length
// allows this. Otherwise returns self as-is.
Expand Down Expand Up @@ -202,7 +180,7 @@ func (self PseudoPath) Append(text []byte) []byte {
}

// Implement the `fmt.Stringer` interface for debug purposes.
func (self PseudoPath) String() string { return appenderToStr(&self) }
func (self PseudoPath) String() string { return AppenderString(&self) }

// Normalizes the expression, returning nil or a single `Ident` if the length
// allows this. Otherwise returns self as-is.
Expand Down Expand Up @@ -247,7 +225,7 @@ func (self AliasedPath) Append(text []byte) []byte {
}

// Implement the `fmt.Stringer` interface for debug purposes.
func (self AliasedPath) String() string { return appenderToStr(&self) }
func (self AliasedPath) String() string { return AppenderString(&self) }

// Normalizes the expression, returning nil or a single `Ident` if the length
// allows this. Otherwise returns self as-is.
Expand Down Expand Up @@ -285,7 +263,7 @@ func (self Table) Append(text []byte) []byte {
}

// Implement the `fmt.Stringer` interface for debug purposes.
func (self Table) String() string { return appenderToStr(&self) }
func (self Table) String() string { return AppenderString(&self) }

/*
Variable-sized sequence of expressions. When encoding, expressions will be
Expand Down Expand Up @@ -1265,7 +1243,7 @@ type RowNumberOver [1]Expr
// Implement the `Expr` interface, making this a sub-expression.
func (self RowNumberOver) AppendExpr(text []byte, args []interface{}) ([]byte, []interface{}) {
if self[0] == nil {
return Int(0).AppendExpr(text, args)
return appendMaybeSpaced(text, `0`), args
}

bui := Bui{text, args}
Expand Down Expand Up @@ -1505,12 +1483,12 @@ func (self OrdinalParam) AppendExpr(text []byte, args []interface{}) ([]byte, []
// encoding.
func (self OrdinalParam) Append(text []byte) []byte {
text = append(text, ordinalParamPrefix)
text = Int(self).Append(text)
text = strconv.AppendInt(text, int64(self), 10)
return text
}

// Implement the `fmt.Stringer` interface for debug purposes.
func (self OrdinalParam) String() string { return appenderToStr(&self) }
func (self OrdinalParam) String() string { return AppenderString(&self) }

// Returns the corresponding Go index (starts at zero).
func (self OrdinalParam) Index() int { return int(self) - 1 }
Expand All @@ -1535,7 +1513,7 @@ func (self NamedParam) Append(text []byte) []byte {
}

// Implement the `fmt.Stringer` interface for debug purposes.
func (self NamedParam) String() string { return appenderToStr(&self) }
func (self NamedParam) String() string { return AppenderString(&self) }

// Converts to the corresponding dictionary key, which is a plain string. This
// is a free cast, used to increase code clarity.
Expand Down
2 changes: 1 addition & 1 deletion sqlb_misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Shortcut for using `(*Bui).Exprs` and `Bui.Reify`. Provided mostly for
examples. Actual code may want to use `Bui`:
bui := MakeBui(4096, 64)
panic(bui.TryExprs(someExprs...))
panic(bui.CatchExprs(someExprs...))
text, args := bui.Reify()
*/
func Reify(vals ...Expr) (string, []interface{}) {
Expand Down
2 changes: 1 addition & 1 deletion sqlb_ord.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ func (self Ord) Append(text []byte) []byte {
}

// Implement the `fmt.Stringer` interface for debug purposes.
func (self Ord) String() string { return appenderToStr(&self) }
func (self Ord) String() string { return AppenderString(&self) }

// True if the path is empty.
func (self Ord) IsEmpty() bool { return len(self.Path) == 0 }
Expand Down
Loading

0 comments on commit 5ed11d4

Please sign in to comment.