diff --git a/builder/builder.go b/builder/builder.go index 6d98ff4..9744d64 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -13,6 +13,9 @@ import ( // Builder defines a generic methods available for Select, Insert, Update and Delete builders. type Builder interface { // String returns the underlying query as a raw statement. + // This function should be used for debugging since it doesn't escape anything and is completely + // vulnerable to SQL injection. + // You should use either NamedQuery() or Query()... String() string // NamedQuery returns the underlying query as a named statement. NamedQuery() (string, map[string]interface{}) diff --git a/builder/delete.go b/builder/delete.go index 80f144b..f404b58 100644 --- a/builder/delete.go +++ b/builder/delete.go @@ -82,6 +82,9 @@ func (b Delete) Returning(values ...interface{}) Delete { } // String returns the underlying query as a raw statement. +// This function should be used for debugging since it doesn't escape anything and is completely +// vulnerable to SQL injection. +// You should use either NamedQuery() or Query()... func (b Delete) String() string { var ctx types.RawContext b.query.Write(&ctx) diff --git a/builder/doc.go b/builder/doc.go new file mode 100644 index 0000000..ade8cfe --- /dev/null +++ b/builder/doc.go @@ -0,0 +1,8 @@ +// Package builder receives user input and generates an AST using "stmt" package. +// +// There is four builder to manipulate an AST: Select, Insert, Update and Delete. +// +// When the AST is ready, you can use String(), NamedQuery() or Query() to generate the underlying query. +// However, be vigilant with String(): it's mainly used for debugging because it's completely vulnerable +// to SQL injection... +package builder diff --git a/builder/insert.go b/builder/insert.go index 206c233..0121f2c 100644 --- a/builder/insert.go +++ b/builder/insert.go @@ -134,6 +134,9 @@ func (b Insert) Set(args ...interface{}) Insert { } // String returns the underlying query as a raw statement. +// This function should be used for debugging since it doesn't escape anything and is completely +// vulnerable to SQL injection. +// You should use either NamedQuery() or Query()... func (b Insert) String() string { var ctx types.RawContext b.insert.Write(&ctx) diff --git a/builder/select.go b/builder/select.go index 6c8d6b7..509a0c8 100644 --- a/builder/select.go +++ b/builder/select.go @@ -225,6 +225,9 @@ func (b Select) Prefix(prefix interface{}) Select { } // String returns the underlying query as a raw statement. +// This function should be used for debugging since it doesn't escape anything and is completely +// vulnerable to SQL injection. +// You should use either NamedQuery() or Query()... func (b Select) String() string { var ctx types.RawContext b.query.Write(&ctx) diff --git a/builder/update.go b/builder/update.go index d4d046e..46878c8 100644 --- a/builder/update.go +++ b/builder/update.go @@ -96,6 +96,9 @@ func (b Update) Returning(values ...interface{}) Update { } // String returns the underlying query as a raw statement. +// This function should be used for debugging since it doesn't escape anything and is completely +// vulnerable to SQL injection. +// You should use either NamedQuery() or Query()... func (b Update) String() string { var ctx types.RawContext b.query.Write(&ctx) diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..b299b4a --- /dev/null +++ b/doc.go @@ -0,0 +1,66 @@ +// Package loukoum provides a simple SQL Query Builder. +// At the moment, only PostgreSQL is supported. +// +// If you have to generate complex queries, which rely on various contexts, loukoum is the right tool for you. +// It helps you generate SQL queries from composable parts. +// However, keep in mind it's not an ORM or a Mapper so you have to use a SQL connector +// (like "database/sql" or "sqlx", for example) to execute queries. +// +// If you're afraid to slip a tiny SQL injection manipulating fmt (or a byte buffer...) when you append +// conditions, loukoum is here to protect you against yourself. +// +// For further informations, you can read this documentation: +// https://github.com/ulule/loukoum/blob/master/README.md +// +// Or you can discover loukoum with these examples. +// An "insert" can be generated like that: +// +// builder := loukoum.Insert("comments"). +// Set( +// loukoum.Pair("email", comment.Email), +// loukoum.Pair("status", "waiting"), +// loukoum.Pair("message", comment.Message), +// loukoum.Pair("created_at", loukoum.Raw("NOW()")), +// ). +// Returning("id") +// +// Also, if you need an upsert, you can define a "on conflict" clause: +// +// builder := loukoum.Insert("comments"). +// Set( +// loukoum.Pair("email", comment.Email), +// loukoum.Pair("status", "waiting"), +// loukoum.Pair("message", comment.Message), +// loukoum.Pair("created_at", loukoum.Raw("NOW()")), +// ). +// OnConflict("email", loukoum.DoUpdate( +// loukoum.Pair("message", comment.Message), +// loukoum.Pair("status", "waiting"), +// loukoum.Pair("created_at", loukoum.Raw("NOW()")), +// loukoum.Pair("deleted_at", nil), +// )). +// Returning("id") +// +// Updating a news is also simple: +// +// builder := loukoum.Update("news"). +// Set( +// loukoum.Pair("published_at", loukoum.Raw("NOW()")), +// loukoum.Pair("status", "published"), +// ). +// Where(loukoum.Condition("id").Equal(news.ID)). +// And(loukoum.Condition("deleted_at").IsNull(true)). +// Returning("published_at") +// +// You can remove a specific user: +// +// builder := loukoum.Delete("users"). +// Where(loukoum.Condition("id").Equal(user.ID)) +// +// Or select a list of users... +// +// builder := loukoum.Select("id", "first_name", "last_name", "email"). +// From("users"). +// Where(loukoum.Condition("deleted_at").IsNull(true)) +// +package loukoum diff --git a/format/doc.go b/format/doc.go new file mode 100644 index 0000000..8c02eaf --- /dev/null +++ b/format/doc.go @@ -0,0 +1,2 @@ +// Package format escape various types for types.RawContext. +package format diff --git a/format/format.go b/format/format.go index 23102e8..a6c2040 100644 --- a/format/format.go +++ b/format/format.go @@ -49,7 +49,7 @@ func Value(arg interface{}) string { // nolint: gocyclo } // String formats the given string. -func String(value string) string { // nolint: errcheck +func String(value string) string { buffer := &bytes.Buffer{} writeRune(buffer, '\'') for _, char := range value { diff --git a/stmt/doc.go b/stmt/doc.go new file mode 100644 index 0000000..663a538 --- /dev/null +++ b/stmt/doc.go @@ -0,0 +1,4 @@ +// Package stmt defines various statements and clauses that are used to generate queries. +// +// Unless you have to develop complex queries, you should'nt have to use this package. +package stmt diff --git a/types/doc.go b/types/doc.go new file mode 100644 index 0000000..56a4f0c --- /dev/null +++ b/types/doc.go @@ -0,0 +1,4 @@ +// Package types defines some internal types that are handled by the "builder" and "stmt" package. +// +// RawContext, StdContext and NamedContext are used to generate queries. +package types