Order by column simplified.

This commit is contained in:
sub0Zero 2019-03-15 21:55:43 +01:00 committed by zer0sub
parent ba3cd37734
commit 8049b2ec01
4 changed files with 167 additions and 75 deletions

View file

@ -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)
//}

View file

@ -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]

View file

@ -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 {

View file

@ -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
} }