package qx

/*
	Tags
с:"-" если поле нужно игнорировать, как при вставке, так и при выборке
json:"columnName" имя колонки
с:"columnName" имя колонки (старше чем json tag)
table:"tableName" имя таблицы, указывать можно только в первом поле структуры (!)
seq:"" для Oracle sequence name, для Mysql любой непустой текст


	Placeholder
Во всех запросах один placeholder: ?
Для Oracle он заменяется на :1, :2 и т.д.
Для MySQL остается как есть.

Для всех баз в IN запросах заменяется на список плейсхолдеров. Такой запрос всегда
составляется динамически, ибо зависит размера массива параметров для IN.
Так же при составлении запроса учитывается СУБД. Запрос составляется целиком,
а не только IN часть, ибо поломается нумерация :1, :2 для всех параметров после IN.
Итого для каждого запроса мне нужно знать: есть ли IN.

*/

/*
Зачем нужна предварительная регистрация.
- единственный способ указать Foreign и Unique ключи с кодами ошибок
- единственный способ избавится от мутексов при выполнении запросов. Все структуры
и связанные с ними планы запросов кэшируются. Если писать и читать из кешей
в ходе работы системы - нужны мутексы.
- можно реализовать дополнительную проверку полей. Есть Insert структура,
которая содержит список всех полей таблицы. По этой структуре можно проверить ключи,
а также все select поля.
*/

import (
	"database/sql"
	"fmt"
	"reflect"
	"regexp"
	"strings"
	"sync"

	"gordenko.dev/dima/fixme"
)

type QueryError struct {
	query  string
	params []interface{}
	err    error
}

func (s QueryError) Error() string {
	return fmt.Sprintf("%s\nQuery: %q\nParams: %v", s.err, s.query, s.params)
}

type Driver interface {
	AdaptLimitClause(string) string
	JoinSelectQuery([]string) string
	// (parsedList, inArgs) -> query, args, error
	ModifySelectQuery([]string, []interface{}) (string, []interface{}, error)
	// last arg - seq name (Oracle)
	LastInsertId(*sql.Tx, sql.Result, string) (int64, error)
}

type queryFunc func(string, ...interface{}) (*sql.Rows, error)
type queryRowFunc func(string, ...interface{}) *sql.Row

func joinWhereClause(columns []string) string {
	if len(columns) == 0 {
		return ""
	}
	var list []string
	for _, column := range columns {
		list = append(list, column+"=?")
	}
	return strings.Join(list, " AND ")
}

type Core struct {
	driver               Driver
	referrerCheckQueries map[string][]string
	// query adapter
	mutex         sync.RWMutex
	parsedQueries map[string]*parsedQuery
	insertCache   map[reflect.Type]*insertStruct
	//constraints   map[string]Constraint
	selectCache map[reflect.Type]*selectStruct // ключ - elemType
	// Код ошибки Foreign Key при удалении записи. Позволяет показать юзеру
	// понятное сообщение об ошибке.
	//foreignKeyErrorCodeWhenDelete int
}

func newCore(driver Driver) *Core {
	s := new(Core)
	s.driver = driver
	//s.constraints = make(map[string]Constraint)
	s.insertCache = make(map[reflect.Type]*insertStruct)
	s.selectCache = make(map[reflect.Type]*selectStruct)
	s.referrerCheckQueries = make(map[string][]string)
	s.parsedQueries = make(map[string]*parsedQuery)
	return s
}

type parsedQuery struct {
	Query       string
	SlicedQuery []string
}

func (s *Core) getCachedParsedQuery(rawQuery string) (cached *parsedQuery) {
	var ok bool
	// Сперва берем READ LOCK
	s.mutex.RLock()
	cached, ok = s.parsedQueries[rawQuery]
	if ok {
		s.mutex.RUnlock()
		return
	}
	s.mutex.RUnlock()

	// Объект не закеширован - берем WRITE LOCK
	s.mutex.Lock()
	defer s.mutex.Unlock()

	// Проверяем еще раз. Возможно между READ и WRITE локами объект уже попал в кэш.
	cached, ok = s.parsedQueries[rawQuery]
	if ok {
		return
	}

	pq := s.parseQuery(rawQuery)
	cached = &pq

	s.parsedQueries[rawQuery] = cached
	return
}

func (s *Core) AdaptQuery(rawQuery string, inArgs []interface{}) (query string, args []interface{}, err error) {
	pq := s.getCachedParsedQuery(rawQuery)
	//fmt.Printf("pq: %#v\n\n", pq)
	if len(pq.SlicedQuery) == 0 {
		return pq.Query, inArgs, nil
	}
	expectedArgsNumber := (len(pq.SlicedQuery) - 1) / 2
	if expectedArgsNumber != len(inArgs) {
		err = fmt.Errorf("qx: expected %d arguments, got %d", expectedArgsNumber, len(inArgs))
		return
	}
	return s.driver.ModifySelectQuery(pq.SlicedQuery, inArgs)
}

// AdaptQueryTextOnly - для Prepare запросов. Результат парсинга запроса кэшируется
func (s *Core) AdaptQueryTextOnly(rawQuery string) (query string) {
	pq := s.getCachedParsedQuery(rawQuery)
	return pq.Query
}

// RewriteQueryNoCacheNoIN - адаптирует запрос под конкретную БД, но не кэширует результат.
// Полезно для запросов типа ByPrimaryKeyQuery, который можно сохранить в selectStruct.
// При вызове метода One() текст запроса достаем из selectStruct, не нужно трогать кэш
// запросов parsedQueries.
// IN(?) в тексте запроса не будет обрабатыватся специальным образом!
func (s *Core) RewriteQueryNoCacheNoIN(rawQuery string) (query string) {
	pq := s.parseQuery(rawQuery)
	return pq.Query
}

var reLeftIn = regexp.MustCompile(`(?i)\bIN\s*\(\s*$`)

func (s *Core) isQuestionMarkInsideIN(str, nextStr string) (_ bool) {
	//fmt.Printf("isQuestionMarkInsideIN: %q, %q\n\n", str, nextStr)
	if reLeftIn.MatchString(str) {
		// Обрезаем пробельные символы на случай запроса: IN(? )
		nextStr := strings.TrimSpace(nextStr)
		if len(nextStr) > 0 && strings.HasPrefix(nextStr, ")") {
			return true
		}
	}
	return
}

// Храним готовый запрос, если в нем нет IN параметра. Запрос уже адаптирован под БД.
// Если IN параметр есть - Query будет пустым.
// Для Prepare запроса
// /Query string
// Заготовка для генерации запроса. Массив не пустой, если в запросе есть IN параметр.

func (s *Core) parseQuery(rawQuery string) (pq parsedQuery) {
	rawQuery = s.driver.AdaptLimitClause(rawQuery)

	chain := strings.Split(rawQuery, "?")
	if len(chain) == 0 {
		pq.Query = rawQuery
		return
	}

	var (
		hasIN bool
		list  []string
	)

	lastIdx := len(chain) - 1

	for idx, str := range chain[:lastIdx] {
		if s.isQuestionMarkInsideIN(str, chain[idx+1]) {
			list = append(list, str, "i")
			hasIN = true
		} else {
			list = append(list, str, "")
		}
	}
	list = append(list, chain[lastIdx])

	// Строим готовый запрос с учетом БД
	pq.Query = s.driver.JoinSelectQuery(list)

	if hasIN {
		pq.SlicedQuery = list
	}
	return
}

type foreignKey struct {
	Query    string
	Field    string // для api.Err
	FieldIdx int
	//ErrorCode int
	//IsCascade bool // ПОКА НЕ ЗНАЮ - НАДО ИЛИ НЕТ каскадное удаление? по умолчанию restrict
}

type uniqueConstraint struct {
	Query    string
	Field    string // для api.Err
	FieldIdx int
	//ErrorCode int
}

type compositeUniqueConstraint struct {
	Query        string
	Field        string // для api.Err
	FieldIndexes []int
	//ErrorCode    int
}

type scanStruct struct {
	ParentFieldIdx int          // Если данная структрура встроена в другую, то
	Indexes        []int        // Поля, в которые нужно считать значения sql.Row
	Embed          []scanStruct // дочерние структуры (JOIN)
}

func buildScanStruct(info *structInfo, parentFieldIdx int) (r scanStruct) {
	// Для вложенных структур индекс будет перезаписан
	r.ParentFieldIdx = parentFieldIdx
	r.Indexes = make([]int, len(info.Indexes))
	copy(r.Indexes, info.Indexes)

	for _, embed := range info.Embed {
		r.Embed = append(r.Embed, buildScanStruct(embed.StructInfo, embed.Idx))
	}
	return
}

func (s *Core) buildSelectStruct(info *structInfo) (_ *selectStruct, err error) {
	out := &selectStruct{
		PrimaryKeyIndexes: info.PrimaryKeyIndexes,
	}
	// Префикс для List, ListBy, One, OneBy запросов.
	// Включает перечисление колонок и, если нужны, JOIN-ы
	builder := new(selectQueryPrefixBuilder)
	// Этот запрос/префикс не нужно переписывать, ибо в нем нет специфичных для
	// конкретной СУБД вещей
	out.QueryPrefix, err = builder.Build(info)
	if err != nil {
		// Ошибка может быть, если не реализованы какие-то фичи.
		return
	}
	//fmt.Printf("QueryPrefix: %s\n", out.QueryPrefix)

	// Строим полный запрос для One (поиск по первичному ключу)
	var columns []string

	if len(info.Embed) > 0 {
		for _, column := range info.PrimaryKey {
			columns = append(columns, fmt.Sprintf("%s.%s", info.Table, column))
		}
	} else {
		columns = info.PrimaryKey
	}
	q := out.QueryPrefix + " WHERE " + joinWhereClause(columns)
	out.ByPrimaryKeyQuery = s.RewriteQueryNoCacheNoIN(q)

	out.ScanStruct = buildScanStruct(info, -1) // -1 указывает что родителя нет
	return out, nil
}

// Для проверки внешних ключей. Чтобы вывести юзеру понятное сообщение, а не шлак.
// Для форм это не очень актуально, а для API, где прислать могут что угодно -
// очень актуально.

type selectQueryPrefixBuilder struct {
	// поля для накопления данных в процессе рекурсивного обхода structInfo
	columns []string // строки типа t1.name
	joins   []string // строки типа "\tINNER JOIN t2 ON t1.id=t2.id"
}

func (s *selectQueryPrefixBuilder) Build(x *structInfo) (q string, err error) {
	if len(x.Embed) == 0 {
		q = fmt.Sprintf("SELECT %s FROM %s", strings.Join(x.Columns, ", "), x.Table)
	} else {
		err = s.processTable(x, "")
		if err != nil {
			return
		}
		q = fmt.Sprintf("SELECT %s FROM %s\n%s", strings.Join(s.columns, ", "),
			x.Table, strings.Join(s.joins, "\n"))
	}
	return
}

func (s *selectQueryPrefixBuilder) processTable(x *structInfo, parentTable string) (err error) {
	for _, c := range x.Columns {
		s.columns = append(s.columns, fmt.Sprintf("%s.%s", x.Table, c))
	}

	if parentTable != "" {
		// FUTURE реализовать выборку вложенных объектов с composite primary key
		if len(x.PrimaryKey) > 1 {
			return fmt.Errorf("select embeded objects with composite Primary Key not implemented")
		}
		s.joins = append(s.joins, fmt.Sprintf("\tINNER JOIN %s ON %s.%s=%s.%s",
			x.Table, parentTable, x.PrimaryKey[0], x.Table, x.PrimaryKey[0]))
		//s.joins = append(s.joins, fmt.Sprintf("\tINNER JOIN %s USING (%s)",
		//x.Table, parentTable, x.PrimaryKey[0], x.Table, x.PrimaryKey[0]))
	}

	for _, embed := range x.Embed {
		err = s.processTable(embed.StructInfo, x.Table)
		if err != nil {
			return
		}
	}
	return
}

func buildScan(scan *[]interface{}, info scanStruct, item reflect.Value) {
	for _, fieldIdx := range info.Indexes {
		f := item.Field(fieldIdx)
		*scan = append(*scan, f.Addr().Interface())
	}

	for _, embed := range info.Embed {
		buildScan(scan, embed, item.Field(embed.ParentFieldIdx))
	}
}

// cacheStruct - разбирает структуру (struct), генерирует запросы, сохраняет в кэш

// Проверить всех, кто ссылается (referrer) на таблицу из которой удаляем.
// Когда мы обьявляем foreign key - мы ссылаемся на таблицу, то есть становимся
// referrer. Нужно составить список referrers, а затем сгенерировать запросы
// и сохранить в map[таблица] => []запрос. Затем в цикле отправлять Has-like
// запрос.
func (s *Core) Remove(tx *sql.Tx, obj interface{}) (err error) {
	v := reflect.ValueOf(obj)
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}
	if v.Kind() != reflect.Struct {
		err = fmt.Errorf("qx: Remove: wrong arg type, expected struct/*struct, got %T", obj)
		return
	}

	cached, err := s.getCachedInsertStruct(v.Type())
	if err != nil {
		return
	}

	if cached.DeleteByPrimaryKeyQuery == "" {
		return fmt.Errorf("qx: Remove requires primary key")
	}

	// Сперва проверяем есть ли записи в других таблицах, которые ссылаются
	// на удаляемую запись
	hasQueries, ok := s.referrerCheckQueries[cached.Table]
	if ok {
		id := v.Field(cached.PrimaryKeyIndexes[0]).Interface()
		for _, hasQuery := range hasQueries {
			var expectZero int
			err = tx.QueryRow(hasQuery, id).Scan(&expectZero)
			if err != nil {
				if err != sql.ErrNoRows {
					return
				}
				err = nil
			} else {
				if expectZero != 0 {
					// Имя поля нет!
					return fix.Error(ForeignKeyRemoveError)
				}
			}
		}
	}

	var args []interface{}
	for _, fieldIdx := range cached.PrimaryKeyIndexes {
		args = append(args, v.Field(fieldIdx).Interface())
	}

	_, err = tx.Exec(cached.DeleteByPrimaryKeyQuery, args...)
	return
}

// Update - обновляет объект по первичному ключу. Для простых объектов допустимы
// как простой, так и композитный первичный ключ. Для объектов с вложенными списками
// - только простой ключ (ограничение Insert).
// ВАЖНО! Обновляет поля только топ объекта. Все зависимые списки удаляются
// и вставляются заново. Если у зависимых объектов первичный ключ - auto_increment,
// все ключи будут обновлены.
func (s *Core) Update(tx *sql.Tx, one interface{}) (err error) {
	v := reflect.ValueOf(one)
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}
	if v.Kind() != reflect.Struct {
		err = fmt.Errorf("qx: Update: wrong arg type, expected struct/*struct, got %T", one)
		return
	}
	cached, err := s.getCachedInsertStruct(v.Type())
	if err != nil {
		return
	}

	if cached.UpdateByPrimaryKeyQuery == "" {
		return fmt.Errorf("qx: Update requires primary key")
	}

	if len(cached.Lists) > 0 {
		// // MULTI KEY FIXED +
		var parentIDs []interface{}
		// Только для вложенных списков
		for _, idx := range cached.PrimaryKeyIndexes {
			parentIDs = append(parentIDs, v.Field(idx).Interface())
		}
		//parentID := v.Field(cached.PrimaryKeyIndexes[0]).Interface()
		// Удаляем в цикле по parentID (pk один, ибо insert для complex pk не реализован)
		for _, deleteQuery := range cached.DeleteChildQueries {
			//fmt.Printf("DELETE: %q", deleteQuery)
			_, err = tx.Exec(deleteQuery, parentIDs...) // MULTI KEY FIXED +
			if err != nil {
				return
			}
		}
		prepared := make(map[*insertStruct]*sql.Stmt)
		// Закрываем все PreparedStatement в конце
		defer func() {
			for _, stmt := range prepared {
				stmt.Close()
			}
		}()

		// Добавляем в цикле
		for _, nested := range cached.Lists {
			err = s.test(tx, prepared, nested.Struct, nested.Field, parentIDs,
				v.Field(nested.Idx))
			if err != nil {
				return
			}
		}
	}
	// FIX наход дубликаты UNIQUE ключей в своей же записи?
	/*
		err = s.checkTableKeys(tx, cached, v)
		if err != nil {
			return
		}
	*/

	var args []interface{}
	// SET и WHERE колонки
	for _, fieldIdx := range cached.UpdateIndexes {
		args = append(args, v.Field(fieldIdx).Interface())
	}
	_, err = tx.Exec(cached.UpdateByPrimaryKeyQuery, args...)
	return
}

// INSERT

func (s *Core) getCachedInsertStruct(elemType reflect.Type) (cached *insertStruct, err error) {
	var ok bool
	// Сперва берем READ LOCK
	s.mutex.RLock()
	cached, ok = s.insertCache[elemType]
	if ok {
		s.mutex.RUnlock()
		return
	}
	s.mutex.RUnlock()

	// Объект не закеширован - берем WRITE LOCK
	s.mutex.Lock()
	defer s.mutex.Unlock()

	// Проверяем еще раз. Возможно между READ и WRITE локами объект уже попал в кэш.
	cached, ok = s.insertCache[elemType]
	if ok {
		return
	}
	// Общий парсер для всех видов запросов
	info, err := parseStruct(elemType)
	if err != nil {
		return
	}

	cached, err = s.buildInsertStruct(info, nil)
	if err != nil {
		return
	}

	s.insertCache[elemType] = cached
	return
}

func (s *Core) Insert(tx *sql.Tx, obj interface{}) (id int64, err error) {
	t := reflect.TypeOf(obj)
	v := reflect.ValueOf(obj)

	if t.Kind() == reflect.Ptr {
		t = t.Elem()
		v = v.Elem()
	}

	if t.Kind() != reflect.Struct {
		err = fmt.Errorf("qx: expect obj argument of struct/*struct type, got %s", reflect.TypeOf(obj))
		return
	}

	cached, err := s.getCachedInsertStruct(t)
	if err != nil {
		return
	}

	//fmt.Printf("cached: %# v", pretty.Formatter(cached))

	prepared := make(map[*insertStruct]*sql.Stmt)
	// Закрываем все PreparedStatement в конце
	defer func() {
		for _, stmt := range prepared {
			stmt.Close()
		}
	}()

	//fmt.Printf("INSERT TABLE: %#v\n\n", table)

	err = s.checkTableKeys(tx, cached, v)
	//fmt.Printf("CHECK: apierr: %v, err: %v\n", err)
	if err != nil {
		return
	}

	var (
		args   []interface{}
		isZero bool
	)
	if cached.Seq != "" {
		// проверяем значение ID в поле PrimaryKey на равно 0
		// Данный код протестирован!
		switch v.Field(0).Kind() {
		case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8:
			idValue := v.Field(0).Int()
			isZero = idValue == 0

		case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8:
			idValue := v.Field(0).Uint()
			isZero = idValue == 0

		default:
			fmt.Printf("bug: %#T\n\n", obj)
			err = fmt.Errorf("bug: wrong auto_increment type %T", v.Field(0).Interface())
			return
		}

		if isZero {
			args = append(args, nil)

			for _, idx := range cached.Indexes[1:] {
				args = append(args, v.Field(idx).Interface())
			}
		} else {
			for _, idx := range cached.Indexes {
				args = append(args, v.Field(idx).Interface())
			}
		}
	} else {
		for _, idx := range cached.Indexes {
			args = append(args, v.Field(idx).Interface())
		}
	}
	/*
		xxx, _ := json.MarshalIndent(obj, "", "    ")
		fmt.Printf("\n\nINSERT obj: %s\n\n", xxx)
		fmt.Printf("cached.PrimaryKeyCheckQuery: %s\n\n", cached.PrimaryKeyCheckQuery)
		fmt.Printf("IS ZERO: %t\n\n", isZero)
		//fmt.Printf("idValue: %v\n\n", idValue)
	*/

	// Проверяем на дубликат по PrimaryKey
	// Два кейса - ключ не AutoIncrement, ключ AutoIncrement, но явно задан (isZero == false)
	if cached.PrimaryKeyCheckQuery != "" {
		// Запрос может быть пустым, если у таблицы нет первичного ключа
		if cached.Seq != "" {
			// Если у таблицы определен auto_increment - проверяем значение Ключа.
			// Если не ноль - нужно проверить на дубликат
			if !isZero {
				var (
					expectZero int
					idValue    = v.Field(0).Interface()
				)
				err = tx.QueryRow(cached.PrimaryKeyCheckQuery, idValue).Scan(&expectZero)
				if err != nil {
					if err != sql.ErrNoRows {
						err = QueryError{
							query:  cached.PrimaryKeyCheckQuery,
							params: []interface{}{idValue},
							err:    err,
						}
					}
					err = nil
				}
				if expectZero != 0 {
					// Dublicate
					err = fix.Error(DuplicateError)
					return
				}
			}
		} else {
			var (
				primaryKeyArgs = make([]interface{}, len(cached.PrimaryKeyIndexes))
				expectZero     int
			)
			for i, fieldIdx := range cached.PrimaryKeyIndexes {
				primaryKeyArgs[i] = v.Field(fieldIdx).Interface()
			}

			err = tx.QueryRow(cached.PrimaryKeyCheckQuery, primaryKeyArgs...).Scan(&expectZero)
			if err != nil {
				if err != sql.ErrNoRows {
					err = QueryError{
						query:  cached.PrimaryKeyCheckQuery,
						params: primaryKeyArgs,
						err:    err,
					}
				}
				err = nil
			}
			if expectZero != 0 {
				// Dublicate
				err = fix.Error(DuplicateError)
				return
			}
		}
	}

	// В отличии от вложенных таблиц выполняем Exec без Prepare, возвращаем id

	//	xxx, _ := json.MarshalIndent(obj, "", "    ")

	//	fmt.Printf("\n\nINSERT obj: %s\n\n", xxx)
	//	fmt.Printf("INSERT: %q\n\nargs: %v\n\n", cached.Query, args)

	res, err := tx.Exec(cached.Query, args...)
	if err != nil {
		err = QueryError{
			query:  cached.Query,
			params: args,
			err:    err,
		}
		return
	}

	var parentIDs []interface{}

	if cached.Seq != "" {
		// у Mysql и Oracle кардинально разные методы получения LastInsertId,
		// поэтому реализация метода вынесена в драйвер
		id, err = s.driver.LastInsertId(tx, res, cached.Seq)
		if err != nil {
			return
		}
		parentIDs = []interface{}{id}
	} else {
		// FIX MULTI PARENT IDs +
		for _, idx := range cached.PrimaryKeyIndexes {
			parentIDs = append(parentIDs, v.Field(idx).Interface()) // primary key
		}
	}

	// Цикл по таблицам
	for _, nested := range cached.Lists {
		err = s.test(tx, prepared, nested.Struct, nested.Field, parentIDs, v.Field(nested.Idx))
		if err != nil {
			return
		}
	}
	return
}

// // FIX MULTI PARENT IDs +
func (s *Core) test(tx *sql.Tx, prepared map[*insertStruct]*sql.Stmt, info *insertStruct, fieldName string, parentIDs []interface{}, list reflect.Value) (err error) {

	// Кешируем PreparedStatement-ы, актуально для 2 и более уровней вложенности
	stmt, ok := prepared[info]
	if !ok {
		stmt, err = tx.Prepare(info.Query)
		if err != nil {
			return QueryError{
				query: info.Query,
				err:   err,
			}
		}
		prepared[info] = stmt
	}

	for itemIdx := 0; itemIdx < list.Len(); itemIdx++ {
		item := list.Index(itemIdx)
		// На случай массивов типа []*SomeType
		if item.Kind() == reflect.Ptr {
			item = item.Elem()
		}

		if item.Kind() != reflect.Struct {
			err = fmt.Errorf("wrong slice item type: %s. Expected struct/*struct",
				// Показываем оригинальный тип (с учетом указателя *)
				list.Index(itemIdx).Type())
			return
		}

		err = s.checkTableKeys(tx, info, item)
		//fmt.Printf("CHECK nested: apierr: %v, err: %v\n", err)

		if err != nil {
			return fixme.PrependItem(err, fieldName, itemIdx)
			/*
				apierr, ok := err.(api.Err)
				if ok {
					leg := api.Leg{
						Field: fieldName,
						Idx:   itemIdx, // номер элемента массива
					}
					// Выходим из рекурсии с конца в начало, поэтому Leg нужно добавить в
					// начало Path
					apierr.Path = append([]api.Leg{leg}, apierr.Path...)
					return apierr
				}
				return err
			*/
		}

		var args []interface{}
		for _, idx := range info.Indexes {
			args = append(args, item.Field(idx).Interface())
		}
		// FIX MULTI PARENT IDs +
		// ВАЖНО! Не забываем добавить ключ родителя!
		args = append(args, parentIDs...)

		//fmt.Printf("INSERT NESTED: %s\n\n", info.Query)
		//fmt.Printf("ARGS: %v\n\n", args)

		// общий код - выполняем запрос, выделяем ключ - и в цикле вызываем зависимые
		res, err := stmt.Exec(args...)
		if err != nil {
			return QueryError{
				query:  info.Query,
				params: args,
				err:    err,
			}
		}

		var nextParentIDs []interface{}

		if info.Seq != "" {
			var parentID int64
			// у Mysql и Oracle кардинально разные методы получения LastInsertId,
			// поэтому реализация метода вынесена в драйвер
			parentID, err = s.driver.LastInsertId(tx, res, info.Seq)
			if err != nil {
				return err
			}
			// FIX MULTI PARENT ID +
			nextParentIDs = []interface{}{parentID}
		} else {
			// FIX MULTI PARENT ID +
			//parentID = item.Field(0).Interface()
			for _, idx := range info.PrimaryKeyIndexes {
				if info.ParentHasAutoPrimaryKey && info.ParentColumnIdx == idx {
					// родительский auto increment
					nextParentIDs = append(nextParentIDs, parentIDs[0])
				} else {
					nextParentIDs = append(nextParentIDs, item.Field(idx).Interface()) // primary key
				}
			}
		}

		// Цикл по таблицам
		for _, nested := range info.Lists {
			err = s.test(tx, prepared, nested.Struct, fieldName, nextParentIDs, item.Field(nested.Idx))
			if err != nil {
				return err
			}
		}
	}
	return
}

func (s *Core) checkTableKeys(tx *sql.Tx, info *insertStruct, v reflect.Value) (err error) {
	// Проверяем ВНЕШНИЕ ключи
	for _, key := range info.ForeignKeys {
		err = s.checkForeignKey(tx, key, v)
		if err != nil {
			return
		}
	}
	// Проверяем UNIQUE ключи
	for _, key := range info.Unique {
		err = s.checkUniqueConstraint(tx, key, v)
		if err != nil {
			return
		}
	}

	// Проверяем UNIQUE COMPOSITE ключи
	for _, key := range info.CompositeUnique {
		err = s.checkCompositeUniqueConstraint(tx, key, v)
		if err != nil {
			return
		}
	}
	return
}

func (s *Core) checkForeignKey(tx *sql.Tx, key foreignKey, v reflect.Value) (err error) {
	var expectOne int

	value := v.Field(key.FieldIdx).Interface()
	//fmt.Printf("checkConstraints fk: %q, %v\n", key.Query, value)

	err = tx.QueryRow(key.Query, value).Scan(&expectOne)
	if err != nil {
		if err != sql.ErrNoRows {
			return QueryError{
				query:  key.Query,
				params: []interface{}{value},
				err:    err,
			}
		}
		err = nil
	}

	if expectOne != 1 {
		return fix.Field(ForeignKeyRemoveError, key.Field)
	}
	return
}

func (s *Core) checkUniqueConstraint(tx *sql.Tx, u uniqueConstraint, v reflect.Value) (err error) {
	var expectZero int

	value := v.Field(u.FieldIdx).Interface()
	// /fmt.Printf("checkUniqueConstraints fk: %q, %v\n", fk.Query, foreignID)

	err = tx.QueryRow(u.Query, value).Scan(&expectZero)
	if err != nil {
		if err != sql.ErrNoRows {
			return QueryError{
				query:  u.Query,
				params: []interface{}{value},
				err:    err,
			}
		}
		err = nil
	}

	if expectZero != 0 {
		return fix.Field(UniqueError, u.Field)
	}
	return
}

func (s *Core) checkCompositeUniqueConstraint(tx *sql.Tx, u compositeUniqueConstraint, v reflect.Value) (err error) {
	var (
		args       []interface{}
		expectZero int
	)
	for _, fieldIdx := range u.FieldIndexes {
		args = append(args, v.Field(fieldIdx).Interface())
	}

	err = tx.QueryRow(u.Query, args...).Scan(&expectZero)
	//fmt.Printf("checkConstraints uniqueComposite: %q, %v\n", uc.Query, args)
	if err != nil {
		if err != sql.ErrNoRows {
			return QueryError{
				query:  u.Query,
				params: args,
				err:    err,
			}
		}
		err = nil
	}

	if expectZero != 0 {
		return fix.Field(UniqueError, u.Field)
	}
	return
}

/////////////////////////////

type insertList struct {
	Idx    int
	Field  string // для api.Err при проверке ключей
	Struct *insertStruct
}

type insertStruct struct {
	Table                   string
	Seq                     string
	Query                   string
	Indexes                 []int
	PrimaryKeyCheckQuery    string // запрос для проверки на дубликат по первичному ключу при Insert
	DeleteByPrimaryKeyQuery string
	PrimaryKeyIndexes       []int
	UpdateByPrimaryKeyQuery string
	UpdateIndexes           []int    // set и where (primary key) колонки
	DeleteChildQueries      []string // Запросы удаляющие 1й уровень вложенных списков при tx.Update
	ForeignKeys             []foreignKey
	Unique                  []uniqueConstraint
	CompositeUnique         []compositeUniqueConstraint
	//ColumnMapping           map[string]int // columnName => field idx для проверки ключей
	Lists []insertList
	// Для правильной вставки с композитным ключем у родителя, в который входит auto PK
	// его родителя
	ParentHasAutoPrimaryKey bool
	ParentColumnIdx         int
}

// метод, чтобы иметь доступ к rewriteQueryTextOnly
func (s *Core) buildInsertStruct(info *structInfo, parent *structInfo) (_ *insertStruct, err error) {

	//fmt.Printf("structInfo: %# v\n\n", pretty.Formatter(info))
	var (
		columns, placeholders []string
		parentColumn          string
	)
	x := &insertStruct{
		Table:             info.Table,
		Seq:               info.Seq,
		PrimaryKeyIndexes: info.PrimaryKeyIndexes,
	}

	if len(info.Lists) > 0 && len(info.PrimaryKey) == 0 {
		err = fmt.Errorf("qx: struct %q has children. Primary key is required",
			info.StructName)
		return
	}

	if parent == nil {
		// Если нет родителя - это топ структура, нужно добавить DELETE и UPDATE запросы,
		// а также проверку первичного ключа на дубликат при Insert запросе
		if len(info.PrimaryKey) > 0 {
			var q string
			if info.Seq != "" {
				//  fix composite key
				q = fmt.Sprintf(`SELECT 1 FROM %s WHERE %s=?`, info.Table, info.PrimaryKey[0])
			} else {
				q = fmt.Sprintf(`SELECT 1 FROM %s WHERE %s`, info.Table,
					joinWhereClause(info.PrimaryKey))
			}
			x.PrimaryKeyCheckQuery = s.RewriteQueryNoCacheNoIN(q)
		}

		if len(info.PrimaryKey) > 0 {
			// Если у объекта нет детей (Lists), то иметь PrimaryKey не обязательно.
			// DELETE и UPDATE запросы без PrimaryKey не имеют смысла, поэтому проверка
			// на наличие PrimaryKey обязательна.
			q := fmt.Sprintf("DELETE FROM %s WHERE %s",
				info.Table, joinWhereClause(info.PrimaryKey))
			x.DeleteByPrimaryKeyQuery = s.RewriteQueryNoCacheNoIN(q)

			// Обновляем все колонки кроме входящих в первичный ключ.
			// Чтобы понять какие колонки не входят в первичный ключ - нужно сделать
			// мапу из колонок первичного ключа.
			primaryKeyColumns := make(map[string]bool)
			for _, column := range info.PrimaryKey {
				primaryKeyColumns[column] = true
			}

			var setCommands []string
			for i, column := range info.Columns {
				if primaryKeyColumns[column] {
					continue
				}
				setCommands = append(setCommands, column+"=?")
				x.UpdateIndexes = append(x.UpdateIndexes, info.Indexes[i])
			}

			for _, fieldIdx := range info.PrimaryKeyIndexes {
				x.UpdateIndexes = append(x.UpdateIndexes, fieldIdx)
			}

			q = fmt.Sprintf("UPDATE %s SET %s WHERE %s", info.Table,
				strings.Join(setCommands, ","), joinWhereClause(info.PrimaryKey))

			x.UpdateByPrimaryKeyQuery = s.RewriteQueryNoCacheNoIN(q)

			// Строим список DELETE запросов для детей (не внуков!)
			// Предполагается что удаление более глубоких объектов будет выполнено каскадно
			// средствами СУБД
			for _, child := range info.Lists {
				// Удаляем в том числе по композитному родительскому ключу
				q := fmt.Sprintf("DELETE FROM %s WHERE %s", child.StructInfo.Table,
					joinWhereClause(info.PrimaryKey))

				x.DeleteChildQueries = append(x.DeleteChildQueries, s.RewriteQueryNoCacheNoIN(q))
			}

			// Формируем список колонок и индексов полей для INSERT запроса
			/*
				if info.Seq != "" {
					// выкидываем auto_increment колонку
					columns = info.Columns[1:]
					x.Indexes = info.Indexes[1:]
				} else {
					columns = info.Columns
					x.Indexes = info.Indexes
				}
			*/
		} //else {
		// Формируем список колонок и индексов полей для INSERT запроса
		columns = info.Columns
		x.Indexes = info.Indexes
		//}
	} else {
		// Есть родитель!
		//if len(parent.PrimaryKeyIndexes) == 1 && parent.Seq != "" {
		// У родителя AutoIncrement поле.
		//}
		// Это вложенный объект, значит DELETE и UPDATE запросы не формируем
		if len(parent.PrimaryKey) > 1 {
			// FIX MULTI PARENT ID
			//err = fmt.Errorf("Insert child objects of composite parent primary key not implemented")
			//return
			// Важно выкинуть колонку c parentID и добавить в конец массива
			// Делаем мапу для быстрой проверки колонки.
			parentPrimaryKeyColumns := make(map[string]bool)
			for _, parentColumn := range parent.PrimaryKey {
				parentPrimaryKeyColumns[parentColumn] = true
			}
			for i, column := range info.Columns {
				// Порядок условий очень важен, ибо при пустом Seq может быть пустой PrimaryKey
				//if info.Seq != "" && column == info.PrimaryKey[0] {
				// Если колонка - primary key и auto_increment - выбрасываем из запроса,
				// ибо будет автоматически сгенерирован ID
				//	continue
				//}
				if !parentPrimaryKeyColumns[column] {
					columns = append(columns, column)
					x.Indexes = append(x.Indexes, info.Indexes[i])
				}
			}
			// Дописываем в конец колонки родительского PK
			for _, parentColumn := range parent.PrimaryKey {
				columns = append(columns, parentColumn)
			}
		} else {
			parentColumn = parent.PrimaryKey[0]
			// Важно выкинуть колонку c parentID и добавить в конец массива
			for i, column := range info.Columns {
				// Порядок условий очень важен, ибо при пустом Seq может быть пустой PrimaryKey
				//if info.Seq != "" && column == info.PrimaryKey[0] {
				// Если колонка - primary key и auto_increment - выбрасываем из запроса,
				// ибо будет автоматически сгенерирован ID
				//	continue
				//}
				if column == parentColumn {
					if parent.Seq != "" {
						// У родителя AutoIncrement поле. Запоминаем индекс этой колонки
						// в нашей структуре для правильной вставки с композитным ключем
						// у родителя.
						x.ParentHasAutoPrimaryKey = true
						x.ParentColumnIdx = i
					}
					// колонку с parentID выбрасываем временно, что добавить в конец списка
					// (после цикла). Только для вложенных объектов!
					continue
				}
				columns = append(columns, column)
				x.Indexes = append(x.Indexes, info.Indexes[i])
			}
			// parentID будем брать не из объекта, поэтому в Indexes ничего не добавляем
			// При формировании scan-массива важно не забыть добавить parentID последним
			// элементом (только для List-структур)
			columns = append(columns, parentColumn)
		}
	}

	// ВАЖНО! Итерация по columns, а не x.Indexes, ибо колонок может быть на одну
	// больше чем индексов для вложенных объектов.
	for range columns {
		placeholders = append(placeholders, "?")
	}

	q := fmt.Sprintf(`INSERT INTO %s (%s) VALUES (%s)`, info.Table,
		strings.Join(columns, ", "), strings.Join(placeholders, ", "))

	x.Query = s.RewriteQueryNoCacheNoIN(q)

	for _, fk := range info.ForeignKeys {
		// Проверяем есть ли запись в foreign таблице
		q := fmt.Sprintf(`SELECT 1 FROM %s WHERE %s=?`, fk.Table, fk.Column)
		x.ForeignKeys = append(x.ForeignKeys, foreignKey{
			Query:    s.RewriteQueryNoCacheNoIN(q),
			FieldIdx: fk.FieldIdx,
			Field:    fk.Column,
		})
		// При удалении записи из foreign таблицы, проверяем есть ли в текущей
		// таблице ссылки на удаляемую запись в foreign таблице
		q = fmt.Sprintf("SELECT 1 FROM %s WHERE %s=?", x.Table, fk.Column)
		q = s.RewriteQueryNoCacheNoIN(q)

		queries, ok := s.referrerCheckQueries[fk.Table]
		if ok {
			queries = append(queries, q)
		} else {
			queries = []string{q}
		}
		s.referrerCheckQueries[fk.Table] = queries
	}

	for _, u := range info.Unique {
		q := fmt.Sprintf(`SELECT 1 FROM %s WHERE %s=?`, info.Table, u.Column)
		x.Unique = append(x.Unique, uniqueConstraint{
			Query:    s.RewriteQueryNoCacheNoIN(q),
			FieldIdx: u.FieldIdx,
			Field:    u.Column,
		})
	}

	for _, u := range info.CompositeUnique {
		q := fmt.Sprintf(`SELECT 1 FROM %s WHERE %s`, info.Table, joinWhereClause(u.Columns))
		x.CompositeUnique = append(x.CompositeUnique, compositeUniqueConstraint{
			Query:        s.RewriteQueryNoCacheNoIN(q),
			FieldIndexes: u.Indexes,
			Field:        u.Field,
		})
	}

	for _, list := range info.Lists {
		nestedInsertStruct, err := s.buildInsertStruct(list.StructInfo, info)
		if err != nil {
			return nil, err
		}
		x.Lists = append(x.Lists, insertList{
			Struct: nestedInsertStruct,
			Field:  list.Field,
			Idx:    list.Idx,
		})
	}
	return x, nil
}

// SELECT

type selectStruct struct {
	QueryPrefix       string
	ByPrimaryKeyQuery string
	ScanStruct        scanStruct
	PrimaryKeyIndexes []int
}

/*
Типы SELECT запросов:
- List, выборка по QueryPrefix
- ListBy, выборка по QueryPrefix + querySuffix и adaptQuery()
- ListQuery, выборка по Query или SlicedQuery (для IN запросов)
- One, выборка по QueryPrefix
- OneBy, выборка по QueryPrefix + querySuffix и adaptQuery()
- OneQuery, выборка по Query или SlicedQuery (для IN запросов)

Для List* и One* методов отличается получение типа элемента, выборка из БД.
*/

//func (s *Core) getParsedQuery(rawQuery string) (pq *parsedQuery) {
//	return
//}

func (s *Core) getListElemType(list interface{}) (v reflect.Value, elemType reflect.Type, isptr bool, err error) {
	v = reflect.ValueOf(list)

	if v.Kind() != reflect.Ptr {
		err = fmt.Errorf("qx: non-pointer result arg %T", list)
		return
	}
	v = v.Elem()

	if v.Kind() != reflect.Slice {
		err = fmt.Errorf("qx: result arg must be *slice, not %T", list)
		return
	}
	// type of slice's element
	elemType = v.Type().Elem()

	if elemType.Kind() == reflect.Ptr {
		elemType = elemType.Elem()
		isptr = true
	}

	if elemType.Kind() != reflect.Struct {
		err = fmt.Errorf("qx: slice elem of result arg must be struct or *struct, not %T",
			elemType)
		return
	}
	return
}

func (s *Core) getElem(one interface{}) (elemType reflect.Value, err error) {
	v := reflect.ValueOf(one)

	if v.Kind() != reflect.Ptr {
		err = fmt.Errorf("qx: non-pointer result arg %T", one)
		return
	}
	v = v.Elem()

	if v.Kind() != reflect.Struct {
		err = fmt.Errorf("qx: result arg must be *struct, not %T", one)
		return
	}
	return v, nil
}

func (s *Core) getElemType(one interface{}) (elemType reflect.Type, isptr bool, err error) {
	v := reflect.ValueOf(one)

	if v.Kind() != reflect.Ptr {
		err = fmt.Errorf("qx: non-pointer result arg %T", one)
		return
	}
	v = v.Elem()

	if v.Kind() != reflect.Struct {
		err = fmt.Errorf("qx: result arg must be *struct, not %T", one)
		return
	}
	elemType = v.Type()
	return
}

func (s *Core) getCachedSelectStruct(elemType reflect.Type) (cached *selectStruct, err error) {
	//fmt.Printf("ELEM_TYPE: %s, %v, %p\n\n", elemType, elemType, elemType)
	var ok bool
	// Сперва берем READ LOCK
	s.mutex.RLock()
	cached, ok = s.selectCache[elemType]
	if ok {
		s.mutex.RUnlock()
		return
	}
	s.mutex.RUnlock()

	// Объект не закеширован - берем WRITE LOCK
	s.mutex.Lock()
	defer s.mutex.Unlock()

	// Проверяем еще раз. Возможно между READ и WRITE локами объект уже попал в кэш.
	cached, ok = s.selectCache[elemType]
	if ok {
		return
	}
	// Общий парсер для всех видов запросов
	info, err := parseStruct(elemType)
	if err != nil {
		return
	}

	cached, err = s.buildSelectStruct(info)
	if err != nil {
		return
	}

	s.selectCache[elemType] = cached
	return
}

//func (s *Core) getCachedRawSelectStruct(elemType reflect.Type) (ss rawSelectStruct, err error) {
//	return
//}

func (s *Core) List(f queryFunc, list interface{}) (err error) {
	v, elemType, isptr, err := s.getListElemType(list)
	if err != nil {
		return
	}

	cached, err := s.getCachedSelectStruct(elemType)
	if err != nil {
		return
	}

	return s.findList(findListIn{
		List:          v,
		Query:         cached.QueryPrefix,
		ScanStruct:    cached.ScanStruct,
		QueryFunc:     f,
		ElemType:      elemType,
		IsElemTypePtr: isptr,
	})
}

func (s *Core) ListBy(f queryFunc, list interface{}, querySuffix string, args []interface{}) (err error) {
	v, elemType, isptr, err := s.getListElemType(list)
	if err != nil {
		return
	}

	cached, err := s.getCachedSelectStruct(elemType)
	if err != nil {
		return
	}

	query := cached.QueryPrefix + " " + strings.TrimSpace(querySuffix)

	query, args, err = s.AdaptQuery(query, args)
	if err != nil {
		return
	}

	return s.findList(findListIn{
		List:          v,
		Query:         query,
		Args:          args,
		QueryFunc:     f,
		ScanStruct:    cached.ScanStruct,
		ElemType:      elemType,
		IsElemTypePtr: isptr,
	})
}

func (s *Core) ListQuery(f queryFunc, list interface{}, rawQuery string, args []interface{}) (err error) {
	var isptr bool
	v := reflect.ValueOf(list)

	if v.Kind() != reflect.Ptr {
		err = fmt.Errorf("qx: non-pointer result arg %T", list)
		return
	}
	v = v.Elem()

	if v.Kind() != reflect.Slice {
		err = fmt.Errorf("qx: result arg must be *slice, not %T", list)
		return
	}
	// type of slice's element
	elemType := v.Type().Elem()

	if elemType.Kind() == reflect.Ptr {
		elemType = elemType.Elem()
		isptr = true
	}

	// Важно обрезать пробельные символы, на случай многострочных запросов
	// в обратных апострофах ``
	query, args, err := s.AdaptQuery(strings.TrimSpace(rawQuery), args)
	if err != nil {
		return
	}

	switch elemType.Kind() {
	case reflect.Struct:
		// Читаем в массив структур
		// Из кэша достаем только scanStruct
		cached, err := s.getCachedSelectStruct(elemType)
		if err != nil {
			return err
		}

		return s.findList(findListIn{
			List:          v,
			Query:         query,
			Args:          args,
			QueryFunc:     f,
			ScanStruct:    cached.ScanStruct,
			ElemType:      elemType,
			IsElemTypePtr: isptr,
		})

	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.String, reflect.Bool, reflect.Float32, reflect.Float64:
		// Читаем в массив простых типов

		//fmt.Printf("\nQUERY: %s\n", query)

		rows, err := f(query, args...)
		if err != nil {
			//fmt.Printf("\nERROR: %s\n", err)
			if err == sql.ErrNoRows {
				return nil
			}
			return QueryError{
				query:  query,
				params: args,
				err:    err,
			}
		}
		defer rows.Close()

		for rows.Next() {
			item := reflect.New(elemType).Elem()

			if err := rows.Scan(item.Addr().Interface()); err != nil {
				return err
			}
			if isptr {
				item = item.Addr()
			}
			v.Set(reflect.Append(v, item))
		}
		if err := rows.Err(); err != nil {
			return err
		}
		return nil

	default:
		err = fmt.Errorf("qx: slice elem of result arg must be struct or *struct, not %T",
			elemType)
		return
	}

}

func (s *Core) One(f queryRowFunc, one interface{}) (found bool, err error) {
	dst, err := s.getElem(one)
	if err != nil {
		return
	}

	cached, err := s.getCachedSelectStruct(dst.Type())
	if err != nil {
		return
	}

	var args []interface{}
	for _, fieldIdx := range cached.PrimaryKeyIndexes {
		args = append(args, dst.Field(fieldIdx).Interface())
	}

	return s.findOne(findOneIn{
		Dst:          dst,
		QueryRowFunc: f,
		Query:        cached.ByPrimaryKeyQuery,
		Args:         args,
		ScanStruct:   cached.ScanStruct,
	})
}

func (s *Core) OneBy(f queryRowFunc, one interface{}, querySuffix string, args []interface{}) (found bool, err error) {
	dst, err := s.getElem(one)
	if err != nil {
		return
	}

	cached, err := s.getCachedSelectStruct(dst.Type())
	if err != nil {
		return
	}

	query := cached.QueryPrefix + " " + strings.TrimSpace(querySuffix)

	query, args, err = s.AdaptQuery(query, args)
	if err != nil {
		return
	}

	return s.findOne(findOneIn{
		Dst:          dst,
		QueryRowFunc: f,
		Query:        query,
		Args:         args,
		ScanStruct:   cached.ScanStruct,
	})
}

// OneQuery - читает запись в структуру или читает какое-то значение в переменную-скаляр
// (число, строка, bool...). Ожидает полный запрос.
func (s *Core) OneQuery(f queryRowFunc, one interface{}, rawQuery string, args []interface{}) (found bool, err error) {
	v := reflect.ValueOf(one)

	if v.Kind() != reflect.Ptr {
		err = fmt.Errorf("qx: cannot scan into Go value of type %T", v.Interface())
		return
	}
	v = v.Elem()

	query, args, err := s.AdaptQuery(strings.TrimSpace(rawQuery), args)
	if err != nil {
		return false, err
	}

	switch v.Kind() {
	case reflect.Struct:
		// Читаем в структуру
		cached, err := s.getCachedSelectStruct(v.Type())
		if err != nil {
			return false, err
		}

		return s.findOne(findOneIn{
			Dst:          v,
			QueryRowFunc: f,
			Query:        query,
			Args:         args,
			ScanStruct:   cached.ScanStruct,
		})

	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.String, reflect.Bool, reflect.Float32, reflect.Float64:
		// Читаем в скаляр
		err = f(query, args...).Scan(v.Addr().Interface())
		if err != nil {
			if err != sql.ErrNoRows {
				err = QueryError{
					query:  query,
					params: args,
					err:    err,
				}
			} else {
				err = nil
			}
			return false, err
		}
		return true, nil

	case reflect.Slice:
		// тип []byte
		elemType := v.Type().Elem()
		if elemType.Kind() != reflect.Uint8 {
			err = fmt.Errorf("qx: cannot scan into Go value of type %T", v.Interface())
			return
		}
		// Читаем в скаляр (повторение кода выше)
		err = f(query, args...).Scan(v.Addr().Interface())
		if err != nil {
			if err != sql.ErrNoRows {
				err = QueryError{
					query:  query,
					params: args,
					err:    err,
				}
			} else {
				err = nil
			}
			return false, err
		}
		return true, nil

	default:
		err = fmt.Errorf("qx: cannot scan into Go value of type %T", v.Interface())
		return
	}
}

type findListIn struct {
	List          reflect.Value
	Query         string
	Args          []interface{}
	QueryFunc     queryFunc
	ScanStruct    scanStruct
	ElemType      reflect.Type
	IsElemTypePtr bool
}

func (s *Core) findList(in findListIn) (err error) {
	//	dst := reflect.ValueOf(in.List)
	//	dst = dst.Elem()
	dst := in.List
	//fmt.Printf("\nQUERY: %s\n", query)

	rows, err := in.QueryFunc(in.Query, in.Args...)
	if err != nil {
		//fmt.Printf("\nERROR: %s\n", err)
		if err == sql.ErrNoRows {
			return nil
		}
		return QueryError{
			query:  in.Query,
			params: in.Args,
			err:    err,
		}
	}
	defer rows.Close()

	for rows.Next() {
		item := reflect.New(in.ElemType).Elem()
		var scan []interface{}
		buildScan(&scan, in.ScanStruct, item)

		//fmt.Printf("%#v\n", scan)

		if err := rows.Scan(scan...); err != nil {
			return err
		}
		if in.IsElemTypePtr {
			item = item.Addr()
		}
		dst.Set(reflect.Append(dst, item))
	}
	if err := rows.Err(); err != nil {
		return err
	}

	return nil
}

type findOneIn struct {
	Dst          reflect.Value
	Query        string
	Args         []interface{}
	QueryRowFunc queryRowFunc
	ScanStruct   scanStruct
}

func (s *Core) findOne(in findOneIn) (found bool, err error) {
	dst := in.Dst
	//fmt.Printf("\nin.One: %T\n", in.One)

	item := reflect.New(dst.Type()).Elem()

	var scan []interface{}
	buildScan(&scan, in.ScanStruct, item)

	//fmt.Printf("One: query=%q; args=%v\n\n", in.Query, in.Args)

	err = in.QueryRowFunc(in.Query, in.Args...).Scan(scan...)
	if err != nil {
		//fmt.Printf("\nERROR: %s\n", err)
		if err == sql.ErrNoRows {
			return false, nil
		}
		return false, QueryError{
			query:  in.Query,
			params: in.Args,
			err:    err,
		}
	}
	dst.Set(item)

	return true, nil
}

type hasIn struct {
	Table        string
	Where        string
	Args         []interface{}
	QueryRowFunc queryRowFunc
}

func (s *Core) Has(in hasIn) (found bool, err error) {
	if !strings.HasPrefix(in.Where, "WHERE") {
		in.Where = "WHERE " + in.Where
	}
	rawQuery := fmt.Sprintf("SELECT 1 FROM %s %s", in.Table, in.Where)
	query, args, err := s.AdaptQuery(rawQuery, in.Args)
	if err != nil {
		return
	}

	//fmt.Printf("%s, %v\n\n", query, args)

	var expectOne int
	err = in.QueryRowFunc(query, args...).Scan(&expectOne)
	if err != nil {
		if err != sql.ErrNoRows {
			err = QueryError{
				query:  query,
				params: args,
				err:    err,
			}
		}
		err = nil
	}
	return expectOne == 1, nil
}

/*

func (s *Core) OneValue(f queryRowFunc, one interface{}, rawQuery string, args []interface{}) (found bool, err error) {
	v := reflect.ValueOf(one)

	if v.Kind() != reflect.Ptr {
		err = fmt.Errorf("qx: cannot scan into Go value of type %T", one)
		return
	}
	v = v.Elem()

	switch v.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.String, reflect.Bool, reflect.Float32, reflect.Float64:
	default:
		err = fmt.Errorf("qx: cannot scan into Go value of type %T", one)
		return
	}

	query, args, err := s.AdaptQuery(strings.TrimSpace(rawQuery), args)
	if err != nil {
		return
	}
	err = f(query, args...).Scan(v.Addr().Interface())
	if err != nil {
		if err != sql.ErrNoRows {
			err = QueryError{
				query:  query,
				params: args,
				err:    err,
			}
		} else {
			err = nil
		}
		return
	}
	return true, nil
}
*/

//fmt.Sprintf("WHERE %s.%s=?", table.Table, , item.Field(0).Interface()))
/*
	for i := 0; i < v.Len(); i++ {
		err = loadLists(sr, plan.Table, v.Index(i))
		if err != nil {
			return
		}
	}
func loadLists(sr *sql.Tx, table *selectTable, item reflect.Value) (err error) {
	for _, list := range table.Lists {
		dst := item.Field(list.FieldIdx)

		err = findList(sr, dst.Addr(), "WHERE modemDevices.imei=?", item.Field(0).Interface())
		if err != nil {
			return
		}
	}
	return
}
*/
/*
func (s *Core) ListValues(f queryFunc, list interface{}, rawQuery string, args []interface{}) (err error) {
	v := reflect.ValueOf(list)

	if v.Kind() != reflect.Ptr {
		return fmt.Errorf("qx: result arg must be *[]scalar or *[]*scalar, not %T", list)
	}
	v = v.Elem()

	if v.Kind() != reflect.Slice {
		return fmt.Errorf("qx: result arg must be *[]scalar or *[]*scalar, not %T", list)
	}
	// type of slice's element
	elemType := v.Type().Elem()

	switch elemType.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.String, reflect.Bool, reflect.Float32, reflect.Float64:
	default:
		err = fmt.Errorf("qx: cannot scan into Go value of type %s", elemType)
		return
	}

	// Важно обрезать пробельные символы, на случай многострочных запросов
	// в обратных апострофах ``
	query, args, err := s.AdaptQuery(strings.TrimSpace(rawQuery), args)
	if err != nil {
		return
	}

	//fmt.Printf("\nQUERY: %s\n", query)

	rows, err := f(query, args...)
	if err != nil {
		//fmt.Printf("\nERROR: %s\n", err)
		if err == sql.ErrNoRows {
			return nil
		}
		return QueryError{
			query:  query,
			params: args,
			err:    err,
		}
	}
	defer rows.Close()

	for rows.Next() {
		item := reflect.New(elemType).Elem()

		if err := rows.Scan(item.Addr().Interface()); err != nil {
			return err
		}
		v.Set(reflect.Append(v, item))
	}
	if err := rows.Err(); err != nil {
		return err
	}
	return nil
}
*/

// RegisterDMTypes - регистрирует (парсит и добавляет в кэш) Data Modification Types
// (структуры для Insert / Update / Remove запросов). Метод необходим, если требуется
// проверка Foreign Keys при удалении из таблицы.
func (s *Core) RegisterDMTypes(objects []interface{}) (err error) {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	for _, obj := range objects {
		t := reflect.TypeOf(obj)
		info, err := parseStruct(t)
		if err != nil {
			return err
		}
		cached, err := s.buildInsertStruct(info, nil)
		if err != nil {
			return err
		}
		s.insertCache[t] = cached
	}
	return
}
