Skip to content

Latest commit

 

History

History
470 lines (348 loc) · 26.8 KB

README-ru.md

File metadata and controls

470 lines (348 loc) · 26.8 KB

ozzo-dbx

Build Status GoDoc

ozzo-dbx это Go-пакет, который расширяет стандартный пакет database/sql, путем предоставления мощных методов извлечения данных, а также позволяет использовать DB-agnostic (независимый от БД) построитель запросов. Он имеет следующие особенности:

  • Заполнение данных в структуры или NullString карты
  • Именованные параметры связывания
  • DB-agnostic методы построения запросов, включая SELECT, запросы на манипуляцию с данными и запросы на манипуляцию со схемой (структурой) БД
  • Мощный построитель запросов с условиями
  • Открытая архитектура, позволяющая с легкостью создавать поддержку для новых баз данных или кастомизировать текущую поддержку
  • Логирование выполненных SQL запросов
  • Предоставляет поддержку основных реляционных баз данных

Требования

Go 1.2 или выше.

Установка

Выполните данные команды для установки:

go get github.com/go-ozzo/ozzo-dbx

В дополнение, установите необходимый пакет драйвера базы данных для той базы, которая будет использоваться. Пожалуйста обратитесь к SQL database drivers для получения полного списка. Например, если вы используете MySQL, вы можете загрузить этот пакет:

go get github.com/go-sql-driver/mysql

и импортировать его в ваш основной код следующим образом:

import _ "github.com/go-sql-driver/mysql"

Поддерживаемые базы данных

Представленные базы данных уже имеют поддержку из коробки:

  • SQLite
  • MySQL
  • PostgreSQL
  • MS SQL Server (2012 или выше)
  • Oracle

Другие базы данных также могут работать. Если нет, вы можете создать для него свой построитель, как описано далее в этом документе.

С чего начать

Представленный ниже код показывает как вы можете использовать пакет для доступа к данным базы данных MySQL.

import (
	"fmt"
	"github.com/go-ozzo/ozzo-dbx"
	_ "github.com/go-sql-driver/mysql"
)

func main() {
	db, _ := dbx.Open("mysql", "user:pass@/example")

	// создаем новый запрос
	q := db.NewQuery("SELECT id, name FROM users LIMIT 10")

	// извлекаем все строки в срез структур
	var users []struct {
		ID, Name string
	}
	q.All(&users)

	// извлекаем одну строку в структуру
	var user struct {
		ID, Name string
	}
	q.One(&user)

	// извлекаем строку в строковую карту
	data := dbx.NullStringMap{}
	q.One(data)

	// извлечение строку за строкой
	rows2, _ := q.Rows()
	for rows2.Next() {
		rows2.ScanStruct(&user)
		// rows.ScanMap(data)
		// rows.Scan(&id, &name)
	}
}

Следующий пример показывает, как можно использовать возможности построителя запросов из этого пакета.

import (
	"fmt"
	"github.com/go-ozzo/ozzo-dbx"
	_ "github.com/go-sql-driver/mysql"
)

func main() {
	db, _ := dbx.Open("mysql", "user:pass@/example")

	// строим SELECT запрос
	//   SELECT `id`, `name` FROM `users` WHERE `name` LIKE '%Charles%' ORDER BY `id`
	q := db.Select("id", "name").
		From("users").
		Where(dbx.Like("name", "Charles")).
		OrderBy("id")

	// извлекаем все строки в срез структур
	var users []struct {
		ID, Name string
	}
	q.All(&users)

	// строим INSERT запрос
	//   INSERT INTO `users` (`name`) VALUES ('James')
	db.Insert("users", dbx.Params{
		"name": "James",
	}).Execute()
}

Соединение с базой данных

Для соединения с базой данных, используйте dbx.Open() точно таким же образом, как вы могли бы сделать это при помощи Open() метода в database/sql.

db, err := dbx.Open("mysql", "user:pass@hostname/db_name")

Метод возвращает экземпляр dbx.DB который можно использовать для создания и выполнения запросов к БД. Обратите внимание, что метод на самом деле не устанавливает соединение до тех пор пока вы не выполните запрос с использованием экземпляра dbx.DB. Он так же не проверяет корректность имени источника данных. Выполните dbx.MustOpen() для того чтобы убедиться, что имя базы данных правильное.

Выполнение запросов

Для выполнения SQL запроса, сначала создайте экземпляр dbx.Query, и далее создайте запрос при помощи DB.NewQuery() с SQL выражением, которое необходимо исполнить. И затем выполните Query.Execute() для исполнения запроса, в случае если запрос не предназначен для извлечения данных. Например,

q := db.NewQuery("UPDATE users SET status=1 WHERE id=100")
result, err := q.Execute()

Если SQL запрос должен вернуть данные (такой как SELECT), следует вызвать один из нижепреведенных методов, которые выполнят запрос и сохранят результат в указанной переменной/переменных.

  • Query.All(): заполняет массив структур или NullString карты всеми строками результата.
  • Query.One(): сохраняет первую строку из результата в структуру или в NullString карту.
  • Query.Row(): заполняет список переменных первой строкой результата, каждая перменная заполняется данными одной из возвращаемых колонок.
  • Query.Rows(): возвращает экземпляр dbx.Rows для обеспечения дальнейшей возможности извлечения данных методом строка за строкой.

Например,

type User struct {
	ID   int
	Name string
}

var (
	users []User
	user User

	row dbx.NullStringMap

	id   int
	name string

	err error
)

q := db.NewQuery("SELECT id, name FROM users LIMIT 10")

// заполняет срез User всеми строками из запроса
err = q.All(&users)
fmt.Println(users[0].ID, users[0].Name)

// заполняет структуру User данными из первой строки
err = q.One(&user)
fmt.Println(user.ID, user.Name)

// запоминает первую строку в карту NullString
err = q.One(&row)
fmt.Println(row["id"], row["name"])

// заполняет переменные id и name данными из первой строки
err = q.Row(&id, &name)

// заполнят данные методом строка за строкой
rows, _ := q.Rows()
for rows.Next() {
	rows.ScanMap(&row)
}

При заполнении структуры, данные правила используются для определения какая колонка будет сохранена в какое поле структуры:

  • Только экспортируемые поля структуры будут заполнены.
  • Поле принимает данные, если его имя имеет отображение к столбцу в соответствии с функцией отображения Query.FieldMapper. Функция отображения поля по умолчанию разделяет слова в имени поля подчеркиванием и приводит их к нижнему регистру. Например, поле FirstName будет отображено в имя столбца first_name, и MyID в my_id.
  • Если поле имеет db тег, значение тега будет использовано для опредления соответствующего имени столбца. Если db тег имеет -, это обозначает, что поле НЕ будет заполнено.
  • Анонимные поля типа структуры будут расширены и будут заполнены в соответствии с правилами описанными выше.
  • Именованные поля типа структуры также будут раширяться. Но их составные поля будут иметь префикс с именем структуры при заполнении.

Представленный ниже пример показывает, как поля будут заполняться в соответствии с указанными выше правилами:

type User struct {
	id     int
	Type   int `db:"-"`
	MyName string `db:"name"`
	Prof   Profile
}

type Profile struct {
	Age int
}
  • User.id: не будет заполнено в следствие того, что поле id не экспортируется (т.к. оно записано с маленькой буквы);
  • User.Type: не будет заполнено потому что поле имеет db тег -;
  • User.MyName: будет заполнено из столбца name, в соответствии с тегом db;
  • Profile.Age: будет заполнено из столбца prof.age, т.к. Prof имя поля типа структуры и оно получит префикс prof..

Обратите внимание, что если столбец не имеет соответствующего поля в структуре, он будет проигнорирован. По аналогии, если поле структуры не имееет соответствующего столбца в результате, то оно не будет заполнено.

Связывание параметров

SQL запросы, как правило, имеют димамические значения. Например, вы можете выбрать запись пользоателя в соответствии с ID пользователя, полученного от клиента. В этом случае должно быть выполнено связывание параметров, и это почти всегда предпочтительно в целях обеспечения безопасности. В отличии от database/sql которая разрешает анонимное связывание, ozzo-dbx использует для связывания именованые параметры. Например,

q := db.NewQuery("SELECT id, name FROM users WHERE id={:id}")
q.Bind(dbx.Params{"id": 100})
q.One(&user)

Приведенный выше пример выберет пользователя id которого 100. Метод Query.Bind() связывает набор из именовынных параметров с SQL запросом который содержит плейсхолдеры в формате {:ParamName}.

Если оператор SQL должен быть выполнен несколько раз с различными параметрами, он должен быть подготовлен для увеличения производительности. Например,

q := db.NewQuery("SELECT id, name FROM users WHERE id={:id}")
q.Prepare()

q.Bind(dbx.Params{"id": 100})
q.One(&user)

q.Bind(dbx.Params{"id": 200})
q.One(&user)

// ...

Обратите внимание, что анонимные параметры не поддерживаются, т.к. это внесет беспорядок в именованное связывание.

Построение запросов

Вместо того чтобы писать обычные SQLs запросы, ozzo-dbx позволяет строить SQL запросы программно, что чаще всего приводит к чистоте кода и большей защищенности, а так делает код не зависимым от используемой базы данных. Вы можете построить три типа запросов: SELECT запросы, запросы для манипуляции с данными и запросы для манипуляции со схемой (структурой) БД.

Построение SELECT запросов

Построение SELECT запроса начинается с вызова DB.Select(). Вы можете использовать различные условия для SELECT запросов, используя соответствующие методы построения запросов. Например,

db, _ := dbx.Open("mysql", "user:pass@/example")
db.Select("id", "name").
	From("users").
	Where(dbx.HashExp{"id": 100}).
	One(&user)

Приведенный выше код будет генерировать следующий SQL запрос:

SELECT `id`, `name` FROM `users` WHERE `id`={:p0}

Обратите внимание, что название таблицы и имена стобцов будут правильно экранированы в сосответствии с типом используемой базы данных. И параметр подстановки значения p0 попадет в условие WHERE.

Каждое ключевое слово SQL имеет свой соответствующий метод в построителе запросов. Например, SELECT соответствет Select(), FROM соответствует From(), WHERE соответствует Where(), и так далее. Вы можете использовать эти методы совместно, как и в обычном SQL запросе. Каждый из этих методов возвращает экземпляр запроса (типа dbx.SelectQuery) который строится. Как только вы закончите построения запроса, вы можете вызвать методы, такие как One(), All() для выполнения запроса и и сохранения данных в переменные. Также вы можете явно вызвать Build() для построения запроса и превратить его в экземпляр dbx.Query что может помоч вам получить SQL выражение и делать другую интересную работу.

Построение условий запроса

ozzo-dbx поддерживает очень гибкий и мощный построитель условий для запроса, который может быть использован для SQL выражений таких как WHERE, HAVING, и т.д. Например,

// id=100
dbx.NewExp("id={:id}", dbx.Params{"id": 100})

// id=100 AND status=1
dbx.HashExp{"id": 100, "status": 1}

// status=1 OR age>30
dbx.Or(dbx.HashExp{"status": 1}, dbx.NewExp("age>30"))

// name LIKE '%admin%' AND name LIKE '%example%'
dbx.Like("name", "admin", "example")

При построении выражения условий в запросе, значение его параметров будет заполнено с помощью параметрического связывания, которое предотвращает использование SQL инекций. Кроме того, если выражение содержит имена столбцов, они будут правильно экранированы. Доступны слудующие функции для построениея условий:

  • dbx.NewExp(): создает условие используя заданное строкового выражение и связываемый параметр. Например, dbx.NewExp("id={:id}", dbx.Params{"id":100}) создаст выражение id=100.
  • dbx.HashExp: подставляет пары имя:значение в AND оператор. Например, dbx.HashExp{"id":100, "status":1} создает id=100 AND status=1.
  • dbx.Not(): создает NOT выражение подставляя NOT в заданное выражение.
  • dbx.And(): создает AND выражение путем объединения заданных выражений при помощи AND оператора.
  • dbx.Or(): создает выражение OR путем объединения заданных выражений при помощи оператора OR.
  • dbx.In(): создает IN выражение для указанного столбца и диапазона значений. Например, dbx.In("age", 30, 40, 50) создаст выражение age IN (30, 40, 50). Обратите внимание, что, если диапазон значений пуст, он будет генерировать выражение, представляющее ложное значение.
  • dbx.NotIn(): создает NOT IN выражение. Что очень похоже на dbx.In().
  • dbx.Like(): создает LIKE выражение для указанного столбца и диапазона значений. Например, dbx.Like("title", "golang", "framework") создаст выражение title LIKE "%golang%" AND title LIKE "%framework%". Вы можете дополнить LIKE выражение при помощи Escape() и/или Match() функции полученного выражения. Обратите внимание, что, если диапазон значений пуст, он будет генерировать пустое выражение.
  • dbx.NotLike(): создает NOT LIKE выражение. Что очень похоже на dbx.Like().
  • dbx.OrLike(): создает LIKE выражение объединением различных LIKE подвыражений при помощи OR вместо AND.
  • dbx.OrNotLike(): создает NOT LIKE выражение путем объединения NOT LIKE выражений используя OR вместо AND.
  • dbx.Exists(): создает EXISTS выражение путем добавления EXISTS к заданному выражению.
  • dbx.NotExists(): создает NOT EXISTS выражение путем добавления NOT EXISTS к заданному выражению.
  • dbx.Between(): создает BETWEEN выражение. Например, dbx.Between("age", 30, 40)создает выражение age BETWEEN 30 AND 40.
  • dbx.NotBetween(): создает NOT BETWEEN выражение.

Вы также можете создавать другие удобные функции для помощи в построении условий, таким образом, чтобы функция возвращала объект реализующий интерфейс dbx.Expression.

Построение запросов для манипуляции с данными

Запросы манипуляции с данными это запросы которые изменяют данные в базе данных, такие как INSERT, UPDATE, DELETE операторы. Такого рода запросы могут быть построены путем вызова соответствующих методов DB. Например,

db, _ := dbx.Open("mysql", "user:pass@/example")

// INSERT INTO `users` (`name`, `email`) VALUES ({:p0}, {:p1})
db.Insert("users", dbx.Params{
	"name": "James",
	"email": "[email protected]",
}).Execute()

// UPDATE `users` SET `status`={:p0} WHERE `id`={:p1}
db.Update("users", dbx.Params{"status": 1}, dbx.HashExp{"id": 100}).Execute()

// DELETE FROM `users` WHERE `status`={:p0}
db.Delete("users", dbx.HashExp{"status": 2}).Execute()

При построении запросов на манипуляцию с данными, не забудьте в конце использовать Execute() для выполнения запроса.

Построение запросов для работы со схемой (структурой) БД

Запросы для манипулирования со схемой БД изменяют структуру БД, то есть создают новую таблицу, добавляют новый столбец и т.д. Эти запросы могут быть построены путем вызова соответствующих методов DB. Например,

db, _ := dbx.Open("mysql", "user:pass@/example")

// CREATE TABLE `users` (`id` int primary key, `name` varchar(255))
q := db.CreateTable("users", map[string]string{
	"id": "int primary key",
	"name": "varchar(255)",
})
q.Execute()

Экранирование названия таблиц и столбцов

Различные базы данных по разному экранируют (заключают в кавычки) название таблиц и названия столбцов. Для возможности писать SQL запросы, независимые от БД (DB-agnostic SQLs), ozzo-dbx предоставляет специальный синтакс для экранирования названия имен таблиц и столбцов. Слово заключенное в {{ и }} трактуется как имя таблицы и будет экранировано в соответствии с используемым драйвером БД. Подобным образом слово заключенное в [[ и ]] трактуется как имя столбца и также будет правильно экранировано. Например, при работе с базой данных MySQL, следующий запрос будет должным образом экранирован:

// SELECT * FROM `users` WHERE `status`=1
q := db.NewQuery("SELECT * FROM {{users}} WHERE [[status]]=1")

Обратите внимание, что если имя таблицы или столбца содержит префикс, он все равно будет правильно проэкранирован. Например, {{public.users}} будет экранирован как "public"."users" для PostgreSQL.

Использование транзакций

Вы можете использовать все возможные методы для выполнения запросов с использованием транзакций. Например,

db, _ := dbx.Open("mysql", "user:pass@/example")

tx, _ := db.Begin()

_, err1 := tx.Insert("users", dbx.Params{
	"name": "user1",
}).Execute()
_, err2 := tx.Insert("users", dbx.Params{
	"name": "user2",
}).Execute()

if err1 == nil && err2 == nil {
	tx.Commit()
} else {
	tx.Rollback()
}

Логирование исполеннных SQL запросов

Когда DB.LogFunc настроен для логирования, все SQL будут выполены и сохранены в лог. В следующем примере показано как сконфигурировать логгер при использования стандартного log пакета:

import (
	"fmt"
	"log"
	"github.com/go-ozzo/ozzo-dbx"
)

func main() {
	db, _ := dbx.Open("mysql", "user:pass@/example")
	db.LogFunc = log.Printf

	// ...
)

А этот пример показывет как применять ozzo-log пакет, который позволяет использовать уровни важности и категории, а также отправлять сообщения различным получателям (таким как файлы, окно консоли, сетевое устройство).:

import (
	"fmt"
	"github.com/go-ozzo/ozzo-dbx"
	"github.com/go-ozzo/ozzo-log"
	_ "github.com/go-sql-driver/mysql"
)

func main() {
	logger := log.NewLogger()
	logger.Targets = []log.Target{log.NewConsoleTarget()}
	logger.Open()

	db, _ := dbx.Open("mysql", "user:pass@/example")
	db.LogFunc = logger.Info

	// ...
)

Поддержка дополнительных баз данных

Не смотря на то, что ozzo-dbx "из коробки" предоставляет поддержку всех основных реляционных баз данных, его окрытая архитектура позволяет вам добавлять поддержку новых баз данных. Действия для добавления новой базы данных включают в себя:

  • создание структуры, которая имплементирует интерфейс QueryBuilder. Вы можете использовать BaseQueryBuilder напрямую или расширить его функциональность.
  • Создать структуру которая имплементирует интерфейс Builder. Вы можете расширить BaseBuilder с помощью композиции.
  • Напишите init() функцию для регистрации нового построителя в dbx.BuilderFuncMap.