Skip to content

Latest commit

 

History

History
203 lines (169 loc) · 8.71 KB

File metadata and controls

203 lines (169 loc) · 8.71 KB

5.1 Interfaz database/sql

Go no provee ningún manejador de base de datos oficial, a diferencia de otros lenguajes como PHP que si lo hacen. Sin embargo, si provee una estándar de interfaz para que los desarrolladores desarrollen manejadores basados en ella. La ventaja es que si tu código es desarrollado de acuerdo a esa interfaz estándar, no vas a tener que cambiar ningún código si tu base de datos cambia. Veamos cuales son los estándares de esta interface.

sql.Register

Esta función está en el paquete database/sql para registrar una base de datos cuando usas un manejador de base de datos de terceros. Todos deberían llamar a la función Register(name String, driver driver.Driver) en la función init() en orden de registrarse a si mismas.

Vamos a mirar los corespondientes llamados en el código de mymysql y sqlite3:

	//manejador https://github.com/mattn/go-sqlite3
	func init() {
	    sql.Register("sqlite3", &SQLiteDriver{})
	}

	//manejador https://github.com/mikespook/mymysql
	// Driver automatically registered in database/sql
	var d = Driver{proto: "tcp", raddr: "127.0.0.1:3306"}
	func init() {
	    Register("SET NAMES utf8")
	    sql.Register("mymysql", &d)
	}

Vemos que los manejadores de terceros implementan esta función para registrarse a si mismos y Go utiliza un mapa para guardar los manejadores dentro de database/sql.

	var drivers = make(map[string]driver.Driver)

	drivers[name] = driver

Así, la función de registro puede registrar tantos manejadores como requieras, cada uno con un nombre diferente.

Siempre veremos el siguiente código cuando usemos manejadores de terceros:

    import (
        "database/sql"
        _ "github.com/mattn/go-sqlite3"
    )

Aquí, el guión bajo, (también llamado 'blank') _ puede ser un poco confuso para muchos principiantes, pero es una gran característica de Go. Sabemos que el identificador de guión bajo es usado para no guardar valores que una función retorna, y también que debes usar todos los paquetes que importes en tu código Go. Entonces, cuando el guión bajo es usado con un import, significa que necesitas ejecutar la función init() de ese paquete sin usarlo directamente. lo cual encaja perfectamente en el caso de registrar el manejador de base de datos.

driver.Driver

Driver es una interface que contiene un método Open(name string) que retorna una interfaz Conn.

	type Driver interface {
	    Open(name string) (Conn, error)
	}

Esta es una conexión de una vez, que significa que solamente puede ser usada una vez por rutina. El siguiente código causará errores:

	...
	go goroutineA (Conn)  // query
	go goroutineB (Conn)  // insert
	...

Porque no no tiene idea de que rutina va a realizar cada operación, la operación de consulta podría obtener el resultado de la operación de incersión y viceversa.

Todos los manejadores de terceros debería tener esta función para analizar el nombre de una Conn y retornar el resultado correcto.

driver.Conn

Esta es la interfaz de conexión con algunos métodos, como dije arriba, la misma conexión solo puede usarse una vez por rutina.

	type Conn interface {
	    Prepare(query string) (Stmt, error)
	    Close() error
	    Begin() (Tx, error)
	}
  • Prepare retorna el estado del comando SQL ejecutado para consultas, eliminaciones, etc.
  • Close cierra la conexión actual y limpia los recursos. La mayoría de paquetes de terceros implementan algún tipo de piscina de conexiones, así que no necesitas almacenar las conexiones que puedan causar errores.
  • Begin retorna un Tx que representa una transacción para manejar. Puedes usarlo para consultar, almacenar, o volver hacia atrás algunas transacciones.

driver.Stmt

Este es un estado de listo que corresponde con una Conn, y solamente puede ser usada una vez por rutina (como en el caso de Conn).

	type Stmt interface {
	    Close() error
	    NumInput() int
	    Exec(args []Value) (Result, error)
	    Query(args []Value) (Rows, error)
	}
  • Close cierra la conexión actual, pero sigue retornando la información de la ejecución de una operación de consulta.
  • NumInput retorna el número de argumentos obligados. Los manejadores de bases de datos debería revisar la cantidad de argumentos cuando los resultados son mayor que 0, y retorna -1 cuando el manejador de la base de datos no conoce ningún argumento obligado.
  • Exec ejecuta un comando update/insert preparados en Prepare, retorna un Result.
  • Query ejecuta un comando select preparado en Prepare, retorna información.

driver.Tx

Generalmente, las transacciones únicamente tienen métodos de envío y de vuelta atrás, y el manejador de las bases de datos solo necesita implementar estos dos métodos.

	type Tx interface {
	    Commit() error
	    Rollback() error
	}

driver.Execer

Esta es una interfaz opcional.

	type Execer interface {
	    Exec(query string, args []Value) (Result, error)
	}

El manejador no implementa esta interfaz, cuando llame DB.Exec, automáticamente llamará Prepare, retornará un Stmt. Después ejecutará el método Exec de Smtt, y cerrará el Smtm.

driver.Result

Esta es la interface para los operaciones de update/insert.

	type Result interface {
	    LastInsertId() (int64, error)
	    RowsAffected() (int64, error)
	}
  • LastInsertId retorna el identificador de autoincremento después de una operación de inserción.
  • RowsAffected retorna la cantidad de filas afectadas por la operación.

driver.Rows

Esta interfaz es el resultado de una operación de consulta.

	type Rows interface {
	    Columns() []string
	    Close() error
	    Next(dest []Value) error
	}
  • Columns retorna la información de los campos de la base de datos. El segmento tiene correspondencia con los campos SQL únicamente. y no retorna todos los campos de la tabla de la base de datos.
  • Close cierra el iterador sobre las columnas.
  • Next retorna la siguiente información y la asigna a dest, convirtiendo todas las cadenas en arreglos de bytes, y retorna un error io.EOF si no existe mas data disponible.

driver.RowsAffected

Este es un alias para int64, pero implementado por la interfaz Result.

	type RowsAffected int64

	func (RowsAffected) LastInsertId() (int64, error)

	func (v RowsAffected) RowsAffected() (int64, error)

driver.Value

Esta es una interfaz vacía que contiene cualquier tipo de información.

	type Value interface{}

El Value debe ser algo con lo que los manejadores puedan operar, o nil, así que debería ser alguno de los siguientes tipos:

	int64
	float64
	bool
	[]byte
	string   [*] Exceptuando los Rows.Next, que no puede retornar una cadena.
	time.Time

driver.ValueConverter

Define una interfaz para convertir valores nativos en valores de driver.Value.

	type ValueConverter interface {
	    ConvertValue(v interface{}) (Value, error)
	}

Esta interfaz es comunmente usada en manejadores de bases de datos y tiene muchas características útiles:

  • Convierte un driver.Value al tipo de campo correspondiente, por ejemplo convierte un int64 a un uint16.
  • Conviernte una consulta de base de datos a un driver.Value.
  • Conviernte un driver.Value a un usuario definido en la función scan.

driver.Valuer

Define una interfaz para devolver un driver.Value.

	type Valuer interface {
	    Value() (Value, error)
	}

Muchos tipos implementan esta interface para la conversión entre driver.Value y ellos mismos.

En este punto, deberías saber un poco mas sobre el desarrollo de manejadores de bases de datos en Go. una vez que hayas implementado interfaces para operaciones como agregar, eliminar, actualizar, etc, existirán algunos problemas relacionados con comunicarse específicamente con algún tipo de bases de datos.

database/sql

database/sql define en un alto nivel métodos existentes en database/sql/driver para tener las operaciones en un nivel mas conveniente, y sugiera que implementes una piscina de conexiones.

	type DB struct {
	    driver   driver.Driver
	    dsn      string
	    mu       sync.Mutex // protects freeConn and closed
	    freeConn []driver.Conn
	    closed   bool
	}

Como puedes ver, la función Open retorna una base de datos que tiene una freeConn, y esta es una piscina de conexiones simple. Su implementación es simple y fea. Usa defer db.putConn(ci, err) en la función Db.Prepare para colocar una conexión en la piscina de conexiones. Siempre que se vaya a llamar la función Conn, verifica la longitud de freeConn. Si es mayor a 0, significa que hay una conexión reusable y la retorna directamente a ti. De otra manera, crea una nueva conexión y la retorna.

Enlaces