Order by column simplified.
This commit is contained in:
parent
ba3cd37734
commit
8049b2ec01
4 changed files with 167 additions and 75 deletions
|
|
@ -4,7 +4,6 @@ package sqlbuilder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"github.com/dropbox/godropbox/database/sqltypes"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/dropbox/godropbox/errors"
|
"github.com/dropbox/godropbox/errors"
|
||||||
|
|
@ -34,6 +33,9 @@ type Column interface {
|
||||||
|
|
||||||
Lte(rhs Expression) BoolExpression
|
Lte(rhs Expression) BoolExpression
|
||||||
LteLiteral(rhs interface{}) BoolExpression
|
LteLiteral(rhs interface{}) BoolExpression
|
||||||
|
|
||||||
|
Asc() OrderByClause
|
||||||
|
Desc() OrderByClause
|
||||||
}
|
}
|
||||||
|
|
||||||
type NullableColumn bool
|
type NullableColumn bool
|
||||||
|
|
@ -93,7 +95,7 @@ func (c *baseColumn) SerializeSqlForColumnList(out *bytes.Buffer) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *baseColumn) SerializeSql(out *bytes.Buffer) error {
|
func (c baseColumn) SerializeSql(out *bytes.Buffer) error {
|
||||||
if c.table != "" {
|
if c.table != "" {
|
||||||
_, _ = out.WriteString(c.table)
|
_, _ = out.WriteString(c.table)
|
||||||
_, _ = out.WriteString(".")
|
_, _ = out.WriteString(".")
|
||||||
|
|
@ -123,6 +125,14 @@ func (c *baseColumn) LteLiteral(literal interface{}) BoolExpression {
|
||||||
return Lte(c, Literal(literal))
|
return Lte(c, Literal(literal))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *baseColumn) Asc() OrderByClause {
|
||||||
|
return Asc(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseColumn) Desc() OrderByClause {
|
||||||
|
return Desc(c)
|
||||||
|
}
|
||||||
|
|
||||||
type bytesColumn struct {
|
type bytesColumn struct {
|
||||||
baseColumn
|
baseColumn
|
||||||
isExpression
|
isExpression
|
||||||
|
|
@ -295,66 +305,75 @@ func validIdentifierName(name string) bool {
|
||||||
return validIdentifierRegexp.MatchString(name)
|
return validIdentifierRegexp.MatchString(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pseudo Column type returned by table.C(name)
|
//
|
||||||
type deferredLookupColumn struct {
|
//// Pseudo Column type returned by table.C(name)
|
||||||
isProjection
|
//type deferredLookupColumn struct {
|
||||||
isExpression
|
// isProjection
|
||||||
table *Table
|
// isExpression
|
||||||
colName string
|
// table *Table
|
||||||
|
// colName string
|
||||||
cachedColumn NonAliasColumn
|
//
|
||||||
}
|
// cachedColumn NonAliasColumn
|
||||||
|
//}
|
||||||
func (c *deferredLookupColumn) Name() string {
|
//
|
||||||
return c.colName
|
//func (c *deferredLookupColumn) Name() string {
|
||||||
}
|
// return c.colName
|
||||||
|
//}
|
||||||
func (c *deferredLookupColumn) SerializeSqlForColumnList(
|
//
|
||||||
out *bytes.Buffer) error {
|
//func (c *deferredLookupColumn) SerializeSqlForColumnList(
|
||||||
|
// out *bytes.Buffer) error {
|
||||||
return c.SerializeSql(out)
|
//
|
||||||
}
|
// return c.SerializeSql(out)
|
||||||
|
//}
|
||||||
func (c *deferredLookupColumn) SerializeSql(out *bytes.Buffer) error {
|
//
|
||||||
if c.cachedColumn != nil {
|
//func (c *deferredLookupColumn) SerializeSql(out *bytes.Buffer) error {
|
||||||
return c.cachedColumn.SerializeSql(out)
|
// if c.cachedColumn != nil {
|
||||||
}
|
// return c.cachedColumn.SerializeSql(out)
|
||||||
|
// }
|
||||||
col, err := c.table.getColumn(c.colName)
|
//
|
||||||
if err != nil {
|
// col, err := c.table.getColumn(c.colName)
|
||||||
return err
|
// if err != nil {
|
||||||
}
|
// return err
|
||||||
|
// }
|
||||||
c.cachedColumn = col
|
//
|
||||||
return col.SerializeSql(out)
|
// c.cachedColumn = col
|
||||||
}
|
// return col.SerializeSql(out)
|
||||||
|
//}
|
||||||
func (c *deferredLookupColumn) setTableName(table string) error {
|
//
|
||||||
return errors.Newf(
|
//func (c *deferredLookupColumn) setTableName(table string) error {
|
||||||
"Lookup column '%s' should never have setTableName called on it",
|
// return errors.Newf(
|
||||||
c.colName)
|
// "Lookup column '%s' should never have setTableName called on it",
|
||||||
}
|
// c.colName)
|
||||||
|
//}
|
||||||
func (c *deferredLookupColumn) Eq(rhs Expression) BoolExpression {
|
//
|
||||||
lit, ok := rhs.(*literalExpression)
|
//func (c *deferredLookupColumn) Eq(rhs Expression) BoolExpression {
|
||||||
if ok && sqltypes.Value(lit.value).IsNull() {
|
// lit, ok := rhs.(*literalExpression)
|
||||||
return newBoolExpression(c, rhs, []byte(" IS "))
|
// if ok && sqltypes.Value(lit.value).IsNull() {
|
||||||
}
|
// return newBoolExpression(c, rhs, []byte(" IS "))
|
||||||
return newBoolExpression(c, rhs, []byte(" = "))
|
// }
|
||||||
}
|
// return newBoolExpression(c, rhs, []byte(" = "))
|
||||||
|
//}
|
||||||
func (c *deferredLookupColumn) Gte(rhs Expression) BoolExpression {
|
//
|
||||||
return Gte(c, rhs)
|
//func (c *deferredLookupColumn) Gte(rhs Expression) BoolExpression {
|
||||||
}
|
// return Gte(c, rhs)
|
||||||
|
//}
|
||||||
func (c *deferredLookupColumn) GteLiteral(rhs interface{}) BoolExpression {
|
//
|
||||||
return Gte(c, Literal(rhs))
|
//func (c *deferredLookupColumn) GteLiteral(rhs interface{}) BoolExpression {
|
||||||
}
|
// return Gte(c, Literal(rhs))
|
||||||
|
//}
|
||||||
func (c *deferredLookupColumn) Lte(rhs Expression) BoolExpression {
|
//
|
||||||
return Lte(c, rhs)
|
//func (c *deferredLookupColumn) Lte(rhs Expression) BoolExpression {
|
||||||
}
|
// return Lte(c, rhs)
|
||||||
|
//}
|
||||||
func (c *deferredLookupColumn) LteLiteral(literal interface{}) BoolExpression {
|
//
|
||||||
return Lte(c, Literal(literal))
|
//func (c *deferredLookupColumn) LteLiteral(literal interface{}) BoolExpression {
|
||||||
}
|
// return Lte(c, Literal(literal))
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func (c *deferredLookupColumn) Asc() OrderByClause {
|
||||||
|
// return sqlbuilder.Asc(c)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func (c *deferredLookupColumn) Desc() OrderByClause {
|
||||||
|
// return sqlbuilder.Desc(c)
|
||||||
|
//}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/serenize/snaker"
|
"github.com/serenize/snaker"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
@ -36,6 +37,7 @@ func Execute(db *sql.DB, query string, destinationPtr interface{}) error {
|
||||||
rowData := createScanValue(columnTypes)
|
rowData := createScanValue(columnTypes)
|
||||||
|
|
||||||
scanContext := &scanContext{
|
scanContext := &scanContext{
|
||||||
|
|
||||||
columnNames: columnNames,
|
columnNames: columnNames,
|
||||||
uniqueObjectsMap: make(map[string]interface{}),
|
uniqueObjectsMap: make(map[string]interface{}),
|
||||||
}
|
}
|
||||||
|
|
@ -47,6 +49,8 @@ func Execute(db *sql.DB, query string, destinationPtr interface{}) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scanContext.rowNum++
|
||||||
|
|
||||||
columnProcessed := make([]bool, len(columnTypes))
|
columnProcessed := make([]bool, len(columnTypes))
|
||||||
|
|
||||||
if destinationType.Elem().Kind() == reflect.Slice {
|
if destinationType.Elem().Kind() == reflect.Slice {
|
||||||
|
|
@ -70,6 +74,7 @@ func Execute(db *sql.DB, query string, destinationPtr interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
type scanContext struct {
|
type scanContext struct {
|
||||||
|
rowNum int
|
||||||
columnNames []string
|
columnNames []string
|
||||||
uniqueObjectsMap map[string]interface{}
|
uniqueObjectsMap map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
@ -107,13 +112,13 @@ func getGroupKey(scanContext *scanContext, row []interface{}, structType reflect
|
||||||
columnName := snaker.CamelToSnake(structName) + "." + snaker.CamelToSnake(fieldName)
|
columnName := snaker.CamelToSnake(structName) + "." + snaker.CamelToSnake(fieldName)
|
||||||
|
|
||||||
//fmt.Println(fieldName)
|
//fmt.Println(fieldName)
|
||||||
rowIndex := getIndex(scanContext.columnNames, columnName)
|
index := getIndex(scanContext.columnNames, columnName)
|
||||||
|
|
||||||
if rowIndex < 0 {
|
if index < 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
rowValue := reflect.ValueOf(row[rowIndex])
|
rowValue := reflect.ValueOf(row[index])
|
||||||
|
|
||||||
groupKey = groupKey + reflectValueToString(rowValue)
|
groupKey = groupKey + reflectValueToString(rowValue)
|
||||||
} else if !isDbBaseType(fieldType.Type) {
|
} else if !isDbBaseType(fieldType.Type) {
|
||||||
|
|
@ -161,7 +166,13 @@ func mapRowToSlice(scanContext *scanContext, groupKey string, columnProcessed []
|
||||||
|
|
||||||
structType := getSliceStructType(destinationPtr)
|
structType := getSliceStructType(destinationPtr)
|
||||||
|
|
||||||
groupKey = groupKey + ":" + getGroupKey(scanContext, row, structType)
|
structGroupKey := getGroupKey(scanContext, row, structType)
|
||||||
|
|
||||||
|
if structGroupKey == "" {
|
||||||
|
structGroupKey = strconv.Itoa(scanContext.rowNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
groupKey = groupKey + ":" + structGroupKey
|
||||||
|
|
||||||
objPtr, ok := scanContext.uniqueObjectsMap[groupKey]
|
objPtr, ok := scanContext.uniqueObjectsMap[groupKey]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -97,12 +97,12 @@ func (t *Table) getColumn(name string) (NonAliasColumn, error) {
|
||||||
|
|
||||||
// Returns a pseudo column representation of the column name. Error checking
|
// Returns a pseudo column representation of the column name. Error checking
|
||||||
// is deferred to SerializeSql.
|
// is deferred to SerializeSql.
|
||||||
func (t *Table) C(name string) NonAliasColumn {
|
//func (t *Table) C(name string) NonAliasColumn {
|
||||||
return &deferredLookupColumn{
|
// return &deferredLookupColumn{
|
||||||
table: t,
|
// table: t,
|
||||||
colName: name,
|
// colName: name,
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
// Returns all columns for a table as a slice of projections
|
// Returns all columns for a table as a slice of projections
|
||||||
func (t *Table) Projections() []Projection {
|
func (t *Table) Projections() []Projection {
|
||||||
|
|
|
||||||
|
|
@ -219,6 +219,68 @@ func TestJoinQuerySliceWithPtrs(t *testing.T) {
|
||||||
assert.Equal(t, len(*filmsPerLanguageWithPtrs[0].Films), int(limit))
|
assert.Equal(t, len(*filmsPerLanguageWithPtrs[0].Films), int(limit))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSelect_WithoutUniqueColumnSelected(t *testing.T) {
|
||||||
|
query := Customer.Select(Customer.FirstName, Customer.LastName, Customer.Email)
|
||||||
|
|
||||||
|
customers := []model.Customer{}
|
||||||
|
|
||||||
|
err := query.Execute(db, &customers)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
//spew.Dump(customers)
|
||||||
|
|
||||||
|
assert.Equal(t, len(customers), 599)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectOrderByAscDesc(t *testing.T) {
|
||||||
|
customersAsc := []model.Customer{}
|
||||||
|
|
||||||
|
err := Customer.Select(Customer.CustomerID, Customer.FirstName, Customer.LastName).
|
||||||
|
OrderBy(Customer.FirstName.Asc()).
|
||||||
|
Execute(db, &customersAsc)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
firstCustomerAsc := customersAsc[0]
|
||||||
|
lastCustomerAsc := customersAsc[len(customersAsc)-1]
|
||||||
|
|
||||||
|
customersDesc := []model.Customer{}
|
||||||
|
err = Customer.Select(Customer.CustomerID, Customer.FirstName, Customer.LastName).
|
||||||
|
OrderBy(Customer.FirstName.Desc()).
|
||||||
|
Execute(db, &customersDesc)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
firstCustomerDesc := customersDesc[0]
|
||||||
|
lastCustomerDesc := customersDesc[len(customersAsc)-1]
|
||||||
|
|
||||||
|
assert.DeepEqual(t, firstCustomerAsc, lastCustomerDesc)
|
||||||
|
assert.DeepEqual(t, lastCustomerAsc, firstCustomerDesc)
|
||||||
|
|
||||||
|
customersAscDesc := []model.Customer{}
|
||||||
|
err = Customer.Select(Customer.CustomerID, Customer.FirstName, Customer.LastName).
|
||||||
|
OrderBy(Customer.FirstName.Asc(), Customer.LastName.Desc()).
|
||||||
|
Execute(db, &customersAscDesc)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
customerAscDesc326 := model.Customer{
|
||||||
|
CustomerID: 67,
|
||||||
|
FirstName: "Kelly",
|
||||||
|
LastName: "Torres",
|
||||||
|
}
|
||||||
|
|
||||||
|
customerAscDesc327 := model.Customer{
|
||||||
|
CustomerID: 546,
|
||||||
|
FirstName: "Kelly",
|
||||||
|
LastName: "Knott",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.DeepEqual(t, customerAscDesc326, customersAscDesc[326])
|
||||||
|
assert.DeepEqual(t, customerAscDesc327, customersAscDesc[327])
|
||||||
|
}
|
||||||
|
|
||||||
func int32Ptr(i int32) *int32 {
|
func int32Ptr(i int32) *int32 {
|
||||||
return &i
|
return &i
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue