Query group scan refactoring.

This commit is contained in:
zer0sub 2019-05-20 17:37:55 +02:00
parent 5ed7cf2b1c
commit e656fb610c
9 changed files with 1273 additions and 398 deletions

View file

@ -54,7 +54,7 @@ func (c ColumnInfo) ToSqlBuilderColumnType() string {
func (c ColumnInfo) ToGoType() string {
typeStr := c.GoBaseType()
if c.IsNullable || c.TableInfo.IsForeignKey(c.Name) {
if c.IsNullable {
return "*" + typeStr
}
@ -62,9 +62,6 @@ func (c ColumnInfo) ToGoType() string {
}
func (c ColumnInfo) GoBaseType() string {
if forignKeyTable, ok := c.TableInfo.ForeignTableMap[c.Name]; ok {
return snaker.SnakeToCamel(forignKeyTable)
} else {
switch c.DataType {
case "USER-DEFINED":
return snaker.SnakeToCamel(c.EnumName)
@ -94,15 +91,11 @@ func (c ColumnInfo) GoBaseType() string {
fmt.Println("Unknown go map type: " + c.DataType + ", " + c.EnumName + ", using string instead.")
return "string"
}
}
}
func (c ColumnInfo) ToGoDMFieldName() string {
if forignKeyTable, ok := c.TableInfo.ForeignTableMap[c.Name]; ok {
return snaker.SnakeToCamel(forignKeyTable)
} else {
return snaker.SnakeToCamel(c.Name)
}
}
func (c ColumnInfo) ToGoFieldName() string {

9
go-sqlbuilder.iml Normal file
View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View file

@ -8,24 +8,64 @@ import (
"github.com/serenize/snaker"
"github.com/sub0zero/go-sqlbuilder/types"
"reflect"
"regexp"
"strconv"
"strings"
"time"
)
func Query(db types.Db, query string, args []interface{}, destinationPtr interface{}) error {
if destinationPtr == nil {
return errors.New("Destination is nil. ")
}
destinationPtrType := reflect.TypeOf(destinationPtr)
if destinationPtrType.Kind() != reflect.Ptr {
return errors.New("Destination has to be a pointer to slice or pointer to struct. ")
}
if destinationPtrType.Elem().Kind() == reflect.Slice {
return queryToSlice(db, query, args, destinationPtr)
} else if destinationPtrType.Elem().Kind() == reflect.Struct {
tempSlicePtrValue := reflect.New(reflect.SliceOf(destinationPtrType))
tempSliceValue := tempSlicePtrValue.Elem()
err := queryToSlice(db, query, args, tempSlicePtrValue.Interface())
if err != nil {
return err
}
fmt.Println("TEMP SLICE SIZE: ", tempSliceValue.Len())
if tempSliceValue.Len() == 0 {
return nil
}
structValue := reflect.ValueOf(destinationPtr).Elem()
firstTempStruct := tempSliceValue.Index(0).Elem()
if structValue.Type().AssignableTo(firstTempStruct.Type()) {
structValue.Set(tempSliceValue.Index(0).Elem())
}
return nil
} else {
return errors.New("Unsupported destination type. ")
}
}
func queryToSlice(db types.Db, query string, args []interface{}, slicePtr interface{}) error {
if db == nil {
return errors.New("db is nil")
}
if destinationPtr == nil {
return errors.New("Destination is nil ")
if slicePtr == nil {
return errors.New("Destination is nil. ")
}
destinationType := reflect.TypeOf(destinationPtr)
if destinationType.Kind() != reflect.Ptr {
return errors.New("Destination has to be a pointer to slice or pointer to struct ")
destinationType := reflect.TypeOf(slicePtr)
if destinationType.Kind() != reflect.Ptr && destinationType.Elem().Kind() != reflect.Slice {
return errors.New("Destination has to be a pointer to slice. ")
}
rows, err := db.Query(query, args...)
@ -35,16 +75,17 @@ func Query(db types.Db, query string, args []interface{}, destinationPtr interfa
}
defer rows.Close()
columnNames, _ := rows.Columns()
columnTypes, _ := rows.ColumnTypes()
scanContext, err := newScanContext(rows)
scanContext := &scanContext{
row: createScanValue(columnTypes),
columnNames: columnNames,
uniqueObjectsMap: make(map[string]interface{}),
if err != nil {
return err
}
//spew.Dump(columnTypes)
if len(scanContext.row) == 0 {
return nil
}
groupTime := time.Duration(0)
for rows.Next() {
err := rows.Scan(scanContext.row...)
@ -55,17 +96,19 @@ func Query(db types.Db, query string, args []interface{}, destinationPtr interfa
scanContext.rowNum++
if destinationType.Elem().Kind() == reflect.Slice {
err := mapRowToSlice(scanContext, "", map[string]bool{}, destinationPtr, nil)
begin := time.Now()
_, err = mapRowToSlice(scanContext, "", reflect.ValueOf(slicePtr), nil)
if err != nil {
return err
}
} else if destinationType.Elem().Kind() == reflect.Struct {
return mapRowToStruct(scanContext, "", map[string]bool{}, destinationPtr, nil)
}
groupTime += time.Now().Sub(begin)
}
fmt.Println(groupTime.String())
err = rows.Err()
if err != nil {
@ -82,68 +125,78 @@ func Query(db types.Db, query string, args []interface{}, destinationPtr interfa
return nil
}
type scanContext struct {
rowNum int
columnNames []string
func mapRowToSlice(scanContext *scanContext, groupKey string, slicePtrValue reflect.Value, structField *reflect.StructField) (updated bool, err error) {
row []interface{}
uniqueObjectsMap map[string]interface{}
}
sliceElemType := getSliceElemType(slicePtrValue)
func getColumnTypeName(columnName string) (string, error) {
split := strings.Split(columnName, ".")
if len(split) != 2 {
return "", errors.New("Invalid column name")
if isGoBaseType(sliceElemType) {
index := 0
if structField != nil {
columnName := getRefTableNameFrom(structField)
index = getIndex(scanContext.columnNames, columnName)
if index < 0 {
return
}
}
rowElemPtr := scanContext.rowElemPtr(index)
if !rowElemPtr.IsNil() {
appendElemToSlice(slicePtrValue, rowElemPtr)
}
return split[0], nil
}
return
}
func allProcessed(arr []bool) bool {
for _, b := range arr {
if !b {
return false
if sliceElemType.Kind() != reflect.Struct {
return false, errors.New("Unsupported dest type: " + structField.Name + " " + structField.Type.String())
}
structGroupKey := getGroupKey(scanContext, sliceElemType, structField)
if structGroupKey == "" {
structGroupKey = "|ROW: " + strconv.Itoa(scanContext.rowNum) + "|"
}
groupKey = groupKey + ":" + structGroupKey
index, ok := scanContext.uniqueObjectsMap[groupKey]
if ok {
structPtrValue := getSliceElemPtrAt(slicePtrValue, index)
return mapRowToStruct(scanContext, groupKey, structPtrValue, structField)
} else {
destinationStructPtr := newElemPtrValueForSlice(slicePtrValue)
updated, err = mapRowToStruct(scanContext, groupKey, destinationStructPtr, structField)
if err != nil {
return
}
if updated {
scanContext.uniqueObjectsMap[groupKey] = slicePtrValue.Elem().Len()
appendElemToSlice(slicePtrValue, destinationStructPtr)
}
}
return true
return
}
func getType(reflectType reflect.Type) string {
var structType reflect.Type
if reflectType.Kind() == reflect.Struct {
structType = reflectType
} else if reflectType.Kind() == reflect.Ptr && reflectType.Elem().Kind() == reflect.Struct {
structType = reflectType.Elem()
}
return structType.Name()
}
func getGroupKey(scanContext *scanContext, typesProcessed map[string]bool, structType reflect.Type, structField *reflect.StructField) string {
tableName := getTableAlias(structField)
//fmt.Println("Group: " + tableName)
func getGroupKey(scanContext *scanContext, structType reflect.Type, structField *reflect.StructField) string {
tableName := getRefTableNameFrom(structField)
if tableName == "" {
tableName = snaker.CamelToSnake(structType.Name())
}
//fmt.Println(tableName)
if typesProcessed[tableName] {
return ""
}
typesProcessed[tableName] = true
groupKeys := []string{}
for i := 0; i < structType.NumField(); i++ {
field := structType.Field(i)
////fmt.Println(field.Tag)
if !isDbBaseType(field.Type) {
if !isGoBaseType(field.Type) {
var structType reflect.Type
if field.Type.Kind() == reflect.Struct {
structType = field.Type
@ -153,11 +206,7 @@ func getGroupKey(scanContext *scanContext, typesProcessed map[string]bool, struc
continue
}
//spew.Dump(structType)
structGroupKey := getGroupKey(scanContext, typesProcessed, structType, &field)
//groupKey = strings.Join([]string{structGroupKey, groupKey}, ":")
structGroupKey := getGroupKey(scanContext, structType, &field)
if structGroupKey != "" {
groupKeys = append(groupKeys, structGroupKey)
@ -166,15 +215,14 @@ func getGroupKey(scanContext *scanContext, typesProcessed map[string]bool, struc
fieldName := field.Name
columnName := tableName + "." + snaker.CamelToSnake(fieldName)
//fmt.Println(fieldName)
index := getIndex(scanContext.columnNames, columnName)
if index < 0 {
continue
}
cellValue := cellValue(scanContext.row, index)
subKey := reflectValueToString(cellValue)
cellValue := scanContext.rowElem(index)
subKey := valueToString(cellValue)
if subKey != "" {
groupKeys = append(groupKeys, subKey)
@ -186,35 +234,13 @@ func getGroupKey(scanContext *scanContext, typesProcessed map[string]bool, struc
return ""
}
return "|" + structType.Name() + "(" + strings.Join(groupKeys, ", ") + ")|"
groupKey := "{" + structType.Name() + "(" + strings.Join(groupKeys, ",") + ")}"
return groupKey
}
func cellValue(row []interface{}, index int) interface{} {
//spew.Dump(row[index])
valuer, ok := row[index].(driver.Valuer)
if !ok {
//fmt.Println("____________________")
//spew.Dump(row[index])
panic("Scan value doesn't implement driver.Valuer")
}
//spew.Dump(valuer)
value, err := valuer.Value()
if err != nil {
panic(err)
}
//spew.Dump(value)
return value
}
func getSliceStructType(slicePtr interface{}) reflect.Type {
sliceTypePtr := reflect.TypeOf(slicePtr)
func getSliceElemType(slicePtrValue reflect.Value) reflect.Type {
sliceTypePtr := slicePtrValue.Type()
elemType := sliceTypePtr.Elem().Elem()
@ -225,148 +251,101 @@ func getSliceStructType(slicePtr interface{}) reflect.Type {
return elemType
}
func cloneProcessedMap(processedMap map[string]bool) map[string]bool {
newMap := make(map[string]bool, len(processedMap))
func getSliceElemPtrAt(slicePtrValue reflect.Value, index int) reflect.Value {
sliceValue := slicePtrValue.Elem()
elem := sliceValue.Index(index)
for k, v := range newMap {
newMap[k] = v
if elem.Kind() == reflect.Ptr {
return elem
}
return newMap
return elem.Addr()
}
func mapRowToSlice(scanContext *scanContext, groupKey string, typesProcessed map[string]bool, destinationPtr interface{}, structField *reflect.StructField) error {
var err error
func appendElemToSlice(slicePtrValue reflect.Value, objPtrValue reflect.Value) {
if slicePtrValue.IsNil() {
panic("Slice is nil")
}
sliceValue := slicePtrValue.Elem()
sliceElemType := sliceValue.Type().Elem()
structType := getSliceStructType(destinationPtr)
newElemValue := objPtrValue
structGroupKey := getGroupKey(scanContext, cloneProcessedMap(typesProcessed), structType, structField)
if structGroupKey == "" {
structGroupKey = "|ROW: " + strconv.Itoa(scanContext.rowNum) + "|"
if sliceElemType.Kind() != reflect.Ptr {
newElemValue = objPtrValue.Elem()
}
groupKey = groupKey + ":" + structGroupKey
//fmt.Println(groupKey)
objPtr, ok := scanContext.uniqueObjectsMap[groupKey]
if ok {
err = mapRowToStruct(scanContext, groupKey, typesProcessed, objPtr, structField)
if err != nil {
return err
if newElemValue.Type().AssignableTo(sliceElemType) {
sliceValue.Set(reflect.Append(sliceValue, newElemValue))
}
} else {
destinationStructPtr := newElemForSlice(destinationPtr)
err = mapRowToStruct(scanContext, groupKey, typesProcessed, destinationStructPtr, structField)
if err != nil {
return err
}
elemPtr := appendElemToSlice(destinationPtr, destinationStructPtr)
scanContext.uniqueObjectsMap[groupKey] = elemPtr
}
return err
}
func appendElemToSlice(slice interface{}, objPtr interface{}) interface{} {
sliceValue := reflect.ValueOf(slice).Elem()
elemType := sliceValue.Type().Elem()
if elemType.Kind() == reflect.Ptr {
sliceValue.Set(reflect.Append(sliceValue, reflect.ValueOf(objPtr)))
return sliceValue.Index(sliceValue.Len() - 1).Interface()
}
sliceValue.Set(reflect.Append(sliceValue, reflect.ValueOf(objPtr).Elem()))
return sliceValue.Index(sliceValue.Len() - 1).Addr().Interface()
}
func newElemForSlice(destinationSlicePtr interface{}) interface{} {
destinationSliceType := reflect.TypeOf(destinationSlicePtr).Elem()
func newElemPtrValueForSlice(slicePtrValue reflect.Value) reflect.Value {
destinationSliceType := slicePtrValue.Type().Elem()
elemType := destinationSliceType.Elem()
if elemType.Kind() == reflect.Ptr {
return reflect.New(elemType.Elem()).Interface()
return reflect.New(elemType.Elem())
}
return reflect.New(elemType).Interface()
return reflect.New(elemType)
}
func mapRowToDestinationValue(scanContext *scanContext, groupKey string, typesProcessed map[string]bool, dest reflect.Value, structField *reflect.StructField) error {
if dest.Kind() == reflect.Struct {
err := mapRowToStruct(scanContext, groupKey, typesProcessed, dest.Addr().Interface(), structField)
if err != nil {
return err
func mapRowToDestinationPtr(scanContext *scanContext, groupKey string, destPtrValue reflect.Value, structField *reflect.StructField) (updated bool, err error) {
if destPtrValue.Kind() != reflect.Ptr {
return false, errors.New("Internal error. ")
}
} else if dest.Kind() == reflect.Slice {
err := mapRowToSlice(scanContext, groupKey, typesProcessed, dest.Addr().Interface(), structField)
if err != nil {
return err
destValueKind := destPtrValue.Elem().Kind()
if destValueKind == reflect.Struct {
return mapRowToStruct(scanContext, groupKey, destPtrValue, structField)
} else if destValueKind == reflect.Slice {
return mapRowToSlice(scanContext, groupKey, destPtrValue, structField)
} else {
return false, errors.New("Unsupported dest type: " + structField.Name + " " + structField.Type.String())
}
}
func mapRowToDestinationValue(scanContext *scanContext, groupKey string, dest reflect.Value, structField *reflect.StructField) (updated bool, err error) {
var destPtrValue reflect.Value
if dest.Kind() != reflect.Ptr {
destPtrValue = dest.Addr()
} else if dest.Kind() == reflect.Ptr {
elemType := dest.Type().Elem()
if elemType.Kind() == reflect.Struct {
var structValuePtr reflect.Value
if dest.IsNil() {
structValuePtr = reflect.New(elemType)
destPtrValue = reflect.New(dest.Type().Elem())
} else {
return nil
destPtrValue = dest
}
} else {
return false, errors.New("Internal error. ")
}
err := mapRowToStruct(scanContext, groupKey, typesProcessed, structValuePtr.Interface(), structField)
updated, err = mapRowToDestinationPtr(scanContext, groupKey, destPtrValue, structField)
if err != nil {
return err
return
}
if structValuePtr.Elem().Interface() != reflect.New(elemType).Elem().Interface() {
dest.Set(structValuePtr)
if dest.Kind() == reflect.Ptr && dest.IsNil() && updated {
dest.Set(destPtrValue)
}
} else if elemType.Kind() == reflect.Slice {
var sliceValuePtr reflect.Value
if dest.IsNil() {
sliceValuePtr = reflect.New(elemType)
} else {
sliceValuePtr = dest
}
err := mapRowToSlice(scanContext, groupKey, typesProcessed, sliceValuePtr.Interface(), structField)
if err != nil {
return err
}
if sliceValuePtr.Elem().Len() > 0 {
dest.Set(sliceValuePtr)
}
} else {
return errors.New("Unsuported field type: " + dest.Type().Name())
}
} else {
return errors.New("Unsuported field type: " + dest.Type().Name())
}
return nil
return
}
func getTableAlias(structField *reflect.StructField) string {
func getRefTableNameFrom(structField *reflect.StructField) string {
if structField == nil {
return ""
}
re := regexp.MustCompile(`sqlbuilder:"(.*?)"`)
tagMatch := re.FindStringSubmatch(string(structField.Tag))
if tagMatch != nil && len(tagMatch) == 2 && tagMatch[1] != "" {
return tagMatch[1]
tagOverwriteName := structField.Tag.Get("sqlbuilder")
if tagOverwriteName != "" {
return tagOverwriteName
}
if !structField.Anonymous {
@ -398,33 +377,20 @@ func getTableAlias(structField *reflect.StructField) string {
return snaker.CamelToSnake(elemType)
}
func mapRowToStruct(scanContext *scanContext, groupKey string, typesProcessed map[string]bool, destinationPtr interface{}, structField *reflect.StructField) error {
structType := reflect.TypeOf(destinationPtr).Elem()
structValue := reflect.ValueOf(destinationPtr).Elem()
func mapRowToStruct(scanContext *scanContext, groupKey string, structPtrValue reflect.Value, structField *reflect.StructField) (updated bool, err error) {
structType := structPtrValue.Type().Elem()
structValue := structPtrValue.Elem()
tableName := getTableAlias(structField)
tableName := getRefTableNameFrom(structField)
if tableName == "" {
tableName = snaker.CamelToSnake(structType.Name())
}
//fmt.Println("map -", tableName)
if typesProcessed[tableName] {
//fmt.Println("Already processed")
return nil
}
typesProcessed[tableName] = true
for i := 0; i < structType.NumField(); i++ {
field := structType.Field(i)
fieldValue := structValue.Field(i)
//fieldTypeName := field.Name
//fmt.Println("---------------", fieldTypeName,)
//spew.Dump(field.Type)
fieldName := field.Name
if scannerValue, ok := implementsScanner(fieldValue); ok {
@ -434,38 +400,53 @@ func mapRowToStruct(scanContext *scanContext, groupKey string, typesProcessed ma
continue
}
//spew.Dump(scannerValue.Interface())
if scannerValue.IsNil() {
initializePtrValue(scannerValue)
}
initializeValueIfNil(fieldValue)
scanner := scannerValue.Interface().(sql.Scanner)
err := scanner.Scan(cellValue)
err = scanner.Scan(cellValue)
if err != nil {
return err
return
}
} else if !isDbBaseType(field.Type) {
//var fieldValueInterface interface{}
err := mapRowToDestinationValue(scanContext, groupKey, typesProcessed, fieldValue, &field)
updated = true
} else if !isGoBaseType(field.Type) {
var changed bool
changed, err = mapRowToDestinationValue(scanContext, groupKey, fieldValue, &field)
if err != nil {
return err
return
}
if changed {
updated = true
}
} else {
cellValue := getCellValue(scanContext, tableName, fieldName)
//spew.Dump(cellValue)
//spew.Dump(rowElem)
//spew.Dump(rowColumnValue, fieldValue)
if cellValue != nil {
updated = true
initializeValueIfNil(fieldValue)
setReflectValue(reflect.ValueOf(cellValue), fieldValue)
}
}
}
return nil
return
}
func initializeValueIfNil(value reflect.Value) {
if !value.IsValid() || !value.CanSet() {
return
}
if value.Type().Kind() == reflect.Slice && value.IsNil() {
value.Set(reflect.New(value.Type()).Elem())
} else if value.Kind() == reflect.Ptr && value.IsNil() {
value.Set(reflect.New(value.Type().Elem()))
}
}
func implementsScanner(value reflect.Value) (reflect.Value, bool) {
@ -480,12 +461,6 @@ func implementsScanner(value reflect.Value) (reflect.Value, bool) {
return value, false
}
func initializePtrValue(value reflect.Value) {
if value.Kind() == reflect.Ptr {
value.Set(reflect.New(value.Type().Elem()))
}
}
func getCellValue(scanContext *scanContext, tableName, fieldName string) interface{} {
columnName := ""
@ -495,28 +470,22 @@ func getCellValue(scanContext *scanContext, tableName, fieldName string) interfa
columnName = tableName + "." + snaker.CamelToSnake(fieldName)
}
//columnName := snaker.CamelToSnake(fieldName)
////fmt.Println(columnName)
index := getIndex(scanContext.columnNames, columnName)
if index < 0 {
return nil
}
return cellValue(scanContext.row, index)
return scanContext.rowElem(index)
}
func reflectValueToString(val interface{}) string {
//spew.Dump(val)
func valueToString(val interface{}) string {
if val == nil {
return ""
}
value := reflect.ValueOf(val)
//if !value.IsValid()
var valueInterface interface{}
if value.Kind() == reflect.Ptr {
valueInterface = value.Elem().Interface()
@ -536,10 +505,7 @@ var floatType = reflect.TypeOf(1.0)
var stringType = reflect.TypeOf("str")
var intType = reflect.TypeOf(1)
func isDbBaseType(objType reflect.Type) bool {
//isBaseType := objType == timeType || floatType == objType || stringType == objType || intType == objType
//isPtrToBaseType := objType.Kind() == reflect.Ptr && (objType.Elem() == timeType || floatType == objType.Elem() ||
// stringType == objType.Elem() || intType == objType.Elem())
func isGoBaseType(objType reflect.Type) bool {
typeStr := objType.String()
switch typeStr {
@ -548,7 +514,6 @@ func isDbBaseType(objType reflect.Type) bool {
return true
}
//return isBaseType || isPtrToBaseType
return false
}
@ -604,8 +569,6 @@ var nullBoolType = reflect.TypeOf(sql.NullBool{})
var nullTimeType = reflect.TypeOf(NullTime{})
func newScanType(columnType *sql.ColumnType) reflect.Type {
//spew.Dump(columnType)
//fmt.Println(columnType.DatabaseTypeName())
switch columnType.DatabaseTypeName() {
case "INT2":
return nullInt16Type
@ -627,3 +590,67 @@ func newScanType(columnType *sql.ColumnType) reflect.Type {
panic("Unknown column database type " + columnType.DatabaseTypeName())
}
}
type scanContext struct {
rowNum int
columnNames []string
row []interface{}
uniqueObjectsMap map[string]int
groupKeyMap map[string]string
}
func newScanContext(rows *sql.Rows) (*scanContext, error) {
columnNames, err := rows.Columns()
if err != nil {
return nil, err
}
columnTypes, err := rows.ColumnTypes()
if err != nil {
return nil, err
}
return &scanContext{
row: createScanValue(columnTypes),
columnNames: columnNames,
uniqueObjectsMap: make(map[string]int),
groupKeyMap: make(map[string]string),
}, nil
}
func (s *scanContext) rowElem(index int) interface{} {
valuer, ok := s.row[index].(driver.Valuer)
if !ok {
panic("Scan value doesn't implement driver.Valuer")
}
value, err := valuer.Value()
if err != nil {
panic(err)
}
return value
}
func (s *scanContext) rowElemPtr(index int) reflect.Value {
rowElem := s.rowElem(index)
rowElemValue := reflect.ValueOf(rowElem)
if rowElemValue.Kind() == reflect.Ptr {
return rowElemValue
}
if rowElemValue.CanAddr() {
return rowElemValue.Addr()
}
newElem := reflect.New(rowElemValue.Type())
newElem.Elem().Set(rowElemValue)
return newElem
}

View file

@ -58,6 +58,10 @@ func NewNumericFunc(name string, expressions ...expression) numericExpression {
return numericFunc
}
func COUNT(expression numericExpression) numericExpression {
return NewNumericFunc("COUNT", expression)
}
func MAX(expression numericExpression) numericExpression {
return NewNumericFunc("MAX", expression)
}

View file

@ -14,6 +14,7 @@ type numericExpression interface {
GtEq(rhs numericExpression) boolExpression
GtEqL(literal interface{}) boolExpression
Lt(rhs numericExpression) boolExpression
LtEq(rhs numericExpression) boolExpression
LtEqL(literal interface{}) boolExpression
@ -55,6 +56,10 @@ func (n *numericInterfaceImpl) GtEqL(literal interface{}) boolExpression {
return GtEq(n.parent, Literal(literal))
}
func (n *numericInterfaceImpl) Lt(expression numericExpression) boolExpression {
return Lt(n.parent, expression)
}
func (n *numericInterfaceImpl) LtEq(expression numericExpression) boolExpression {
return LtEq(n.parent, expression)
}

View file

@ -4,6 +4,7 @@ import (
"database/sql"
"fmt"
_ "github.com/lib/pq"
"github.com/pkg/profile"
"github.com/sub0zero/go-sqlbuilder/generator"
"gotest.tools/assert"
"os"
@ -31,6 +32,8 @@ var db *sql.DB
func TestMain(m *testing.M) {
fmt.Println("Begin")
defer profile.Start().Stop()
var err error
db, err = sql.Open("postgres", connectString)
if err != nil {
@ -66,7 +69,36 @@ CREATE TABLE IF NOT EXISTS test_sample.link (
name VARCHAR (255) NOT NULL,
description VARCHAR (255),
rel VARCHAR (50)
);`
);
DROP TABLE IF EXISTS test_sample.employee;
CREATE TABLE test_sample.employee (
employee_id INT PRIMARY KEY,
first_name VARCHAR (255) NOT NULL,
last_name VARCHAR (255) NOT NULL,
manager_id INT,
FOREIGN KEY (manager_id)
REFERENCES test_sample.employee (employee_id)
ON DELETE CASCADE
);
INSERT INTO test_sample.employee (
employee_id,
first_name,
last_name,
manager_id
)
VALUES
(1, 'Windy', 'Hays', NULL),
(2, 'Ava', 'Christensen', 1),
(3, 'Hassan', 'Conner', 1),
(4, 'Anna', 'Reeves', 2),
(5, 'Sau', 'Norman', 2),
(6, 'Kelsie', 'Hays', 3),
(7, 'Tory', 'Goff', 3),
(8, 'Salley', 'Lester', 3);
`
result, err := db.Exec(linkTableCreate)
@ -78,6 +110,24 @@ CREATE TABLE IF NOT EXISTS test_sample.link (
}
func queryAll(t *testing.T, query string, args []interface{}) {
rows, err := db.Query(query, args...)
assert.NilError(t, err)
defer rows.Close()
for rows.Next() {
//err := rows.Scan(scanContext.row...)
//
//assert.NilError(t, err)
}
err = rows.Err()
assert.NilError(t, err)
}
func TestGenerateModel(t *testing.T) {
err := generator.Generate(folderPath, connectString, dbname, schemaName)

660
tests/scan_test.go Normal file
View file

@ -0,0 +1,660 @@
package tests
import (
. "github.com/sub0zero/go-sqlbuilder/sqlbuilder"
"github.com/sub0zero/go-sqlbuilder/tests/.test_files/dvd_rental/dvds/model"
. "github.com/sub0zero/go-sqlbuilder/tests/.test_files/dvd_rental/dvds/table"
"gotest.tools/assert"
"testing"
)
var query = Inventory.
SELECT(Inventory.AllColumns).
LIMIT(1).
ORDER_BY(Inventory.InventoryID)
func TestScanToInvalidDestination(t *testing.T) {
t.Run("nil dest", func(t *testing.T) {
err := query.Query(db, nil)
assert.Error(t, err, "Destination is nil. ")
})
t.Run("struct dest", func(t *testing.T) {
err := query.Query(db, struct{}{})
assert.Error(t, err, "Destination has to be a pointer to slice or pointer to struct. ")
})
t.Run("slice dest", func(t *testing.T) {
err := query.Query(db, []struct{}{})
assert.Error(t, err, "Destination has to be a pointer to slice or pointer to struct. ")
})
t.Run("slice of pointers to pointer dest", func(t *testing.T) {
err := query.Query(db, []**struct{}{})
assert.Error(t, err, "Destination has to be a pointer to slice or pointer to struct. ")
})
t.Run("map dest", func(t *testing.T) {
err := query.Query(db, []map[string]string{})
assert.Error(t, err, "Destination has to be a pointer to slice or pointer to struct. ")
})
}
func TestScanToValidDestination(t *testing.T) {
t.Run("pointer to struct", func(t *testing.T) {
err := query.Query(db, &struct{}{})
assert.NilError(t, err)
})
t.Run("pointer to slice", func(t *testing.T) {
err := query.Query(db, &[]struct{}{})
assert.NilError(t, err)
})
t.Run("pointer to slice of pointer to structs", func(t *testing.T) {
err := query.Query(db, &[]*struct{}{})
assert.NilError(t, err)
})
t.Run("pointer to slice of strings", func(t *testing.T) {
err := query.Query(db, &[]string{})
assert.NilError(t, err)
})
}
func TestScanToStruct(t *testing.T) {
query := Inventory.
SELECT(Inventory.AllColumns).
ORDER_BY(Inventory.InventoryID)
t.Run("one struct", func(t *testing.T) {
dest := model.Inventory{}
err := query.LIMIT(1).Query(db, &dest)
assert.NilError(t, err)
assert.DeepEqual(t, inventory1, dest)
})
t.Run("multiple structs, just first one used", func(t *testing.T) {
dest := model.Inventory{}
err := query.LIMIT(10).Query(db, &dest)
assert.NilError(t, err)
assert.DeepEqual(t, inventory1, dest)
})
t.Run("one struct", func(t *testing.T) {
dest := struct {
model.Inventory
}{}
err := query.LIMIT(1).Query(db, &dest)
assert.NilError(t, err)
assert.DeepEqual(t, inventory1, dest.Inventory)
})
t.Run("one struct", func(t *testing.T) {
dest := struct {
*model.Inventory
}{}
err := query.LIMIT(1).Query(db, &dest)
assert.NilError(t, err)
assert.DeepEqual(t, inventory1, *dest.Inventory)
})
t.Run("invalid dest", func(t *testing.T) {
dest := struct {
Inventory **model.Inventory
}{}
err := query.Query(db, &dest)
assert.Error(t, err, "Unsupported dest type: Inventory **model.Inventory")
})
t.Run("invalid dest 2", func(t *testing.T) {
dest := struct {
Inventory ***model.Inventory
}{}
err := query.Query(db, &dest)
assert.Error(t, err, "Unsupported dest type: Inventory ***model.Inventory")
})
}
func TestScanToNestedStruct(t *testing.T) {
query := Inventory.
INNER_JOIN(Film, Inventory.FilmID.Eq(Film.FilmID)).
INNER_JOIN(Store, Inventory.StoreID.Eq(Store.StoreID)).
SELECT(Inventory.AllColumns, Film.AllColumns, Store.AllColumns).
WHERE(Inventory.InventoryID.EqL(1))
t.Run("embedded structs", func(t *testing.T) {
dest := struct {
model.Inventory
model.Film
model.Store
}{}
err := query.Query(db, &dest)
assert.NilError(t, err)
assert.DeepEqual(t, dest.Inventory, inventory1)
assert.DeepEqual(t, dest.Film, film1)
assert.DeepEqual(t, dest.Store, store1)
})
t.Run("embedded pointer structs", func(t *testing.T) {
dest := struct {
*model.Inventory
*model.Film
*model.Store
}{}
err := query.Query(db, &dest)
assert.NilError(t, err)
assert.DeepEqual(t, *dest.Inventory, inventory1)
assert.DeepEqual(t, *dest.Film, film1)
assert.DeepEqual(t, *dest.Store, store1)
})
t.Run("embedded unused structs", func(t *testing.T) {
dest := struct {
model.Inventory
model.Actor //unused
}{}
err := query.Query(db, &dest)
assert.NilError(t, err)
assert.DeepEqual(t, dest.Inventory, inventory1)
assert.DeepEqual(t, dest.Actor, model.Actor{})
})
t.Run("embedded unused pointer structs", func(t *testing.T) {
dest := struct {
model.Inventory
*model.Actor //unused
}{}
err := query.Query(db, &dest)
assert.NilError(t, err)
assert.DeepEqual(t, dest.Inventory, inventory1)
assert.DeepEqual(t, dest.Actor, (*model.Actor)(nil))
})
t.Run("embedded unused pointer structs", func(t *testing.T) {
dest := struct {
model.Inventory
Actor *model.Actor //unused
}{}
err := query.Query(db, &dest)
assert.NilError(t, err)
assert.DeepEqual(t, dest.Inventory, inventory1)
assert.DeepEqual(t, dest.Actor, (*model.Actor)(nil))
})
t.Run("embedded pointer to selected column", func(t *testing.T) {
query := Inventory.
INNER_JOIN(Film, Inventory.FilmID.Eq(Film.FilmID)).
INNER_JOIN(Store, Inventory.StoreID.Eq(Store.StoreID)).
SELECT(Inventory.AllColumns, Film.AllColumns, Store.AllColumns, Literal("").AS("actor.first_name")).
WHERE(Inventory.InventoryID.EqL(1))
dest := struct {
model.Inventory
Actor *model.Actor //unused
}{}
err := query.Query(db, &dest)
assert.NilError(t, err)
assert.DeepEqual(t, dest.Inventory, inventory1)
assert.Assert(t, dest.Actor != nil)
})
t.Run("struct embedded unused pointer", func(t *testing.T) {
dest := struct {
model.Inventory
Actor *struct {
model.Actor
} //unused
}{}
err := query.Query(db, &dest)
assert.NilError(t, err)
assert.DeepEqual(t, dest.Inventory, inventory1)
assert.DeepEqual(t, dest.Actor, (*struct{ model.Actor })(nil))
})
t.Run("multiple embedded unused pointer", func(t *testing.T) {
dest := struct {
model.Inventory
Actor *struct {
model.Actor //unused
model.Language //unesed
}
}{}
err := query.Query(db, &dest)
assert.NilError(t, err)
assert.DeepEqual(t, dest.Inventory, inventory1)
assert.DeepEqual(t, dest.Actor, (*struct {
model.Actor
model.Language
})(nil))
})
t.Run("field not nil, embedded selected model", func(t *testing.T) {
dest := struct {
model.Inventory
Actor *struct {
model.Actor //unselected
model.Film //selected
}
}{}
err := query.Query(db, &dest)
assert.NilError(t, err)
assert.DeepEqual(t, dest.Inventory, inventory1)
assert.Assert(t, dest.Actor != nil)
assert.DeepEqual(t, dest.Actor.Actor, model.Actor{})
assert.DeepEqual(t, dest.Actor.Film, film1)
})
t.Run("field not nil, deeply nested selected model", func(t *testing.T) {
dest := struct {
model.Inventory
Actor *struct {
model.Actor //unselected
Film *struct {
*model.Film //selected
}
}
}{}
err := query.Query(db, &dest)
assert.NilError(t, err)
assert.DeepEqual(t, dest.Inventory, inventory1)
assert.Assert(t, dest.Actor != nil)
assert.Assert(t, dest.Actor.Film != nil)
assert.DeepEqual(t, dest.Actor.Film.Film, &film1)
})
t.Run("embedded structs", func(t *testing.T) {
query := Inventory.
INNER_JOIN(Film, Inventory.FilmID.Eq(Film.FilmID)).
INNER_JOIN(Store, Inventory.StoreID.Eq(Store.StoreID)).
INNER_JOIN(Language, Film.LanguageID.Eq(Language.LanguageID)).
SELECT(Inventory.AllColumns, Film.AllColumns, Store.AllColumns, Language.AllColumns).
WHERE(Inventory.InventoryID.EqL(1))
dest := struct {
model.Inventory
Film struct {
model.Film
Language model.Language
Language2 *model.Language
Language3 *model.Language `sqlbuilder:"language"`
Lang struct {
model.Language
}
Lang2 *struct {
model.Language
}
}
Store model.Store
}{}
err := query.Query(db, &dest)
assert.NilError(t, err)
assert.DeepEqual(t, dest.Inventory, inventory1)
assert.DeepEqual(t, dest.Film.Film, film1)
assert.DeepEqual(t, dest.Store, store1)
assert.DeepEqual(t, dest.Film.Language, language1)
assert.DeepEqual(t, dest.Film.Lang.Language, language1)
assert.DeepEqual(t, dest.Film.Lang2.Language, language1)
assert.DeepEqual(t, dest.Film.Language2, (*model.Language)(nil))
assert.DeepEqual(t, dest.Film.Language3, &language1)
})
}
func TestScanToSlice(t *testing.T) {
t.Run("slice of structs", func(t *testing.T) {
query := Inventory.
SELECT(Inventory.AllColumns).
ORDER_BY(Inventory.InventoryID).
LIMIT(10)
dest := []model.Inventory{}
err := query.Query(db, &dest)
assert.NilError(t, err)
assert.Equal(t, len(dest), 10)
assert.DeepEqual(t, dest[0], inventory1)
assert.DeepEqual(t, dest[1], inventory2)
})
t.Run("slice of complex structs", func(t *testing.T) {
query := Inventory.
INNER_JOIN(Film, Inventory.FilmID.Eq(Film.FilmID)).
INNER_JOIN(Store, Inventory.StoreID.Eq(Store.StoreID)).
SELECT(Inventory.AllColumns, Film.AllColumns, Store.AllColumns).
ORDER_BY(Inventory.InventoryID).
LIMIT(10)
t.Run("complex struct 1", func(t *testing.T) {
dest := []struct {
model.Inventory
model.Film
model.Store
}{}
err := query.Query(db, &dest)
assert.NilError(t, err)
assert.Equal(t, len(dest), 10)
assert.DeepEqual(t, dest[0].Inventory, inventory1)
assert.DeepEqual(t, dest[0].Film, film1)
assert.DeepEqual(t, dest[0].Store, store1)
assert.DeepEqual(t, dest[1].Inventory, inventory2)
})
t.Run("complex struct 2", func(t *testing.T) {
var dest []struct {
*model.Inventory
model.Film
*model.Store
}
err := query.Query(db, &dest)
assert.NilError(t, err)
assert.Equal(t, len(dest), 10)
assert.DeepEqual(t, dest[0].Inventory, &inventory1)
assert.DeepEqual(t, dest[0].Film, film1)
assert.DeepEqual(t, dest[0].Store, &store1)
assert.DeepEqual(t, dest[1].Inventory, &inventory2)
})
t.Run("complex struct 3", func(t *testing.T) {
var dest []struct {
Inventory model.Inventory
Film *model.Film
Store struct {
*model.Store
}
}
err := query.Query(db, &dest)
assert.NilError(t, err)
assert.Equal(t, len(dest), 10)
assert.DeepEqual(t, dest[0].Inventory, inventory1)
assert.DeepEqual(t, dest[0].Film, &film1)
assert.DeepEqual(t, dest[0].Store.Store, &store1)
assert.DeepEqual(t, dest[1].Inventory, inventory2)
})
t.Run("complex struct 4", func(t *testing.T) {
var dest []struct {
model.Film
Inventories []struct {
model.Inventory
model.Store
}
}
err := query.Query(db, &dest)
assert.NilError(t, err)
assert.Equal(t, len(dest), 2)
assert.DeepEqual(t, dest[0].Film, film1)
assert.DeepEqual(t, len(dest[0].Inventories), 8)
assert.DeepEqual(t, dest[0].Inventories[0].Inventory, inventory1)
assert.DeepEqual(t, dest[0].Inventories[0].Store, store1)
})
t.Run("complex struct 5", func(t *testing.T) {
var dest []struct {
model.Film
Inventories []struct {
model.Inventory
Rentals *[]model.Rental
Rentals2 []model.Rental
}
}
err := query.Query(db, &dest)
assert.NilError(t, err)
assert.Equal(t, len(dest), 2)
assert.DeepEqual(t, dest[0].Film, film1)
assert.Equal(t, len(dest[0].Inventories), 8)
assert.DeepEqual(t, dest[0].Inventories[0].Inventory, inventory1)
assert.Assert(t, dest[0].Inventories[0].Rentals == nil)
assert.Assert(t, dest[0].Inventories[0].Rentals2 == nil)
})
})
t.Run("slice of complex structs 2", func(t *testing.T) {
query := Country.
INNER_JOIN(City, City.CountryID.Eq(Country.CountryID)).
INNER_JOIN(Address, Address.CityID.Eq(City.CityID)).
INNER_JOIN(Customer, Customer.AddressID.Eq(Address.AddressID)).
SELECT(Country.AllColumns, City.AllColumns, Address.AllColumns, Customer.AllColumns).
ORDER_BY(Country.CountryID.ASC(), City.CityID.ASC(), Address.AddressID.ASC(), Customer.CustomerID.ASC()).
LIMIT(1000)
t.Run("dest1", func(t *testing.T) {
var dest []struct {
model.Country
Cities []struct {
model.City
Adresses []struct {
model.Address
Customer model.Customer
}
}
}
err := query.Query(db, &dest)
assert.NilError(t, err)
assert.Equal(t, len(dest), 108)
assert.DeepEqual(t, dest[100].Country, countryUk)
assert.Equal(t, len(dest[100].Cities), 8)
assert.DeepEqual(t, dest[100].Cities[2].City, cityLondon)
assert.Equal(t, len(dest[100].Cities[2].Adresses), 2)
assert.DeepEqual(t, dest[100].Cities[2].Adresses[0].Address, address256)
assert.DeepEqual(t, dest[100].Cities[2].Adresses[0].Customer, customer256)
assert.DeepEqual(t, dest[100].Cities[2].Adresses[1].Address, addres517)
assert.DeepEqual(t, dest[100].Cities[2].Adresses[1].Customer, customer512)
})
t.Run("dest1", func(t *testing.T) {
var dest []*struct {
*model.Country
Cities []*struct {
*model.City
Adresses *[]*struct {
*model.Address
Customer *model.Customer
}
}
}
err := query.Query(db, &dest)
assert.NilError(t, err)
assert.Equal(t, len(dest), 108)
assert.DeepEqual(t, dest[100].Country, &countryUk)
assert.Equal(t, len(dest[100].Cities), 8)
assert.DeepEqual(t, dest[100].Cities[2].City, &cityLondon)
assert.Equal(t, len(*dest[100].Cities[2].Adresses), 2)
assert.DeepEqual(t, (*dest[100].Cities[2].Adresses)[0].Address, &address256)
assert.DeepEqual(t, (*dest[100].Cities[2].Adresses)[0].Customer, &customer256)
assert.DeepEqual(t, (*dest[100].Cities[2].Adresses)[1].Address, &addres517)
assert.DeepEqual(t, (*dest[100].Cities[2].Adresses)[1].Customer, &customer512)
})
})
t.Run("dest1", func(t *testing.T) {
var dest []*struct {
*model.Country
Cities []**struct {
*model.City
}
}
err := query.Query(db, &dest)
assert.Error(t, err, "Unsupported dest type: Cities []**struct { *model.City }")
})
}
var address256 = model.Address{
AddressID: 256,
Address: "1497 Yuzhou Drive",
Address2: stringPtr(""),
District: "England",
CityID: 312,
PostalCode: stringPtr("3433"),
Phone: "246810237916",
LastUpdate: *timeWithoutTimeZone("2006-02-15 09:45:30", 0),
}
var addres517 = model.Address{
AddressID: 517,
Address: "548 Uruapan Street",
Address2: stringPtr(""),
District: "Ontario",
CityID: 312,
PostalCode: stringPtr("35653"),
Phone: "879347453467",
LastUpdate: *timeWithoutTimeZone("2006-02-15 09:45:30", 0),
}
var customer256 = model.Customer{
CustomerID: 252,
StoreID: 2,
FirstName: "Mattie",
LastName: "Hoffman",
Email: stringPtr("mattie.hoffman@sakilacustomer.org"),
AddressID: 256,
Activebool: true,
CreateDate: *timeWithoutTimeZone("2006-02-14 00:00:00", 0),
LastUpdate: timeWithoutTimeZone("2013-05-26 14:49:45.738", 0),
Active: int32Ptr(1),
}
var customer512 = model.Customer{
CustomerID: 512,
StoreID: 1,
FirstName: "Cecil",
LastName: "Vines",
Email: stringPtr("cecil.vines@sakilacustomer.org"),
AddressID: 517,
Activebool: true,
CreateDate: *timeWithoutTimeZone("2006-02-14 00:00:00", 0),
LastUpdate: timeWithoutTimeZone("2013-05-26 14:49:45.738", 0),
Active: int32Ptr(1),
}
var countryUk = model.Country{
CountryID: 102,
Country: "United Kingdom",
LastUpdate: *timeWithoutTimeZone("2006-02-15 09:44:00", 0),
}
var cityLondon = model.City{
CityID: 312,
City: "London",
CountryID: 102,
LastUpdate: *timeWithoutTimeZone("2006-02-15 09:45:25", 0),
}
var inventory1 = model.Inventory{
InventoryID: 1,
FilmID: 1,
StoreID: 1,
LastUpdate: *timeWithoutTimeZone("2006-02-15 10:09:17", 0),
}
var inventory2 = model.Inventory{
InventoryID: 2,
FilmID: 1,
StoreID: 1,
LastUpdate: *timeWithoutTimeZone("2006-02-15 10:09:17", 0),
}
var film1 = model.Film{
FilmID: 1,
Title: "Academy Dinosaur",
Description: stringPtr("A Epic Drama of a Feminist And a Mad Scientist who must Battle a Teacher in The Canadian Rockies"),
ReleaseYear: int32Ptr(2006),
LanguageID: 1,
RentalDuration: 6,
RentalRate: 0.99,
Length: int16Ptr(86),
ReplacementCost: 20.99,
Rating: &pgRating,
LastUpdate: *timeWithoutTimeZone("2013-05-26 14:50:58.951", 3),
SpecialFeatures: stringPtr("{\"Deleted Scenes\",\"Behind the Scenes\"}"),
Fulltext: "'academi':1 'battl':15 'canadian':20 'dinosaur':2 'drama':5 'epic':4 'feminist':8 'mad':11 'must':14 'rocki':21 'scientist':12 'teacher':17",
}
var store1 = model.Store{
StoreID: 1,
ManagerStaffID: 1,
AddressID: 1,
LastUpdate: *timeWithoutTimeZone("2006-02-15 09:57:12", 0),
}
var pgRating = model.MpaaRating_PG
var language1 = model.Language{
LanguageID: 1,
Name: "English ",
LastUpdate: *timeWithoutTimeZone("2006-02-15 10:02:19", 0),
}

View file

@ -5,6 +5,8 @@ import (
. "github.com/sub0zero/go-sqlbuilder/sqlbuilder"
"github.com/sub0zero/go-sqlbuilder/tests/.test_files/dvd_rental/dvds/model"
. "github.com/sub0zero/go-sqlbuilder/tests/.test_files/dvd_rental/dvds/table"
model2 "github.com/sub0zero/go-sqlbuilder/tests/.test_files/dvd_rental/test_sample/model"
. "github.com/sub0zero/go-sqlbuilder/tests/.test_files/dvd_rental/test_sample/table"
"gotest.tools/assert"
"testing"
)
@ -16,14 +18,12 @@ SELECT actor.actor_id AS "actor.actor_id",
actor.last_name AS "actor.last_name",
actor.last_update AS "actor.last_update"
FROM dvds.actor
WHERE actor.actor_id = 1
ORDER BY actor.actor_id ASC;
WHERE actor.actor_id = 1;
`
query := Actor.
SELECT(Actor.AllColumns).
WHERE(Actor.ActorID.EqL(1)).
ORDER_BY(Actor.ActorID.ASC())
WHERE(Actor.ActorID.EqL(1))
assertQuery(t, query, expectedSql, 1)
@ -79,8 +79,6 @@ LIMIT 30;
assert.NilError(t, err)
assert.Equal(t, len(dest), 30)
//spew.Dump(dest)
}
func TestSelect_ScanToSlice(t *testing.T) {
@ -159,30 +157,99 @@ LIMIT 12;
assertQuery(t, query, expectedSql, int64(1), int64(1), int64(10), int64(1), int64(2), int64(1), int64(12))
}
//func TestJoinQueryStruct(t *testing.T) {
//
// query := FilmActor.
// INNER_JOIN(Actor, FilmActor.ActorID.Eq(Actor.ActorID)).
// INNER_JOIN(Film, FilmActor.FilmID.Eq(Film.FilmID)).
// INNER_JOIN(Language, Film.LanguageID.Eq(Language.LanguageID)).
// SELECT(FilmActor.AllColumns, Film.AllColumns, Language.AllColumns, Actor.AllColumns).
// WHERE(FilmActor.ActorID.GtEq(1).AND(FilmActor.ActorID.LteLiteral(2)))
//
// queryStr, args, err := query.Sql()
// assert.NilError(t, err)
// assert.Equal(t, queryStr, `SELECT film_actor.actor_id AS "film_actor.actor_id", film_actor.film_id AS "film_actor.film_id", film_actor.last_update AS "film_actor.last_update",film.film_id AS "film.film_id", film.title AS "film.title", film.description AS "film.description", film.release_year AS "film.release_year", film.language_id AS "film.language_id", film.rental_duration AS "film.rental_duration", film.rental_rate AS "film.rental_rate", film.length AS "film.length", film.replacement_cost AS "film.replacement_cost", film.rating AS "film.rating", film.last_update AS "film.last_update", film.special_features AS "film.special_features", film.fulltext AS "film.fulltext",language.language_id AS "language.language_id", language.name AS "language.name", language.last_update AS "language.last_update",actor.actor_id AS "actor.actor_id", actor.first_name AS "actor.first_name", actor.last_name AS "actor.last_name", actor.last_update AS "actor.last_update" FROM dvds.film_actor JOIN dvds.actor ON film_actor.actor_id = actor.actor_id JOIN dvds.film ON film_actor.film_id = film.film_id JOIN dvds.language ON film.language_id = language.language_id WHERE (film_actor.actor_id>=1 AND film_actor.actor_id<=2)`)
//
// //fmt.Println(queryStr)
//
// filmActor := []model.FilmActor{}
//
// err = query.Execute(db, &filmActor)
//
// assert.NilError(t, err)
//
// //fmt.Println("ACTORS: --------------------")
// //spew.Dump(filmActor)
//}
func TestJoinQueryStruct(t *testing.T) {
expectedSql := `
SELECT film_actor.actor_id AS "film_actor.actor_id",
film_actor.film_id AS "film_actor.film_id",
film_actor.last_update AS "film_actor.last_update",
film.film_id AS "film.film_id",
film.title AS "film.title",
film.description AS "film.description",
film.release_year AS "film.release_year",
film.language_id AS "film.language_id",
film.rental_duration AS "film.rental_duration",
film.rental_rate AS "film.rental_rate",
film.length AS "film.length",
film.replacement_cost AS "film.replacement_cost",
film.rating AS "film.rating",
film.last_update AS "film.last_update",
film.special_features AS "film.special_features",
film.fulltext AS "film.fulltext",
language.language_id AS "language.language_id",
language.name AS "language.name",
language.last_update AS "language.last_update",
actor.actor_id AS "actor.actor_id",
actor.first_name AS "actor.first_name",
actor.last_name AS "actor.last_name",
actor.last_update AS "actor.last_update",
inventory.inventory_id AS "inventory.inventory_id",
inventory.film_id AS "inventory.film_id",
inventory.store_id AS "inventory.store_id",
inventory.last_update AS "inventory.last_update",
rental.rental_id AS "rental.rental_id",
rental.rental_date AS "rental.rental_date",
rental.inventory_id AS "rental.inventory_id",
rental.customer_id AS "rental.customer_id",
rental.return_date AS "rental.return_date",
rental.staff_id AS "rental.staff_id",
rental.last_update AS "rental.last_update"
FROM dvds.film_actor
JOIN dvds.actor ON film_actor.actor_id = actor.actor_id
JOIN dvds.film ON film_actor.film_id = film.film_id
JOIN dvds.language ON film.language_id = language.language_id
JOIN dvds.inventory ON inventory.film_id = film.film_id
JOIN dvds.rental ON rental.inventory_id = inventory.inventory_id
ORDER BY film.film_id ASC
LIMIT 50;
`
for i := 0; i < 1; i++ {
query := FilmActor.
INNER_JOIN(Actor, FilmActor.ActorID.Eq(Actor.ActorID)).
INNER_JOIN(Film, FilmActor.FilmID.Eq(Film.FilmID)).
INNER_JOIN(Language, Film.LanguageID.Eq(Language.LanguageID)).
INNER_JOIN(Inventory, Inventory.FilmID.Eq(Film.FilmID)).
INNER_JOIN(Rental, Rental.InventoryID.Eq(Inventory.InventoryID)).
SELECT(
FilmActor.AllColumns,
Film.AllColumns,
Language.AllColumns,
Actor.AllColumns,
Inventory.AllColumns,
Rental.AllColumns,
).
//WHERE(FilmActor.ActorID.GtEqL(1).AND(FilmActor.ActorID.LtEqL(2))).
ORDER_BY(Film.FilmID.ASC()).
LIMIT(50)
assertQuery(t, query, expectedSql, int64(50))
var languageActorFilm []struct {
model.Language
Films []struct {
model.Film
Actors []struct {
model.Actor
}
Inventory []struct {
model.Inventory
Rental []model.Rental
}
}
}
err := query.Query(db, &languageActorFilm)
assert.NilError(t, err)
assert.Equal(t, len(languageActorFilm), 1)
assert.Equal(t, len(languageActorFilm[0].Films), 1)
assert.Equal(t, len(languageActorFilm[0].Films[0].Actors), 10)
}
}
func TestJoinQuerySlice(t *testing.T) {
expectedSql := `
@ -408,7 +475,10 @@ LIMIT 1000;
assertQuery(t, query, expectedSql, int64(1000))
customerAddresCrosJoined := []model.Customer{}
var customerAddresCrosJoined []struct {
model.Customer
model.Address
}
err := query.Query(db, &customerAddresCrosJoined)
@ -417,6 +487,57 @@ LIMIT 1000;
assert.NilError(t, err)
}
func TestSelecSelfJoin1(t *testing.T) {
var expectedSql = `
SELECT employee.employee_id AS "employee.employee_id",
employee.first_name AS "employee.first_name",
employee.last_name AS "employee.last_name",
employee.manager_id AS "employee.manager_id",
manager.employee_id AS "manager.employee_id",
manager.first_name AS "manager.first_name",
manager.last_name AS "manager.last_name",
manager.manager_id AS "manager.manager_id"
FROM test_sample.employee
LEFT JOIN test_sample.employee AS manager ON manager.employee_id = employee.manager_id
ORDER BY employee.employee_id;
`
manager := Employee.AS("manager")
query := Employee.
LEFT_JOIN(manager, manager.EmployeeID.Eq(Employee.ManagerID)).
SELECT(Employee.AllColumns, manager.AllColumns).
ORDER_BY(Employee.EmployeeID)
assertQuery(t, query, expectedSql)
var dest []struct {
model2.Employee
Manager *model2.Employee
}
err := query.Query(db, &dest)
assert.NilError(t, err)
assert.Equal(t, len(dest), 8)
assert.DeepEqual(t, dest[0].Employee, model2.Employee{
EmployeeID: 1,
FirstName: "Windy",
LastName: "Hays",
ManagerID: nil,
})
assert.Assert(t, dest[0].Manager == nil)
assert.DeepEqual(t, dest[7].Employee, model2.Employee{
EmployeeID: 8,
FirstName: "Salley",
LastName: "Lester",
ManagerID: int32Ptr(3),
})
}
func TestSelectSelfJoin(t *testing.T) {
expectedSql := `
SELECT f1.film_id AS "f1.film_id",
@ -446,21 +567,19 @@ SELECT f1.film_id AS "f1.film_id",
f2.special_features AS "f2.special_features",
f2.fulltext AS "f2.fulltext"
FROM dvds.film AS f1
JOIN dvds.film AS f2 ON (f1.film_id != f2.film_id AND f1.length = f2.length)
ORDER BY f1.film_id ASC
LIMIT 100;
JOIN dvds.film AS f2 ON (f1.film_id < f2.film_id AND f1.length = f2.length)
ORDER BY f1.film_id ASC;
`
f1 := Film.AS("f1")
f2 := Film.AS("f2")
query := f1.
INNER_JOIN(f2, f1.FilmID.NotEq(f2.FilmID).AND(f1.Length.Eq(f2.Length))).
INNER_JOIN(f2, f1.FilmID.Lt(f2.FilmID).AND(f1.Length.Eq(f2.Length))).
SELECT(f1.AllColumns, f2.AllColumns).
ORDER_BY(f1.FilmID.ASC()).
LIMIT(100)
ORDER_BY(f1.FilmID.ASC())
assertQuery(t, query, expectedSql, int64(100))
assertQuery(t, query, expectedSql)
type F1 model.Film
type F2 model.Film
@ -474,7 +593,9 @@ LIMIT 100;
assert.NilError(t, err)
assert.Equal(t, len(theSameLengthFilms), 100)
//spew.Dump(theSameLengthFilms)
//assert.Equal(t, len(theSameLengthFilms), 100)
}
func TestSelectAliasColumn(t *testing.T) {
@ -517,61 +638,62 @@ LIMIT 1000;
assert.DeepEqual(t, films[0], thesameLengthFilms{"Alien Center", "Iron Moon", 46})
}
type Manager staff
type staff struct {
StaffID int32 `sql:"unique"`
FirstName string
LastName string
//Address *model.Address
//Email *string
//StoreID int16
//Active bool
//Username string
//Password *string
//LastUpdate time.Time
*Manager //`sqlbuilder:"manager"`
}
func TestSelectSelfReferenceType(t *testing.T) {
expectedSql := `
SELECT DISTINCT staff.staff_id AS "staff.staff_id",
staff.first_name AS "staff.first_name",
staff.last_name AS "staff.last_name",
address.address_id AS "address.address_id",
address.address AS "address.address",
address.address2 AS "address.address2",
address.district AS "address.district",
address.city_id AS "address.city_id",
address.postal_code AS "address.postal_code",
address.phone AS "address.phone",
address.last_update AS "address.last_update",
manager.staff_id AS "manager.staff_id",
manager.first_name AS "manager.first_name"
FROM dvds.staff
JOIN dvds.address ON staff.address_id = address.address_id
JOIN dvds.staff AS manager ON staff.staff_id = manager.staff_id;
`
manager := Staff.AS("manager")
query := Staff.
INNER_JOIN(Address, Staff.AddressID.Eq(Address.AddressID)).
INNER_JOIN(manager, Staff.StaffID.Eq(manager.StaffID)).
SELECT(Staff.StaffID, Staff.FirstName, Staff.LastName, Address.AllColumns, manager.StaffID, manager.FirstName).
DISTINCT()
assertQuery(t, query, expectedSql)
staffs := []staff{}
err := query.Query(db, &staffs)
assert.NilError(t, err)
fmt.Println(query.DebugSql())
//spew.Dump(staffs)
}
//
//type Manager staff
//
//type staff struct {
// StaffID int32 `sql:"unique"`
// FirstName string
// LastName string
// //Address *model.Address
// //Email *string
// //StoreID int16
// //Active bool
// //Username string
// //Password *string
// //LastUpdate time.Time
// *Manager //`sqlbuilder:"manager"`
//}
//
//func TestSelectSelfReferenceType(t *testing.T) {
//
// expectedSql := `
//SELECT DISTINCT staff.staff_id AS "staff.staff_id",
// staff.first_name AS "staff.first_name",
// staff.last_name AS "staff.last_name",
// address.address_id AS "address.address_id",
// address.address AS "address.address",
// address.address2 AS "address.address2",
// address.district AS "address.district",
// address.city_id AS "address.city_id",
// address.postal_code AS "address.postal_code",
// address.phone AS "address.phone",
// address.last_update AS "address.last_update",
// manager.staff_id AS "manager.staff_id",
// manager.first_name AS "manager.first_name"
//FROM dvds.staff
// JOIN dvds.address ON staff.address_id = address.address_id
// JOIN dvds.staff AS manager ON staff.staff_id = manager.staff_id;
//`
// manager := Staff.AS("manager")
//
// query := Staff.
// INNER_JOIN(Address, Staff.AddressID.Eq(Address.AddressID)).
// INNER_JOIN(manager, Staff.StaffID.Eq(manager.StaffID)).
// SELECT(Staff.StaffID, Staff.FirstName, Staff.LastName, Address.AllColumns, manager.StaffID, manager.FirstName).
// DISTINCT()
//
// assertQuery(t, query, expectedSql)
//
// staffs := []staff{}
//
// err := query.Query(db, &staffs)
//
// assert.NilError(t, err)
//
// fmt.Println(query.DebugSql())
// //spew.Dump(staffs)
//}
func TestSubQuery(t *testing.T) {
@ -684,7 +806,8 @@ ORDER BY film.film_id ASC;
maxFilmRentalRate := NumExp(Film.SELECT(MAX(Film.RentalRate)))
query := Film.SELECT(Film.AllColumns).
query := Film.
SELECT(Film.AllColumns).
WHERE(Film.RentalRate.Eq(maxFilmRentalRate)).
ORDER_BY(Film.FilmID.ASC())
@ -705,7 +828,7 @@ ORDER BY film.film_id ASC;
Title: "Ace Goldfinger",
Description: stringPtr("A Astounding Epistle of a Database Administrator And a Explorer who must Find a Car in Ancient China"),
ReleaseYear: int32Ptr(2006),
Language: nil,
LanguageID: 1,
RentalRate: 4.99,
Length: int16Ptr(48),
ReplacementCost: 12.99,
@ -810,6 +933,7 @@ ORDER BY customer_payment_sum.amount_sum ASC;
StoreID: 1,
FirstName: "Brian",
LastName: "Wyman",
AddressID: 323,
Email: stringPtr("brian.wyman@sakilacustomer.org"),
Activebool: true,
CreateDate: *timeWithoutTimeZone("2006-02-14 00:00:00", 0),
@ -851,6 +975,9 @@ ORDER BY payment.payment_date ASC;
assert.Equal(t, len(payments), 9)
assert.DeepEqual(t, payments[0], model.Payment{
PaymentID: 17793,
CustomerID: 416,
StaffID: 2,
RentalID: 1158,
Amount: 2.99,
PaymentDate: *timeWithoutTimeZone("2007-02-14 21:21:59.996577", 6),
})

View file

@ -17,7 +17,7 @@ func assertQuery(t *testing.T, query sqlbuilder.Statement, expectedQuery string,
debuqSql, err := query.DebugSql()
assert.NilError(t, err)
assert.Equal(t, debuqSql, expectedQuery, args)
assert.Equal(t, debuqSql, expectedQuery)
}
func int16Ptr(i int16) *int16 {
@ -55,7 +55,7 @@ var customer0 = model.Customer{
FirstName: "Mary",
LastName: "Smith",
Email: stringPtr("mary.smith@sakilacustomer.org"),
Address: nil,
AddressID: 5,
Activebool: true,
CreateDate: *timeWithoutTimeZone("2006-02-14 00:00:00", 0),
LastUpdate: timeWithoutTimeZone("2013-05-26 14:49:45.738", 3),
@ -68,7 +68,7 @@ var customer1 = model.Customer{
FirstName: "Patricia",
LastName: "Johnson",
Email: stringPtr("patricia.johnson@sakilacustomer.org"),
Address: nil,
AddressID: 6,
Activebool: true,
CreateDate: *timeWithoutTimeZone("2006-02-14 00:00:00", 0),
LastUpdate: timeWithoutTimeZone("2013-05-26 14:49:45.738", 3),
@ -81,7 +81,7 @@ var lastCustomer = model.Customer{
FirstName: "Austin",
LastName: "Cintron",
Email: stringPtr("austin.cintron@sakilacustomer.org"),
Address: nil,
AddressID: 605,
Activebool: true,
CreateDate: *timeWithoutTimeZone("2006-02-14 00:00:00", 0),
LastUpdate: timeWithoutTimeZone("2013-05-26 14:49:45.738", 3),