Code Monkey home page Code Monkey logo

go-sqlbuilder's Introduction

SQL builder for Go

Go GoDoc Go Report Coverage Status Join the chat at https://gitter.im/go-sqlbuilder/community

The sqlbuilder package implements a series of flexible and powerful SQL string concatenation builders. This package focuses on constructing SQL strings for direct use with the Go standard library's sql.DB and sql.Stmt related interfaces, and strives to optimize the performance of building SQL and reduce memory consumption.

The initial goal in designing this package was to create a pure SQL construction library that is independent of specific database drivers and business logic. It is designed to meet the needs of enterprise-level scenarios that require various customized database drivers, special operation and maintenance standards, heterogeneous systems, and non-standard SQL in complex situations. Since its open-source inception, this package has been tested in a large enterprise-level application scenario, enduring the pressure of hundreds of millions of orders daily and nearly ten million transactions per day, demonstrating good performance and scalability.

This package does not bind to any specific database driver, nor does it automatically connect to any database. It does not even assume the use of the generated SQL, making it suitable for any application scenario that constructs SQL-like statements. It is also very suitable for secondary development on this basis, to implement more business-related database access packages, ORMs, and so on.

Install

Use go get to install this package.

go get github.com/huandu/go-sqlbuilder

Usage

Basic usage

We can build a SQL really quick with this package.

sql := sqlbuilder.Select("id", "name").From("demo.user").
    Where("status = 1").Limit(10).
    String()

fmt.Println(sql)

// Output:
// SELECT id, name FROM demo.user WHERE status = 1 LIMIT 10

In the most common cases, we need to escape all input from user. In this case, create a builder before starting.

sb := sqlbuilder.NewSelectBuilder()

sb.Select("id", "name", sb.As("COUNT(*)", "c"))
sb.From("user")
sb.Where(sb.In("status", 1, 2, 5))

sql, args := sb.Build()
fmt.Println(sql)
fmt.Println(args)

// Output:
// SELECT id, name, COUNT(*) AS c FROM user WHERE status IN (?, ?, ?)
// [1 2 5]

Pre-defined SQL builders

This package includes following pre-defined builders so far. API document and examples can be found in the godoc online document.

There is a special method SQL(sql string) implemented by all statement builders. We can use this method to insert any arbitrary SQL fragment into a builder when building a SQL. It's quite useful to build SQL containing non-standard syntax supported by a OLTP or OLAP system.

// Build a SQL to create a HIVE table.
sql := sqlbuilder.CreateTable("users").
    SQL("PARTITION BY (year)").
    SQL("AS").
    SQL(
        sqlbuilder.Select("columns[0] id", "columns[1] name", "columns[2] year").
            From("`all-users.csv`").
            Limit(100).
            String(),
    ).
    String()

fmt.Println(sql)

// Output:
// CREATE TABLE users PARTITION BY (year) AS SELECT columns[0] id, columns[1] name, columns[2] year FROM `all-users.csv` LIMIT 100

Following are some utility methods to deal with special cases.

  • Flatten can convert an array-like variable to a flat slice of []interface{} recursively. For instance, calling Flatten([]interface{"foo", []int{2, 3}}) returns []interface{}{"foo", 2, 3}. This method can work with builder methods like In/NotIn/Values/etc to convert a typed array to []interface{} or merge inputs.
  • List works similar to Flatten except that its return value is dedecated for builder args. For instance, calling Buildf("my_func(%v)", List([]int{1, 2, 3})).Build() returns SQL my_func(?, ?, ?) and args []interface{}{1, 2, 3}.
  • Raw marks a string as "raw string" in args. For instance, calling Buildf("SELECT %v", Raw("NOW()")).Build() returns SQL SELECT NOW().

To learn how to use builders, check out examples on GoDoc.

Build WHERE clause

WHERE clause is the most important part of a SQL. We can use Where method to add one or more conditions to a builder.

To make building WHERE clause easier, there is an utility type called Cond to build condition. All builders which support WHERE clause have an anonymous Cond field so that we can call methods implemented by Cond on these builders.

sb := sqlbuilder.Select("id").From("user")
sb.Where(
    sb.In("status", 1, 2, 5),
    sb.Or(
        sb.Equal("name", "foo"),
        sb.Like("email", "foo@%"),
    ),
)

sql, args := sb.Build()
fmt.Println(sql)
fmt.Println(args)

// Output:
// SELECT id FROM user WHERE status IN (?, ?, ?) AND (name = ? OR email LIKE ?)
// [1 2 5 foo foo@%]

There are many methods for building conditions.

There are also some methods to combine conditions.

  • Cond.And: Combine conditions with AND operator.
  • Cond.Or: Combine conditions with OR operator.

Share WHERE clause among builders

Due to the importance of the WHERE statement in SQL, we often need to continuously append conditions and even share some common WHERE conditions among different builders. Therefore, we abstract the WHERE statement into a WhereClause struct, which can be used to create reusable WHERE conditions.

Here is a sample to show how to copy WHERE clause from a SelectBuilder to an UpdateBuilder.

// Build a SQL to select a user from database.
sb := Select("name", "level").From("users")
sb.Where(
    sb.Equal("id", 1234),
)
fmt.Println(sb)

ub := Update("users")
ub.Set(
    ub.Add("level", 10),
)

// Set the WHERE clause of UPDATE to the WHERE clause of SELECT.
ub.WhereClause = sb.WhereClause
fmt.Println(ub)

// Output:
// SELECT name, level FROM users WHERE id = ?
// UPDATE users SET level = level + ? WHERE id = ?

Read samples for WhereClause to learn how to use it.

Build SQL for different systems

SQL syntax and parameter marks vary in different systems. In this package, we introduce a concept called "flavor" to smooth out these difference.

Right now, MySQL, PostgreSQL, SQLServer, SQLite, CQL, ClickHouse, Presto and Oracle are defined in flavor list. Feel free to open issue or send pull request if anyone asks for a new flavor.

By default, all builders uses DefaultFlavor to build SQL. The default value is MySQL.

There is a BuildWithFlavor method in Builder interface. We can use it to build a SQL with provided flavor.

We can wrap any Builder with a default flavor through WithFlavor.

To be more verbose, we can use PostgreSQL.NewSelectBuilder() to create a SelectBuilder with the PostgreSQL flavor. All builders can be created in this way.

Using Struct as a light weight ORM

Struct stores type information and struct fields of a struct. It's a factory of builders. We can use Struct methods to create initialized SELECT/INSERT/UPDATE/DELETE builders to work with the struct. It can help us to save time and avoid human-error on writing column names.

We can define a struct type and use field tags to let Struct know how to create right builders for us.

type ATable struct {
    Field1     string                                    // If a field doesn't has a tag, use "Field1" as column name in SQL.
    Field2     int    `db:"field2"`                      // Use "db" in field tag to set column name used in SQL.
    Field3     int64  `db:"field3" fieldtag:"foo,bar"`   // Set fieldtag to a field. We can call `WithTag` to include fields with tag or `WithoutTag` to exclude fields with tag.
    Field4     int64  `db:"field4" fieldtag:"foo"`       // If we use `s.WithTag("foo").Select("t")`, columnes of SELECT are "t.field3" and "t.field4".
    Field5     string `db:"field5" fieldas:"f5_alias"`   // Use "fieldas" in field tag to set a column alias (AS) used in SELECT.
    Ignored    int32  `db:"-"`                           // If we set field name as "-", Struct will ignore it.
    unexported int                                       // Unexported field is not visible to Struct.
    Quoted     string `db:"quoted" fieldopt:"withquote"` // Add quote to the field using back quote or double quote. See `Flavor#Quote`.
    Empty      uint   `db:"empty" fieldopt:"omitempty"`  // Omit the field in UPDATE if it is a nil or zero value.

    // The `omitempty` can be written as a function.
    // In this case, omit empty field `Tagged` when UPDATE for tag `tag1` and `tag3` but not `tag2`.
    Tagged     string `db:"tagged" fieldopt:"omitempty(tag1,tag3)" fieldtag:"tag1,tag2,tag3"`

    // By default, the `SelectFrom("t")` will add the "t." to all names of fields matched tag.
    // We can add dot to field name to disable this behavior.
    FieldWithTableAlias string `db:"m.field"`
}

Read examples for Struct to learn details of how to use it.

What's more, we can use Struct as a kind of zero-config ORM. While most ORM implementations requires several prerequisite configs to work with database connections, Struct doesn't require any config and work well with any SQL driver which works with database/sql. Struct doesn't call any database/sql API; It just creates right SQL with arguments for DB#Query/DB#Exec or a slice of address of struct fields for Rows#Scan/Row#Scan.

Here is a sample to use Struct as ORM. It should be quite straight forward for developers who are familiar with database/sql APIs.

type User struct {
    ID     int64  `db:"id" fieldtag:"pk"`
    Name   string `db:"name"`
    Status int    `db:"status"`
}

// A global variable to create SQL builders.
// All methods of userStruct are thread-safe.
var userStruct = NewStruct(new(User))

func ExampleStruct() {
    // Prepare SELECT query.
    //     SELECT user.id, user.name, user.status FROM user WHERE id = 1234
    sb := userStruct.SelectFrom("user")
    sb.Where(sb.Equal("id", 1234))

    // Execute the query.
    sql, args := sb.Build()
    rows, _ := db.Query(sql, args...)
    defer rows.Close()

    // Scan row data and set value to user.
    // Suppose we get following data.
    //
    //     |  id  |  name  | status |
    //     |------|--------|--------|
    //     | 1234 | huandu | 1      |
    var user User
    rows.Scan(userStruct.Addr(&user)...)

    fmt.Println(sql)
    fmt.Println(args)
    fmt.Printf("%#v", user)

    // Output:
    // SELECT user.id, user.name, user.status FROM user WHERE id = ?
    // [1234]
    // sqlbuilder.User{ID:1234, Name:"huandu", Status:1}
}

In many production environments, table column names are usually snake_case words, e.g. user_id, while we have to use CamelCase in struct types to make struct fields public and golint happy. It's a bit redundant to use the db tag in every struct field. If there is a certain rule to map field names to table column names, We can use field mapper function to make code simpler.

The DefaultFieldMapper is a global field mapper function to convert field name to new style. By default, it sets to nil and does nothing. If we know that most table column names are snake_case words, we can set DefaultFieldMapper to sqlbuilder.SnakeCaseMapper. If we have some special cases, we can set custom mapper to a Struct by calling WithFieldMapper.

Following are special notes regarding to field mapper.

  • Field tag has precedence over field mapper function - thus, mapper is ignored if the db tag is set;
  • Field mapper is called only once on a Struct when the Struct is used to create builder for the first time.

See field mapper function sample as a demo.

Nested SQL

It's quite straight forward to create a nested SQL: use a builder as an argument to nest it.

Here is a sample.

sb := sqlbuilder.NewSelectBuilder()
fromSb := sqlbuilder.NewSelectBuilder()
statusSb := sqlbuilder.NewSelectBuilder()

sb.Select("id")
sb.From(sb.BuilderAs(fromSb, "user")))
sb.Where(sb.In("status", statusSb))

fromSb.Select("id").From("user").Where(fromSb.GreaterThan("level", 4))
statusSb.Select("status").From("config").Where(statusSb.Equal("state", 1))

sql, args := sb.Build()
fmt.Println(sql)
fmt.Println(args)

// Output:
// SELECT id FROM (SELECT id FROM user WHERE level > ?) AS user WHERE status IN (SELECT status FROM config WHERE state = ?)
// [4 1]

Use sql.Named in a builder

The function sql.Named defined in database/sql can create a named argument in SQL. It's necessary if we want to reuse an argument several times in one SQL. It's still quite simple to use named arguments in a builder: use it as an argument.

Here is a sample.

now := time.Now().Unix()
start := sql.Named("start", now-86400)
end := sql.Named("end", now+86400)
sb := sqlbuilder.NewSelectBuilder()

sb.Select("name")
sb.From("user")
sb.Where(
    sb.Between("created_at", start, end),
    sb.GE("modified_at", start),
)

sql, args := sb.Build()
fmt.Println(sql)
fmt.Println(args)

// Output:
// SELECT name FROM user WHERE created_at BETWEEN @start AND @end AND modified_at >= @start
// [{{} start 1514458225} {{} end 1514544625}]

Argument modifiers

There are several modifiers for arguments.

  • List(arg) represents a list of arguments. If arg is a slice or array, e.g. a slice with 3 ints, it will be compiled to ?, ?, ? and flattened in the final arguments as 3 ints. It's a tool for convenience. We can use it in the IN expression or VALUES of INSERT INTO.
  • TupleNames(names) and Tuple(values) represent the tuple syntax in SQL. See Tuple for usage.
  • Named(name, arg) represents a named argument. It only works with Build or BuildNamed to define a named placeholder using syntax ${name}.
  • Raw(expr) marks an expr as a plain string in SQL rather than an argument. When we build a builder, the value of raw expressions are copied in SQL string directly without leaving any ? in SQL.

Freestyle builder

A builder is only a way to record arguments. If we want to build a long SQL with lots of special syntax (e.g. special comments for a database proxy), simply use Buildf to format a SQL string using a fmt.Sprintf-like syntax.

sb := sqlbuilder.NewSelectBuilder()
sb.Select("id").From("user")

explain := sqlbuilder.Buildf("EXPLAIN %v LEFT JOIN SELECT * FROM banned WHERE state IN (%v, %v)", sb, 1, 2)
sql, args := explain.Build()
fmt.Println(sql)
fmt.Println(args)

// Output:
// EXPLAIN SELECT id FROM user LEFT JOIN SELECT * FROM banned WHERE state IN (?, ?)
// [1 2]

Using special syntax to build SQL

Package sqlbuilder defines special syntax to represent an uncompiled SQL internally. If we want to take advantage of the syntax to build customized tools, we can use Build to compile it with arguments.

The format string uses special syntax to represent arguments.

  • $? refers successive arguments passed in the call. It works similar as %v in fmt.Sprintf.
  • $0 $1 ... $n refers nth-argument passed in the call. Next $? will use arguments n+1.
  • ${name} refers a named argument created by Named with name.
  • $$ is a "$" string.
sb := sqlbuilder.NewSelectBuilder()
sb.Select("id").From("user").Where(sb.In("status", 1, 2))

b := sqlbuilder.Build("EXPLAIN $? LEFT JOIN SELECT * FROM $? WHERE created_at > $? AND state IN (${states}) AND modified_at BETWEEN $2 AND $?",
    sb, sqlbuilder.Raw("banned"), 1514458225, 1514544625, sqlbuilder.Named("states", sqlbuilder.List([]int{3, 4, 5})))
sql, args := b.Build()

fmt.Println(sql)
fmt.Println(args)

// Output:
// EXPLAIN SELECT id FROM user WHERE status IN (?, ?) LEFT JOIN SELECT * FROM banned WHERE created_at > ? AND state IN (?, ?, ?) AND modified_at BETWEEN ? AND ?
// [1 2 1514458225 3 4 5 1514458225 1514544625]

If we just want to use ${name} syntax to refer named arguments, use BuildNamed instead. It disables all special syntax but ${name} and $$.

Interpolate args in the sql

Some SQL-like drivers, e.g. SQL for Redis, SQL for ES, etc., doesn't actually implement StmtExecContext#ExecContext. They will fail when len(args) > 0. The only solution is to interpolate args in the sql, and execute the interpolated query with the driver.

The design goal of the interpolation feature in this package is to implement a "basically sufficient" capability, rather than a feature that is on par with various SQL drivers and DBMS systems.

Security warning: I try my best to escape special characters in interpolate methods, but it's still less secure than Stmt implemented by SQL servers.

This feature is inspired by interpolation feature in package github.com/go-sql-driver/mysql.

Here is a sample for MySQL.

sb := MySQL.NewSelectBuilder()
sb.Select("name").From("user").Where(
    sb.NE("id", 1234),
    sb.E("name", "Charmy Liu"),
    sb.Like("desc", "%mother's day%"),
)
sql, args := sb.Build()
query, err := MySQL.Interpolate(sql, args)

fmt.Println(query)
fmt.Println(err)

// Output:
// SELECT name FROM user WHERE id <> 1234 AND name = 'Charmy Liu' AND desc LIKE '%mother\'s day%'
// <nil>

Here is a sample for PostgreSQL. Note that the dollar quote is supported.

// Only the last `$1` is interpolated.
// Others are not interpolated as they are inside dollar quote (the `$$`).
query, err := PostgreSQL.Interpolate(`
CREATE FUNCTION dup(in int, out f1 int, out f2 text) AS $$
    SELECT $1, CAST($1 AS text) || ' is text'
$$
LANGUAGE SQL;

SELECT * FROM dup($1);`, []interface{}{42})

fmt.Println(query)
fmt.Println(err)

// Output:
//
// CREATE FUNCTION dup(in int, out f1 int, out f2 text) AS $$
//     SELECT $1, CAST($1 AS text) || ' is text'
// $$
// LANGUAGE SQL;
//
// SELECT * FROM dup(42);
// <nil>

License

This package is licensed under MIT license. See LICENSE for details.

go-sqlbuilder's People

Contributors

abanoub-fathy avatar cemremengu avatar ddouglas avatar dgellow avatar enzobrands avatar florianlechner avatar gitter-badger avatar hanyuancheung avatar huandu avatar jvrplmlmn avatar mdobak avatar petersunbag avatar rw-access avatar scheca avatar serpentiel avatar spencerc avatar yansal avatar zhangyongding avatar zhongr3n avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

go-sqlbuilder's Issues

Ilike condition

Hello,
I'm using the like function of the selectBuilder to filter my results

	builder.Where(builder.Like("names", "%"+*queryParams.Names+"%"))

I want the condition to be case insensitive, so I found 'ilike' function on postgres. I see the condition is not implemented, ¿is there a way to implement my own filter conditions?

ORDER BY statement bug

when I use this:
builder.OrderBy("up_ts", "task_id").Desc()
will generate
ORDER BY up_ts task_id DESC
but I want:
ORDER BY up_ts DESC task_id DESC

Order By doesn't work with multiple columns and different sorting

SELECT name, species, birth FROM pet ORDER BY species DESC, birth DESC

I cannot seem to find a good way to write this. I can do orderBy(species, birth).Desc() but that's different. That's the equivalent of

SELECT name, species, birth FROM pet ORDER BY species ASC, birth DESC

Support different order on different columns

Currently it only supports a single order for whole select builder. Can we support different order on different columns? Something like

sb.OrderBy("col1").Asc().OrderBy("col2").Desc()

Improved SQL alias support on columns

I have the use-case where I need to alias column names. I'm currently doing this:

type Report struct {
    First string `db:"FIRST_NAME as \"First\""'`
    Last string `db:"LAST_NAME as \"Last\""'`
}

This works, but if feels like a hack?

Assuming this is something you'd want to add to the library, would you recommend the above, or should we look at something like a new fieldopt

希望添加对struct columns和values的单独导出功能

例如:
struct.StructColumnsForTag("table", "tag") -> []string,返回在build语句中会使用的column names
struct.StructValuesForTag("table", "tag", values...) -> [][]interface{},返回columns对应的values

场景是我在配合 sqlmock 使用的时候有些困难,目前需要手动实现一遍struct内的反射逻辑

Cannot using .Like in Where

sb.Select("*").From("articles").OrderBy("created_at desc").Where(
  sb.Like("title", "%a%"),
)

error shows:

not enough args to execute query: want 1 got 0

SQL Server flavor

Hello,

This is a very interesting project, thank you for publishing @huandu !

Right now, there are only three flavors, MySQL, PostgreSQL and SQLite. Open new issue to me to ask for a new flavor if you find it necessary.

Any chance you could add SQL Server support ? If go-sqlbuilder supports the 2nd, 4th and 9th RDBMS by rank, why not the 3rd as well? I believe this will increase project adoption by enterprise developers.

Auto create table builder by fields in struct

type User struct{
  ID int `db:"id" sqldb:"int(11)"`
  Login string `db:"login" sqldb:"varchar(255)"`
}

ctb := sqlbuilder.NewCreateTableBuilder()
// compile struct to ctb obj
ctb = ctb.IfNotExists()
println(ctb.String())
// CREATE TABLE IF NOT EXISTS ...

Replace Into

Please realize the possibility of rewriting cells and creating tables

REPLACE INTO ...
CREATE TABLE ...

JSON_TABLE issue

Input:
sb.From("warehouse", "JSON_TABLE(data, '$[*]' COLUMNS (id JSON PATH '$.id')) AS warehouseJson")`

Output:
FROM warehouse, JSON_TABLE(data, '[*]' COLUMNS (id JSON PATH '.id')) AS warehouseJson

"$" is missing :-/

No easy way to use .In(...) with a slice

Hi,

I have the following problem:
Something like this is not possible:

func getSomethingByIds(ctx context.Contex, ids []int32) error {
	b := p.getANewSelect(ctx)
	b.Where(b.In("something.somethingId", ids...))
//...
}

It cannot convert []int32 to []interface{} on the fly.
But as this seems to be a very usefull pattern to me (to pass any slice in an In statement), it would be great to have support for this.
Also b.In("parameter.parameterid", ids) doesn't work, which would also be a nice syntax and possible with reflection.

The only workaround I see for now is to first convert all values to an interface{} in a for loop before passing it, which is very cumbersome and not nice.

Possible solutions:

  1. Just provide an .InSlice(interface{}) which only accepts slices directly and panics if it's no slice (as it's a programmer error)
  2. Use reflection in .In() to and check if the first passed value is a slice.

What do you think?

fieldas does not support tags

Didn't think about this when requesting fieldas -- at the moment if the same alias is used between tags, the last specified fieldas is used. For example:

type structWithAsAndTags struct {
	T5 string `db:"t5" fieldas:"f4" fieldtag:"a"`
	T6 string `db:"t5" fieldas:"f5" fieldtag:"b"`
}

func TestStructFieldAsAndTagsA(t *testing.T) {
	a := assert.New(t)
	s := NewStruct(new(structWithAsAndTags))
	sb := s.SelectFromForTag("t", "a")
	b := Build(`COPY ($?) TO '/path/to/file.csv' (FORMAT CSV, HEADER)`, sb)
	sql, _ := b.Build()
	a.Equal(string(sql), `COPY (SELECT t.t5 AS f4 FROM t) TO '/path/to/file.csv' (FORMAT CSV, HEADER)`)
}

func TestStructFieldAsAndTagsB(t *testing.T) {
	a := assert.New(t)
	s := NewStruct(new(structWithAsAndTags))
	sb := s.SelectFromForTag("t", "b")
	b := Build(`COPY ($?) TO '/path/to/file.csv' (FORMAT CSV, HEADER)`, sb)
	sql, _ := b.Build()
	a.Equal(string(sql), `COPY (SELECT t.t5 AS f5 FROM t) TO '/path/to/file.csv' (FORMAT CSV, HEADER)`)
}

At the moment the first test passes, but the second fails. The use-case being: to support a different fieldas per tag.

Do you think it would be worth refactoring to allow fieldas to support tags in this way?

Equal Does Not Work With Column Names

sb.Equal doesn't work with column names because it tries to set the column as a string. Needs a new equal function for column names that doesn't get embedded as an arg

example

sb.Equal(tableB.id=tableA.question_id) would fail silently

omitempty

omitempty works on creation action?

Problem when using with InfluxDB.

When I use this lib to generate SQL for InfluxDB, I found it works good, except for that: I can't get the plain SQL.

Today, many databases are using SQL-Like DSL like InfluxDB, but they are using REST API, which needs plain SQL but not ? placeholder and a parameter list.

I think that a method to generate plain SQL is much helpful.

How to model WITH queries

Hey @huandu,

First, thanks so much for this repo, helped me tremendously to streamline my code to something more readable and manageable.

One thing I'm wondering how to migrate to sqlbuilder is queries using WITH pseudo tables, e.g.

WITH food AS (
  SELECT * FROM fruits
  UNION
  SELECT * FROM vegetables
)
SELECT avg(price)
FROM food;

Thanks for your help :)

Add INNER as a JoinOption

I've run into a use case that could be made simpler with another JoinOption available.

I'm dynamically generating SQL queries and conditionally change from JOIN to LEFT JOIN. Since an unmodified JOIN is equivalent to INNER JOIN then I realized that I could use JoinWithOption, and conditionally change the JoinOption.

There are a few obvious options Ito do this:

  • Conditionally call either Join or JoinWIthOption, with a JoinOption to the second
  • Create my own const JoinOption that's just the word "INNER". Then I'll conditionally just use a package constant Inner or the imported constant sqlbuilder.LeftJoin. It's a little inconsistent, but not bad
  • Add a new const Inner JoinOption = "INNER" to the library

Any thoughts on the last option? I figured it's an easy and reasonable addition.
I can create a PR if you think it's a good path forward for the library.

Why update method using flavor not replace with $ ?

Hi @huandu, why i'm using flavor for update method is not work for query builder example 1, because this flavor not replace all ? in query update to $.

UPDATE BUILDER 1

psql := sqlb.PostgreSQL
query := psql.NewUpdateBuilder().Update("todos").Set("name = ?", "category = ?", "description = ?").Where("id = ?").String()

fmt.Println(query) // UPDATE todos SET name = ?, category = ?, description = ? WHERE id = ?

UPDATE BUILDER 2

psql := sqlb.PostgreSQL
query := psql.NewUpdateBuilder().Update("todos")
query.Set(query.Equal("name", "?"), query.Equal("category", "?"), query.Equal("description", "?")).Where(query.Equal("id", "?")).String()

fmt.Println(query) //UPDATE todos SET name = $1, category = $2, description = $3 WHERE id = $4

add converter function for a specific field

I am using postgresql and want to use json column. I have described json tags and want to make sql builder parse data before insertion into table.
table structure is next

CREATE TABLE IF NOT EXISTS json_test
(
    id uuid UNIQUE NOT NULL,
    meta  jsonb
);

golang structure below:

type JsonTest struct {
	id uuid.UUID `db:"id"`
	Meta []Meta`db:"meta"`
}

type Meta struct {
	Name string `json:"name"`
	Value string `json:"value"`
}

Is there a way to set function that Marshals my data to a correct values? If no It would be great to add such functionality.

add new tag

Hi. I am using your builder with go modules. I want to get JOIN functionality. It would be great if you'll add new tag like v1.1.0 to current master

trying to build a query with another select

i am trying to build this query Like this (I took the example from here)

SELECT b.name, a.the_count
FROM
  some_table b, 
  (SELECT name, COUNT(*) AS the_count
  FROM some_table
  GROUP BY name) AS a
WHERE b.name = a.name;

i tried

func x()(string,[]interface{}) {

	s := sqlb.NewSelectBuilder()

	s.Select("b.name", "a.the_count")

	s.From("some_table b")
	
        s2 := sqlb.NewSelectBuilder()

	s2.Select("name", "COUNT(*) AS the_count")

	s2.From("some_table")
	s2.GroupBy("name")

	s.BuilderAs(s2, "a")

	s.Where(s.E("b.name", "a.name"))

	return s.Build()

}

but I am getting this SELECT b.name, a.the_count FROM some_table b WHERE b.name = ? without the 2nd SELECT

What is required to add ClickHouse support?

Hi,

I want to add ClickHouse support to this, but not sure that is required to add a new database.
I hope I can get some tips, then I would love to make a PR for it.

Thanks

Only the last `GroupBy()` call will be used

When you call GroupBy() multiple times, only the last one will be in the SQL.

Code:

func (sb *SelectBuilder) GroupBy(col ...string) *SelectBuilder {
	sb.groupByCols = col
	sb.marker = selectMarkerAfterGroupBy
	return sb
}

Why not:

func (sb *SelectBuilder) GroupBy(col ...string) *SelectBuilder {
	sb.groupByCols = append(sb.groupByCols, col...)
	sb.marker = selectMarkerAfterGroupBy
	return sb
}

Struct SelectFrom should include full table name

When making joins, it would be useful is the Struct added the table name to the fields or if it implemented a SelectFromAs method.

Right now, for example, there is no way of doing this:

var c Company
sb := companyStruct.SelectFrom("companies", &c)
sb.Join("company_balances", "company_balances.company_id = companies.company_id").
	Where(sb.E("balance_id", balanceID)).
	Limit(1)

This will fail as both the companies and company_balances table have a company_id field.

Why using sql builder like squirrel or go-sqlbuilder rather than plain old sql string?

just starting to use golang in production and found some legacy code using sqlbuilder heavily. I found the code is very hard to understand compared to the sql string.

sb := sqlbuilder.NewSelectBuilder()

sb.Select("id", "name", sb.As("COUNT(*)", "c"))
sb.From("user")
sb.Where(sb.In("status", 1, 2, 5))
vs

sql:=SELECT id, name, COUNT(*) AS c FROM user WHERE status IN (?, ?, ?)

I think the second one is much easy to understand.

Column MapperFunc required.

希望可以使用类似sqlx的MapperFunc,自己配置字段的映射,而不是每个打tag。

因为对MySQL一般都是驼峰转下划线,现在全部手动写感觉比较麻烦。

PostgreSQL 参数占位符用法

PostgreSQL 参数的占位符,一般都是 $1...$6... ,而 go-sqlbuilder 生成的 sql 语句是 ?...?... ,这个是有特别的用法,没有在文档里说明吗?

Update of non omitempty pointer field with nil value panics

Given a pointer field in a Struct, and a value update missing that field, there's an extra call to dereferencedValue in UpdateForTag which returns an invalid Value. Avoiding this call for non-omitempty fields with empty values would prevent that nil-pointer access in reflection:

		if isEmptyValue(val) {
			if _, ok := s.omitEmptyFields[f]; ok {
				continue
			}
		} else {
			val = dereferencedValue(val)
		}
		data := val.Interface()
		assignments = append(assignments, ub.Assign(quoted[i], data))

I have a local branch but wasn't able to open a PR. If you think it may be worth a review, please let me know how I can open it and upload the code.

This is the test case I've added to struct_test.go to work on this:

type structWithPointers struct {
	A int      `db:"aa" fieldopt:"omitempty"`
	B *string  `db:"bb"`
	C *float64 `db:"cc" fieldopt:"omitempty"`
}

func TestStructWithPointers(t *testing.T) {
	st := NewStruct(new(structWithPointers)).For(MySQL)
	sql1, _ := st.Update("foo", new(structWithPointers)).Build()

	if expected := "UPDATE foo SET bb = ?"; sql1 != expected {
		t.Fatalf("invalid sql. [expected:%v] [actual:%v]", expected, sql1)
	}

	a := 123
	c := 123.45
	sql2, args2 := st.Update("foo", &structWithPointers{
		A: a,
		C: &c,
	}).Build()

	if expected := "UPDATE foo SET aa = ?, bb = ?, cc = ?"; sql2 != expected {
		t.Fatalf("invalid sql. [expected:%v] [actual:%v]", expected, sql2)
	}

	if expected := []interface{}{a, (*string)(nil), c}; !reflect.DeepEqual(expected, args2) {
		t.Fatalf("invalid args. [expected:%#v] [actual:%#v]", expected, args2)
	}
}```

Method In can't accept a slice

// In represents "field IN (value...)".  
func (c *Cond) In(field string, value ...interface{}) string {
func GetByIds(ids []int64) (err error) {
	sb :=sqlbuilder.NewSelectBuilder()
	sb.Select("*")
	sb.From(tableName)
	sb.Where(
		sb.In("delivery_id", deliveryIds...),
	)

make error is

cannot use deliveryIds (type []int64) as type []interface {} in argument to sb.Cond.In

It seems the only way is to do a for-loop to transfer the deliveryIds to a []interface {}, but it is ugly.
Maybe method In can the do this transfer.

Is it possible to call stored procedure

Hello,

Is it possible to call stored procedures from this. We can call functions as it can be called as part of select query.

But any chance for stored procedures. If yes, can you share any sample code.

Thank you

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.