|
| 1 | +# 5.1 Interfaz database/sql |
| 2 | + |
| 3 | +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. |
| 4 | + |
| 5 | +## sql.Register |
| 6 | + |
| 7 | +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. |
| 8 | + |
| 9 | +Vamos a mirar los corespondientes llamados en el código de mymysql y sqlite3: |
| 10 | +``` |
| 11 | + //manejador https://github.com/mattn/go-sqlite3 |
| 12 | + func init() { |
| 13 | + sql.Register("sqlite3", &SQLiteDriver{}) |
| 14 | + } |
| 15 | +
|
| 16 | + //manejador https://github.com/mikespook/mymysql |
| 17 | + // Driver automatically registered in database/sql |
| 18 | + var d = Driver{proto: "tcp", raddr: "127.0.0.1:3306"} |
| 19 | + func init() { |
| 20 | + Register("SET NAMES utf8") |
| 21 | + sql.Register("mymysql", &d) |
| 22 | + } |
| 23 | +``` |
| 24 | +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`. |
| 25 | +``` |
| 26 | + var drivers = make(map[string]driver.Driver) |
| 27 | +
|
| 28 | + drivers[name] = driver |
| 29 | +``` |
| 30 | +Así, la función de registro puede registrar tantos manejadores como requieras, cada uno con un nombre diferente. |
| 31 | + |
| 32 | +Siempre veremos el siguiente código cuando usemos manejadores de terceros: |
| 33 | +``` |
| 34 | + import ( |
| 35 | + "database/sql" |
| 36 | + _ "github.com/mattn/go-sqlite3" |
| 37 | + ) |
| 38 | +``` |
| 39 | +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. |
| 40 | + |
| 41 | +## driver.Driver |
| 42 | + |
| 43 | +`Driver` es una interface que contiene un método `Open(name string)` que retorna una interfaz `Conn`. |
| 44 | +``` |
| 45 | + type Driver interface { |
| 46 | + Open(name string) (Conn, error) |
| 47 | + } |
| 48 | +``` |
| 49 | +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: |
| 50 | +``` |
| 51 | + ... |
| 52 | + go goroutineA (Conn) // query |
| 53 | + go goroutineB (Conn) // insert |
| 54 | + ... |
| 55 | +``` |
| 56 | +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. |
| 57 | + |
| 58 | +Todos los manejadores de terceros debería tener esta función para analizar el nombre de una Conn y retornar el resultado correcto. |
| 59 | + |
| 60 | +## driver.Conn |
| 61 | + |
| 62 | +Esta es la interfaz de conexión con algunos métodos, como dije aarriba, la misma conexión solo puede usarse una vez por rutina. |
| 63 | +``` |
| 64 | + type Conn interface { |
| 65 | + Prepare(query string) (Stmt, error) |
| 66 | + Close() error |
| 67 | + Begin() (Tx, error) |
| 68 | + } |
| 69 | +``` |
| 70 | +- `Prepare` retorna el estado del comando SQL ejecutado para consultas, eliminaciones, etc. |
| 71 | +- `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. |
| 72 | +- `Begin` retorna un Tx que representa una transacción para manejar. Puedes usarlo para consultar, almacenar, o volver hacia atrás algunas transacciones. |
| 73 | + |
| 74 | +## driver.Stmt |
| 75 | + |
| 76 | +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). |
| 77 | +``` |
| 78 | + type Stmt interface { |
| 79 | + Close() error |
| 80 | + NumInput() int |
| 81 | + Exec(args []Value) (Result, error) |
| 82 | + Query(args []Value) (Rows, error) |
| 83 | + } |
| 84 | +``` |
| 85 | +- `Close` cierra la conexión actual, pero sigue retornando la información de la ejecución de una operación de consulta. |
| 86 | +- `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. |
| 87 | +- `Exec` ejecuta un comando `update/insert` preparados en `Prepare`, retorna un `Result`. |
| 88 | +- `Query` ejecuta un comando `select` preparado en `Prepare`, retorna información. |
| 89 | + |
| 90 | +## driver.Tx |
| 91 | + |
| 92 | +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. |
| 93 | +``` |
| 94 | + type Tx interface { |
| 95 | + Commit() error |
| 96 | + Rollback() error |
| 97 | + } |
| 98 | +``` |
| 99 | +## driver.Execer |
| 100 | +Esta es una interfaz opcional. |
| 101 | +``` |
| 102 | + type Execer interface { |
| 103 | + Exec(query string, args []Value) (Result, error) |
| 104 | + } |
| 105 | +``` |
| 106 | +El 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`. |
| 107 | + |
| 108 | +## driver.Result |
| 109 | + |
| 110 | +Esta es la interface para los operaciones de `update/insert`. |
| 111 | +``` |
| 112 | + type Result interface { |
| 113 | + LastInsertId() (int64, error) |
| 114 | + RowsAffected() (int64, error) |
| 115 | + } |
| 116 | +``` |
| 117 | +- `LastInsertId` retorna el identificador de autoincremento después de una operación de inserción. |
| 118 | +- `RowsAffected` retorna la cantidad de filas afectadas por la operación. |
| 119 | + |
| 120 | +## driver.Rows |
| 121 | + |
| 122 | +Esta interfaz es el resultado de una operación de consulta. |
| 123 | +``` |
| 124 | + type Rows interface { |
| 125 | + Columns() []string |
| 126 | + Close() error |
| 127 | + Next(dest []Value) error |
| 128 | + } |
| 129 | +``` |
| 130 | +- `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. |
| 131 | +- `Close` cierra el iterador sobre las columnas. |
| 132 | +- `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. |
| 133 | + |
| 134 | +## driver.RowsAffected |
| 135 | + |
| 136 | +Este es un alias para int64, pero implementado por la interfaz Result. |
| 137 | +``` |
| 138 | + type RowsAffected int64 |
| 139 | +
|
| 140 | + func (RowsAffected) LastInsertId() (int64, error) |
| 141 | +
|
| 142 | + func (v RowsAffected) RowsAffected() (int64, error) |
| 143 | +``` |
| 144 | +## driver.Value |
| 145 | + |
| 146 | +Esta es una interfaz vacía que contiene cualquier tipo de información. |
| 147 | +``` |
| 148 | + type Value interface{} |
| 149 | +``` |
| 150 | +El `Value` debe ser algo con lo que los manejadores puedan operar, o nil, así que debería ser alguno de los siguientes tipos: |
| 151 | +``` |
| 152 | + int64 |
| 153 | + float64 |
| 154 | + bool |
| 155 | + []byte |
| 156 | + string [*] Exceptuando los Rows.Next, que no puede retornar una cadena. |
| 157 | + time.Time |
| 158 | +``` |
| 159 | +## driver.ValueConverter |
| 160 | + |
| 161 | +Define una interfaz para convertir valores nativos en valores de driver.Value. |
| 162 | +``` |
| 163 | + type ValueConverter interface { |
| 164 | + ConvertValue(v interface{}) (Value, error) |
| 165 | + } |
| 166 | +``` |
| 167 | +Esta interfaz es comunmente usada en manejadores de bases de datos y tiene muchas características útiles: |
| 168 | + |
| 169 | +- Convierte un driver.Value al tipo de campo correspondiente, por ejemplo convierte un int64 a un uint16. |
| 170 | +- Conviernte una consulta de base de datos a un driver.Value. |
| 171 | +- Conviernte un driver.Value a un usuario definido en la función `scan`. |
| 172 | + |
| 173 | +## driver.Valuer |
| 174 | + |
| 175 | +Define una interfaz para devolver un driver.Value. |
| 176 | +``` |
| 177 | + type Valuer interface { |
| 178 | + Value() (Value, error) |
| 179 | + } |
| 180 | +``` |
| 181 | +Muchos tipos implementan esta interface para la conversión entre driver.Value y ellos mismos. |
| 182 | + |
| 183 | +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. |
| 184 | + |
| 185 | +## database/sql |
| 186 | + |
| 187 | +`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. |
| 188 | +``` |
| 189 | + type DB struct { |
| 190 | + driver driver.Driver |
| 191 | + dsn string |
| 192 | + mu sync.Mutex // protects freeConn and closed |
| 193 | + freeConn []driver.Conn |
| 194 | + closed bool |
| 195 | + } |
| 196 | +``` |
| 197 | +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. |
| 198 | + |
| 199 | +## Enlaces |
| 200 | + |
| 201 | +- [Índice](preface.md) |
| 202 | +- Sección anterior: [Bases de Datos](05.0.md) |
| 203 | +- Sección siguiente: [MySQL](05.2.md) |
0 commit comments