santz
v1.0.2
Published
Pequeña librería para realizar consultas SQL desde NodeJS a MySQL
Downloads
16
Readme
Santz 1.0.2
Librería NodeJS para realizar consultas a bases de datos MySQL, 100% basada en promesas
- Novedades
- Santz
- Instalación
- Métodos de conexión
- Modo estricto y tablas estáticas
- Modo estricto
- Tablas estáticas
- Métodos de la clase Santz
- select
- where
- from
- insert
- update
- values
- destroy
- hidden
- show
- rowsHidden
- innerJoin
- leftJoin
- rightJoin
- on
- and
- or
- orderBy
- limit
- testConnection
- startTransaction
- exec
- Ejemplos de uso
- Select simple
- Select con where
- Select con strToSql
- Inserción de datos
- Actualización de datos
- Sentencia INNER JOIN
- Sentencia RIGHT/LEFT JOIN
- Omitir columnas
- Ocultas filas
- Volver visibles filas ocultas
- Ver todos los registros ocultos
- Eliminación de datos
- Ordenar valores devueltos
- Limitar el número de filas a mostrar
- Ejecutando código SQL más complejo
- Con Async - Await
- Usar con TypeScript
Novedades
Cuando se tienen que hacer varias consultas, y a las vez una depende del resultado de la otra, no hay mejor opción para garantizar la integridad de los datos que ejecutar una transacción. Se mantiene la filosofía de basado en promesas y nombres de métodos descriptivos.
Ver ejemplo
Para permitir decidir qué hacer de acuerdo a si se ha conectado (o no) a la base de datos, ahora el método retornará una promesa.
Ver ejemplo
En las versiones anteriores el poder seleccionar todas las columnas de una tabla (en una consulta
JOIN
) no era posible. Lo más cercano era seleccionar todas las columnas de todas las tablas relacionadas:const result = await model.select({ all: true }) .from('users') .rightJoin('country', true) // ...
con esto se obtienen todas las columnas de las tablas «users» y «country», ¿pero y si en la tabla «users» solo requiero ciertas columnas?
Desde la versión
0.9.9
es posible:// solo «id, nick» de «users» y todas de «country» const result = await model .select({ users: ['id','nick'], country: '*' }) .from('users', true) .innerJoin('country', true) .on('users.country', 'country.id') .exec(); // si se prefiere, se puede obtener el mismo resultado (todas las columnas - todas las tablas), pero especificando por tabla const result = await model .select({ users: '*', country: '*' }) .from('users', true) .innerJoin('country', true) .on('users.country', 'country.id') .exec();
Obligatorio tener la propiedad «nestTables» en «true», no funciona con el prefijo.
Algo que muchas veces es muy necesario, es el de poder seleccionar todas las columnas de una tabla pero omitiendo alguna/s. Por ejemplo: de la tabla «users» (id, nick, country, state) se requieren todas menos «state»; a partir de ahora es posible:
// select normal const result = await model .select({ // se puede espeficiar las columnas que sean necesarias not: ['state'] }) .from('users', true) .limit(1) .exec(); // tipo JOIN const result = await model .select({ users: { not: ['state'] }, country: '*' }) .from('users', true) .innerJoin('country', true) .on('users.country', 'country.id') .exec();
Lo que se hizo arriba es omitir solo la columna «state» de «users», el resto serán obtenidas. Como se puede apreciar, es posible gracias a la asignación de la propiedad «not» a la tabla (o como propiedad del parámetro del método
select
); esta será un arreglo de string con el nombre de las columnas a omitir.
Una característica muy básica, pero que no venía incluida en versiones anteriores, es la de poder obtener los datos de las diferentes tablas, en consultas tipo
JOIN
, como objetos independientes. Hasta ahora se agregaba el prefijo «_» para separar el nombre de la tabla con el de la columna, situación que podría estar bien para ciertos casos pero no para la mayoría. A continuación puedes ver mejor de qué se trata:// consulta a ejecutar const result = model .select({ users: ['id', 'nick'], country: ['name'] }) .from('users') .innerJoin('country', true) .on('users.country', 'country.id') .where('users.id', '=', 4) .exec();
# resultado en versiones anteriores (prefijo _) [ RowDataPacket { users_id: 4, users_nick: 'Santz', country_name: 'Colombia' } ]
# ahora [ RowDataPacket { users: { id: 4, nick: 'Santz' }, country: { name: 'Colombia' } } ]
A partir de ahora se obtendrán los datos por defecto de esta manera, pero si prefieres separar con el prefijo «_» es solo de especificarlo en el modelo con la propiedad «nestTables»
const model = santzModel({ // ... nestTables: '_' });
Santz
es una pequeña librería, 100% basada en promesas, que facilita la manera de realizar algunas consultas SQL
desde Nodejs
a MySQL
. Específicamente hablando, ejecutará sentencias sin escribir código SQL
, todo mediante métodos JavaScript
, encadenados y con nombres intuitivos, que permitirán comprender fácilmente la acción a ejecutar. Similar a un ORM pero muy simplificado, va genial en proyectos pequeños.
Escapará todos los datos ingresandos en los diferentes métodos, tantos los identificadores como sus valores, evitando así inyecciones SQL.
$ npm install santz
// Requerir métodos de la librería
const { createPool, santzModel } = require('santz');
// Credenciales básicas para establecer conexión a base de datos
const config = {
host: '127.0.0.1',
user: 'root',
password: '',
database: 'nodejs',
};
// Método que creará y retornará un objeto tipo «Pool», el cual será el encargado de ejecutar las queries.
const pool = createPool(config);
// Obtener los métodos disponibles de la librería, listos para ejecutar
const model = santzModel({
// Objecto conexión
pool: pool,
// Especificar el modo estricto. Si se omite por defecto estará activo
strict: true,
// Nombre de la columna que indicará la visibilidad de las filas, omitirse cuando el modo estricto esté inactivo
columnNameState: 'state',
// Indica si se quiere ver mensajes de respuesta en consola, por defecto será verdadero
showQuery: true,
// indica que al ejecutar sentencias de tipo JOIN, los resultados (tabla - columna) vengan como objetos independientes. Una tabla será un objeto con el nombre nombre de la misma y sus propiedades corresponderán a las columnas.
nestTables: true, // valor por defecto, tambíen se puede especificar el prefijo «_»
});
Las mismas opciones de conexión son admitidas cuando se crea un objeto Pool. Para ver las opciones específicas de esta puede ir a: opciones Pool
Con el código anterior se establecerá una conexión. Para saber si todo ha ido bien ejecutar el método testConnection
:
model.testConnection().then(res => {
if (res.connected) {
console.log('¡Conexión exitosa!');
} else {
console.log(res.message);
}
});
createPool(poolConfig)
: Método encargado de obtener un objeto conexión Pool de la libreríaMySQL
. Su parámetropoolConfig
deberá ser un objeto que contendrá las credenciales básicas necesarias para establecer conexión con la base de datos. Retornará el mismo objeto de conexión que deberá ser pasado como parámetro al métodomodelSantz
para ser usado, finalmente, en la ejecución de queries.modelSantz(objectConfig)
: Retornará una instancia de la claseSantz
con todos los métodos disponibles para realizar y ejecutar consultasSQLs
. Recibirá un objeto con ciertas propiedades útiles para configurar la librería.
Literalmente, lo que hará este paquete en cada consulta SQL
, será agregarle un WHERE
implícitamente al query
para buscar o afectar solo aquella información que tenga en su columnNameState
(dicho nombre de columna será pasado en el objecto configuración) el valor de 1
. Por ejemplo, si se hace 'SELECT * FROM ´user´;
', la librería lo convertiría a 'SELECT * FROM ´user´ WHERE ´user´.´state´ = 1;
' y así para todas aquellas consultas que quieran leer, modificar, o insertar información (excepto el método destroy, este eliminará cualquier dato), recordando que esto siempre y cuando el modo estricto esté activo.
Ahora bien, aún con el modo estricto, habrán tablas estáticas
, lo que quiere decir que estas no contarán con la columna columnNameState
, todo un problema si la librería siempre busca información dependiendo del valor de esta columna. Para indicarle al módulo que la tabla a consultar será estática, los métodos: from
, innerJoin
, leftJoin
, rightJoin
, insert
, update
, tendrán un segundo parámetro boleano, staticTable
que por defecto estrá en false
, simplemente se le pasa un true
y listo, se omitirá la búsqueda de la columna columnNameState
en dicha tabla.
Cuando se ponga este modo como inactivo se deberá omitir el paso de la propiedad columnNameState
en el objeto conexión pasado al método santzModel
.
Si se dejase el modo estricto inactivo no sería necesario indicar cuando una tabla será estática, pero sí si lo está es simple de pasar en el método a usar un true
como segundo parámetro.
select
Parámetros:
- columns : array | string | object
- executable?: boolean - false
Crea una consulta de tipo SELECT
. Como parámetro se puede pasar un arreglo de string
, identificando cada uno como el nombre de una columna; esto cuando se quiera traer información de ciertas columnas. Cuando se requieran todas se puede usar '*'
o como un arreglo ['*']
, o si se quiere seleccionar una sola columna se puede especificar como un string 'nick'
; por otro lado, si lo que se necesita es traer todas las columnas menos unas (o una) en específico, se insta a pasar un objeto con la propiedad not
, cuyo valor será un arreglo de string con el nombre de columnas a omitir.
Para consultas más completas, tipo JOIN (INNER, LEFT, RIGHT)
, el parámetro que se requiere es un objeto (sí, o sí), donde cada propiedad o llave del mismo hará referencia al nombre de la tabla y su valor, un arreglo, contendrá los nombres de columnas a consultar. Si se quisiese seleccionar todas las columnas en una consulta de tipo JOIN
, se pasará un objeto con una propiedad especial all
cuyo valor será un boolean
con true
; aunque también es posible espeficarlo en cada columna. Ahora cada propiedad del objeto no será un arrego, sino un string de valor "*"
.
Para omitir columnas en consultas JOIN
es muy silimar a como si fuese un select normal, a direferencia de que ahora el objeto con la propiedad not
no será pasado como parámetro al método select
sino a la tabla en específico.
Cuando se quiera ejecutar funciones como CURRENT_TIMESTAMP()
, por ejemplo, en el select
, se debe invocar el método strToSql
del modelo, colocarle como parámetro el string correspondiente al código SQL y luego pasarselo al método select
. Este último, entonces, deberá recibir un segundo parámetro de tipo boolean con un valor de «true».
Ejemplos:
// Todas las columnas, string select('*') // Todas las columnas, sintaxis de array select(['*']) // Solo ciertas columnas select(['id', 'name', 'age', 'country']) // Cierta columna (cuando es una sola se puede pasar como string) select('nick') // omitiendo ciertas columnas (pasarse cuantas sean necesarias) select({ not: ['state'] }) /* --- de tipo JOIN --- */ select({ user: ['id', 'name'], type: ['name'] }) // De tipo JOIN seleccionando todas las columnas (all es una propiedad especial) select({ all: true }) // de tipo JOIN, seleccionando todas las columnas pero especificando la tabla select({ user: '*', country: ['name'] }) // de tipo JOIN, omitiendo ciertas columnas select({ users: { not: ['state'] } }) /* --- Ejecutando funciones, código SQL --- */ const model = santzModel({...}); // Objeto que permitirá ejecutar el string const currentTime = model.strToSql('CURRENT_TIMESTAMP()'); // pasarle el string a ejecutar // Pasar el objeto al método select (obligatorio el paso del segundo parámetro) select(currentTime, true)
Ejemplos prácticos:
- select simple
- select con where
- select con srtToSql
where
Parámetros:
- columnName: string
- operator: string
- value: string | number
Añade la cláusula WHERE
, permitiendo así filtrar datos. Como primer parámetro recibirá el identificador
o nombre de columna, de segundo el operador
(=, LIKE, >, <, >= ...) por el cual se van a comparar los datos. Por último se tiene el valor
, que va a ser el dato a buscar.
- Ejemplos:
where('id', '=', 7); // Utilizando el operador 'LIKE' (name LIKE %jos%) where('name', 'like', 'jos');
from
Parámetros:
- tableName: string
- staticTable boolean - default: false
Inserta al query la cláusula FROM
.
El parámetro tableName
hará referencia al nombre de la tabla donde se consultarán datos, y staticTable
(tablas estáticas), que por defecto será falso, indica si sus valores serán estáticos o no, es decir, que el usuario no cambiará su información (solo en modo estricto)
- Ejemplos:
// De la tabla 'user' (modo estático desactivado) from('user'); // Activado (sus valores solo son para leerse) from('user', true);
insert
Parámetros:
- tableName: string
- staticTable: boolean - default: false
Crea una sentencia SQL
de inserción de datos.
Su parámetro tabla
, indica el nombre de la tabla donde se insertarán las nuevas filas.
- Ejemplo:
Ejemplo práctico:// Inserta datos en la tabla 'user' insert('user'); // En una tabla estática insert('user', true);
- Inserción de datos
update
Parámetros:
- tableName: string
- staticTable: boolean - default: false
Sentencia SQL
para la modificación de datos.
Su parámetro tabla
, indica la tabla donde se modificarán las filas.
- Ejemplo:
Ejemplo práctico:// Modifica datos en la tabla 'user' update('user'); // Tabla estática update('user', true);
- Actualización de datos
values
Parámetros:
- data: object
Recibirá un objeto donde las propiedad serán nombres de tablas y su valor el dato a insertar/modificar. Utilizarse solo desde los métodos insert o update.
- Ejemplo:
// Inserta/modifica el valor de la columna `name`, `age` y `country` values({ name: 'Chris', age: 22, country: 'Colombia', });
destroy
Parámetros:
- tableName: string
Ejecutará una sentencia DELETE
en el cual, a diferencia del método hidden, eliminará definitivamente los datos especificados.
- Ejemplo:
Ejemplo práctico:// ELimina la fila donde el valor de la columna `id` sea igual a 7 destroy('user').where('id', '=', 7);
- Eliminación de datos
hidden
Parámetros:
- tableName: string
Solo en modo estricto.
Cambiará el estado de visibilidad de la filas seleccionadas. Esto impedirá que al intentar leer o modificar estos datos sea imposible con los métodos select
y update
.
- Ejemplo:
Si se intenta llamar este método, con el modo estricto desactivado, no se ejecutará.// Oculta los datos de la columna `user` donde el `id` sea 5 hidden('user').where('id', '=', 5);
Ejemplo práctico:
- Ocultar filas
show
Parámetros:
- tableName: string
Solo en modo estricto.
Volverá visibles aquellas filas que han sido ocultas por el método hidden.
- Ejemplo:
Si se intenta llamar este método, con el modo estricto desactivado, no se ejecutará.// Vuelve visibles los datos de la columna `user` donde el `id` es 7 show('user').where('user.id', '=', 7);
Ejemplo práctico: Volver visibles filas ocultas
rowsHidden
Parámetros:
- tableName: string
- columns: type - string | default - []
Solo en modo estricto.
Permitirá visualizar todas aquellas filas que han sido ocultas por el método hidden. A diferencia de show, este no cambiará el estado de visibilidad, solo leerá los datos.
- Ejemplo:
Si se intenta llamar este método, con el modo estricto desactivado, no se ejecutará.// Devuelve todas las filas ocultas de la columna `user` rowsHidden('user'); // Obtener solo ciertas columnas rowsHidden('user', ['id', 'nick']);
Ejemplo práctico:
- Ver todos los registros ocultos
innerJoin
Parámetros:
- tableName: string
- staticTable: boolean - default: false
Método encargado de agregar al query la cláusula INNER JOIN
.
- Ejemplo:
Ejemplo práctico:// Añade la tabla `types` innerJoin('types'); // Con una tabla estática innerJoin('types', true);
- Sentencia INNER JOIN
leftJoin
Parámetros:
- tableName: string
- staticTable: boolean - default: false
Método encargado de agregar al query la cláusula LEFT JOIN
.
- Ejemplo:
Ejemplo práctico:// Añade la tabla `types` leftJoin('types'); // Con una tabla estática leftJoin('types', true);
- Sentencia LEFT JOIN
rightJoin
Parámetros:
- tableName: string
- staticTable: boolean - default: false
Método encargado de agregar al query la cláusula RIGHT JOIN
.
- Ejemplo:
Ejemplo práctico:// Añade la tabla `types` rightJoin('types'); // Con una tabla estática rightJoin('types', true);
- Sentencia RIGHT JOIN
on
Parámetros:
- firstIdentifier: string
- secondIdentifier: string
Agrega la cláusula ON
al query. Sus dos parámetros deberán corresponder a los identificadores, o nombre de columnas, a comparar.
- Ejemplo:
// Verifica si la columna `type` de la tabla `user` es igual a `id_type` de la columna `types` on('user.type', 'types.id_type');
and
Parámetros:
- columnName: string
- operator: string
- value: string | number
Añade la cláusula AND
al query.
- Ejemplo:
// Donde el valor de `cash` sea mayor a 1200 and('user.cash', '>', 1200);
or
Parámetros:
- columnName: string
- operator: string
- value: string | number
Añade la cláusula OR
al query.
- Ejemplo:
// Donde el valor de `age` sea mayor o igual a 18 or('user.age', '>=', 18);
orderBy
Parámetros:
- columnName: string
- mode: string - default: 'ASC'
Ordena ascendente o descendentemente todas las filas obtenidas, por los valores de una columna en específico.
- Ejemplo:
Ejemplo práctico:// Por defecto odenará ascendentemente orderBy('user.id'); // Descendentemente orderBy('user.id', 'DESC');
- Ordenar valores devueltos
limit
Parámetros:
- startOrAmount: number
- numRows: number
Agrega la cláusula «LIMIT» a la consulta, usarse solo al final de esta. Recibirá dos parámetros cuando se quiera mostrar filas desde cierta posición hasta la cantidad deseada, o un parámetro cuando solo se quiera limitar la cantidad de registros a mostrar iniciando desde la primera posición por defecto (0).
- Ejemplo:
Ejemplo práctico:// Con un solo parámetro: mostrará las 5 primeras filas. limit(5).exec(); // Con dos parámetros: Mostrará 2 filas, a partir de la 5. limit(5, 2).exec();
- Limitar el número de filas a mostrar
testConnection
Poder comprobar si la conexión a la base de datos ha ido bien.
Será una promesa, pero no capturará errores con catch, siempre resolverá.
Ejemplo:
model.testConnection().then(res => { // res: { connected: boolean, message: string } if (res.connected) { console.log('¡Conexión exitosa!'); } else { console.log(res.message); } });
startTransaction
Permite ejecutar transacciones SQL.
Retornará un objeto con dos propiedades: la conexión y el método para hacer commit. Como se tiene que utilizar una sola conexión, para ejecutar tantas sentencias sean necesarias, es imprescindible el objeto retornado. La función commit
simplemente confirmará los cambios anteriormente ejecutados, será una promesa.
Hay que tener algo muy en cuenta, para poder utilizar cualquier método de la clase Santz
en una transacción, será obligatorio el paso como parámetro del objeto conexión en el método exec
; esto por cada consulta ejecutada. Si llegase a ocurrir un error, en cualquier petición, se hará un rollback
automático para impedir que los datos se rompan.
- Ejemplo:
(async () => { try { // iniciando una transacción const { connection, commit } = await model.startTransaction(); // insertar nuevo usuario const result = await model .insert('users') .values({ nick: 'skynet', country: 5 }) .exec(connection); // paso de la conexión /* se hará rollback automático si ocurre error en cualquier sentencia */ // obtener data del nuevo usuario const user = await model .select({ users: ['id','nick'], country: '*' }) .from('users') .innerJoin('country', true) .on('users.country', 'country.id') .where('users.id', '=', result.insertId) // ID del nuevo usuario .exec(connection); // hacer commit await commit(); console.log(user); } catch (err) { console.error(err); } })();
exec
Método encargado de ejecutar la sentencia SQL
antes preparada. Siempre debe ser invocado de último. Retornará una promesa con los resultados de la ejecución del query.
- Ejemplo:
exec(); // solo se pasará parámetro cuando se ejecute dentro de una transacción exec(connection);
(async () => {
try {
const result = await model
.select('nick')
.from('users')
.exec();
console.log(result);
} catch (err) {
console.log(err);
}
})();
Resultado de ejecución mostrado en consola:
************************************* MODO ESTRICTO: ACTIVADO ************************************
QUERY:
SELECT `nick` FROM `users` WHERE `users`.`state` = 1;
**************************************************************************************************
[
RowDataPacket { nick: 'chris' },
RowDataPacket { nick: 'sky' },
RowDataPacket { nick: 'luc' },
RowDataPacket { nick: 'rex' },
RowDataPacket { nick: 'angel' },
RowDataPacket { nick: 'julia' },
RowDataPacket { nick: 'andrea' },
RowDataPacket { nick: 'Alex' }
]
El identificador o columna ´state´
, llamado en este caso así. Será la columna que indica si ese registro es visible o no. 1
es visible, 0
está oculto.
El nombre de esta columna será asignado en el método santzModel
con el parámetro columnNameState
, y será insertado automáticamente en la query al ejecutar.
(async () => {
try {
const result = await model
.select(['id', 'nick'])
.from('users')
.where('id', '=', 4)
.exec();
console.log(result);
} catch (err) {
console.log(err);
}
})();
Resultado en consola:
************************************* MODO ESTRICTO: ACTIVADO ************************************
QUERY:
SELECT `id`, `nick` FROM `users` WHERE `users`.`state` = 1 AND `id` = 4;
**************************************************************************************************
[ RowDataPacket { id: 4, nick: 'luc' } ]
// Modelo
const model = santzModel({...});
( async () => {
try {
// Objeto que permitirá ejecutar el string
const currentTime = model.strToSql('CURRENT_TIMESTAMP()'); // string del SQL
// Ejecución de la sentencia (segundo parámetro obligatorio cuando se va a ejecutar una función SQL)
const result = await model.select(currentTime, true).exec();
console.log(result);
} catch (err) {
console.log(err);
}
})();
Resultado en consola:
************************************* MODO ESTRICTO: ACTIVADO ************************************
QUERY:
SELECT CURRENT_TIMESTAMP();
**************************************************************************************************
[ RowDataPacket { 'CURRENT_TIMESTAMP()': 2019-02-10T22:29:59.000Z } ]
// Valores a insertar. Cada propiedad del objeto debe corresponder al nombre de la columna.
let data = { nick: 'dLil', pass: 'password' };
const result = model
.insert('user')
.values(data)
.exec();
result.then(res => console.log(res)).catch(err => console.log(err));
Resultado en consola:
************************************* MODO ESTRICTO: ACTIVADO ************************************
QUERY:
INSERT INTO `users` SET `nick` = 'dLil', `pass` = 'password', `state` = 1;
**************************************************************************************************
OkPacket {
fieldCount: 0,
affectedRows: 1,
insertId: 12,
serverStatus: 2,
warningCount: 0,
message: '',
protocol41: true,
changedRows: 0
}
let data = { pass: 'newPassword' };
const result = model
.update('users')
.values(data)
.where('id', '=', 2)
.exec();
result.then(res => console.log(res)).catch(err => console.log(err));
Resultado en consola:
************************************* MODO ESTRICTO: ACTIVADO ************************************
QUERY:
UPDATE `users` SET `pass` = 'newPassword' WHERE `id` = 12 AND `users`.`state` = 1;
**************************************************************************************************
OkPacket {
fieldCount: 0,
affectedRows: 1,
insertId: 0,
serverStatus: 2,
warningCount: 0,
message: '(Rows matched: 1 Changed: 1 Warnings: 0',
protocol41: true,
changedRows: 1
}
Cuando la consulta a realizar es de tipo JOIN
, al método select se le debe pasar un objeto en el cual sus llaves corresponderán al nombre de la tabla y su valor, un arreglo, contendrá los nombres de columnas a mostrar.
const result = model
.select({
// De la tabla `user` las columnas `id` y `name`
users: ['id', 'nick'],
// De la tabla `country` la columna `name`
country: ['name'],
})
.from('users')
// Se le agrega 'true' porque 'country' es una tabla estática
.innerJoin('country', true)
// Donde las columnas `users.country` y `country.id` sean iguales
.on('users.country', 'country.id')
.exec();
result.then(res => console.log(res), err => console.log(err));
Resultado en consola:
************************************* MODO ESTRICTO: ACTIVADO ************************************
QUERY:
SELECT `users`.`id`, `users`.`nick`, `country`.`name` FROM `users` INNER JOIN `country` ON `users`.`country` = `country`.`id` WHERE `users`.`state` = 1;
**************************************************************************************************
[
RowDataPacket {
users:{ id: 3, nick: 'Chris' }, country: { name: 'Venezuela'}
},
RowDataPacket {
users: { id: 4, nick: 'Santz' }, country: { name: 'Colombia' }
},
RowDataPacket {
users: { id: 5, nick: 'Sky' }, country: { name: 'none' }
}
]
En la consulta anterior no se le añadió el método where, esto pues, como ya se ha dicho, la librería por defecto estará buscando solo aquellos registros cuyo valor de la columna state
(en este caso) sea 1
, es decir, se puede ver.
Con el método where se vería así:
const result = model
.select({ users: ['id', 'nick'], country: ['name'] })
.from('users')
.innerJoin('country', true)
.on('users.country', 'country.id')
// Importante pasar el nombre de la tabla primero, para evitar ambigüedad
.where('users.id', '=', 4)
.exec();
result.then(res => console.log(res)).catch(err => console.log(err));
************************************* MODO ESTRICTO: ACTIVADO ************************************
QUERY:
SELECT `users`.`id`, `users`.`nick`, `country`.`name` FROM `users` INNER JOIN `country` ON `users`.`country` = `country`.`id` WHERE `users`.`id` = 4 AND `users`.`state` = 1;
**************************************************************************************************
[
RowDataPacket {
users: { id: 4, nick: 'Santz' },
country: { name: 'Colombia' }
}
]
A continuación se hará un ejemplo con rightJoin()
, pero la sintaxis para el método leftJoin()
es el mismo.
Como se sabe, con el modo estricto
solo se estarán seleccionando aquellas filas que estén configuradas como visibles para la librería. Por lo tanto, para este caso en específico, si quiero ver realmente todas las filas que estén a en la tabla dos, sin la necesidad que estén en la uno, hay que declarar a la tabla users
(tabla uno) como estática (pasando «true» como segundo parámetro) para evitar así el «WHERE» implícito que se hace en una tabla dinámica.
const result = model
.select({ users: ['id', 'nick'], country: ['name'] })
// Definiéndola como estática
.from('users', true)
.rightJoin('country', true)
.on('users.country', 'country.id')
.exec();
Resultado en consola:
************************************* MODO ESTRICTO: ACTIVADO ************************************
QUERY:
SELECT `users`.`id`, `users`.`nick`, `country`.`name` FROM `users` RIGHT JOIN `country` ON `users`.`country` = `country`.`id`;
**************************************************************************************************
[
RowDataPacket {
users: { id: 5, nick: 'sky' },
country: { name: 'none' }
},
RowDataPacket {
users: { id: 3, nick: 'Chris' },
country: { name: 'Venezuela' }
},
RowDataPacket {
users: { id: 4, nick: 'Santz' },
country: { name: 'Colombia' }
},
# Fila que no se encontraba relacionada con la tabla uno (users)
RowDataPacket {
users: { id: null, nick: null },
country: { name: 'Chile' }
}
]
De la columna «users» (id, nick, country, state) omitir las columnas «state, country». A continuación se estará utilizando la sentencia INNER JOIN
, pero bien que se pueden usar LEFT-RIGH JOIN.
// select normal
const result = await model
.select({
not: ['state','country']
})
.from('users', true)
.limit(3)
.exec();
// JOIN
const result = await model
.select({
users: { not: ['state','country'] },
country: '*'
})
.from('users', true)
.innerJoin('country', true)
.on('users.country', 'country.id')
.limit(3)
.exec();
Resultado en consola:
# resultado select normal
[
RowDataPacket { id: 3, nick: 'Chris' },
RowDataPacket { id: 4, nick: 'Santz' },
RowDataPacket { id: 5, nick: 'sky' }
]
# resultado sentencia JOIN
[ RowDataPacket {
users: { id: 3, nick: 'Chris' },
country: { id: 3, name: 'Venezuela' }
},
RowDataPacket {
users: { id: 4, nick: 'Santz' },
country: { id: 4, name: 'Colombia' }
},
RowDataPacket {
users: { id: 5, nick: 'sky' },
country: { id: 1, name: 'none' }
}
]
Cambia la visibilidad de la fila a oculto.
Si se quisiese eliminar datos completamente, puede mirar el método de eliminación de datos
const result = model
.hidden('user')
.where('id', '=', 5)
.exec();
result.then(res => console.log(res)).catch(err => console.log(err));
Resultado en consola:
************************************* MODO ESTRICTO: ACTIVADO ************************************
QUERY:
UPDATE `user` SET `user`.`state` = 0 WHERE `id` = 5;
**************************************************************************************************
OkPacket {
fieldCount: 0,
affectedRows: 1,
insertId: 0,
serverStatus: 2,
warningCount: 0,
message: '(Rows matched: 1 Changed: 1 Warnings: 0',
protocol41: true,
changedRows: 1
}
const result = model
.show('user')
.where('id', '=', 5)
.exec();
result.then(res => console.log(res)).catch(err => console.log(err));
Resultado en consola:
************************************* MODO ESTRICTO: ACTIVADO ************************************
QUERY:
UPDATE `user` SET `user`.`state` = 1 WHERE `id` = 5;
**************************************************************************************************
OkPacket {
fieldCount: 0,
affectedRows: 1,
insertId: 0,
serverStatus: 2,
warningCount: 0,
message: '(Rows matched: 1 Changed: 1 Warnings: 0',
protocol41: true,
changedRows: 1
}
(async () => {
try {
// Todas las columnas
const result = model.rowsHidden('user').exec();
// Obteniendo solo ciertas columnas
const result1 = model.rowsHidden('user', ['id', 'nick']).exec();
const all = await Promise.all([result, result1]);
console.log(all);
} catch (err) {
console.log(err);
}
})();
Resultado en consola:
************************************* MODO ESTRICTO: ACTIVADO ************************************
QUERY:
SELECT * FROM `user` WHERE `user`.`state` = 0;
QUERY:
SELECT `id`, `nick` FROM `user` WHERE `user`.`state` = 0;
**************************************************************************************************
[
# Resultado sentencia uno
[RowDataPacket { id: 3, name: 'Tefy', type: 1, state: 0 }],
# Resultado sentencia dos
[RowDataPacket { id: 3, name: 'Tefy'}]
]
Estas filas solo pueden ser mostradas mediante este método, rowsHidden().
const result = model
.destroy('user')
.where('id', '=', 6)
.exec();
result.then(res => console.log(res)).catch(err => console.log(err));
Resultado en consola
************************************* MODO ESTRICTO: ACTIVADO ************************************
QUERY:
DELETE FROM `user` WHERE `id` = 5;
**************************************************************************************************
OkPacket {
fieldCount: 0,
affectedRows: 1,
insertId: 0,
serverStatus: 2,
warningCount: 0,
message: '',
protocol41: true,
changedRows: 0
}
// Si se quisiese ordenar descendentemente, pasarle al método `orderBy` como segundo parámetro 'DESC'
const result = model
.select('*')
.from('user')
.orderBy('id')
.exec();
result.then(res => console.log(res)).catch(err => console.log(err));
Resultado en consola
************************************* MODO ESTRICTO: ACTIVADO ************************************
QUERY:
SELECT * FROM `user` WHERE `user`.`state` = 1 ORDER BY `id` ASC;
**************************************************************************************************
[
RowDataPacket { id: 1, name: 'Natalia', type: 1, state: 1 },
RowDataPacket { id: 2, name: 'Esteban Chávez', type: 1, state: 1 },
RowDataPacket { id: 4, name: 'María', type: 1, state: 1 },
RowDataPacket { id: 7, name: 'Gabriel', type: 1, state: 1 },
RowDataPacket { id: 8, name: 'José', type: 2, state: 1 },
RowDataPacket { id: 9, name: 'Sandra', type: 1, state: 1 },
RowDataPacket { id: 10, name: 'Liliana', type: 2, state: 1 }
]
Recordatorio:
Como en las filas de una tabla en MySql la primera posición siempre será «0», cuando le indicamos al método limit
en qué fila empezar, hay que tener en cuenta que si le colocamos «1» esta nos mostrará el registro «2», y así con las demás posiciones.
// Mostrando los primeros 5 registros
const result = model
.select('id', 'nick')
.from('users')
.limit(5)
.exec();
// Mostrando 5 filas a partir de la posición 2
const result1 = model
.select('id', 'nick')
.from('users')
.limit(2, 5)
.exec();
// Ejecutar las dos consultas
Promise.all([result, result1])
.then(data => console.log(data))
.catch(err => console.log(err));
Resultado en consola:
[
# Sentencia 1: mostrando los primeros 5 registros
[
RowDataPacket { id: 2, nick: 'santz' },
RowDataPacket { id: 3, nick: 'may' },
RowDataPacket { id: 4, nick: 'sky' },
RowDataPacket { id: 5, nick: 'chris' },
RowDataPacket { id: 6, nick: 'angel' }
],
# Sentencia 2: mostrando 5 registros a partir de la posición 2
[
RowDataPacket { id: 4, nick: 'sky' },
RowDataPacket { id: 5, nick: 'chris' },
RowDataPacket { id: 6, nick: 'angel' },
RowDataPacket { id: 7, nick: 'charly' },
RowDataPacket { id: 8, nick: 'jose' }
]
]
¿Qué pasaría si quisiésemos actualizar una columna con valores numéricos incrementando su valor actual en uno, dos, etcétera?
Por ejemplo, en el siguiente caso tenemos una columna «pj» cuyo valor requiere ser incrementado en uno, su valor actual es «10».
Se podría ejecutar un SELECT consultando su contenido, almacenarlo en una variable, sumarle uno y luego ejecutar un UPDATE. Pero esto no sería para nada recomendado si pensamos en el rendimiento de nuestro servidor. Si eres bueno en SQL, o por lo menos conoces conceptos básicos, sabrás que este incremento se puede ejecutar en la misma sentencia UPDATE sin necesidad de obtener previamente el valor de la columna. Sería algo así:
UPDATE users SET pj = pj + 1 WHERE id = 4;
Si no conocías este «truco» es hora de ponerlo en práctica.
Hasta ahí bien, ahora intentemos ejecutar esa misma sentencia SQL desde nuestra librería:
const result = model
.update('users')
.values({ pj: 'pj + 1' })
.where('id', '=', 4)
.exec();
result.then(data => console.log(data));
Obtendremos lo siguiente:
************************************* MODO ESTRICTO: ACTIVADO ************************************
QUERY:
UPDATE `users` SET `pj` = 'pj + 1' WHERE `id` = 4 AND `users`.`state` = 1;
**************************************************************************************************
OkPacket {
fieldCount: 0,
affectedRows: 1,
insertId: 0,
serverStatus: 2,
warningCount: 1,
message: '(Rows matched: 1 Changed: 1 Warnings: 1',
protocol41: true,
changedRows: 1
}
A primera parece que se ejecutó correctamente, si vemos la propiedad «changedRows» (indica el número de filas cuyo valor haya cambiado) del objeto devuelto tiene como valor «1», y efectivamente, revisa tu base de datos y te fijarás en sí, cambió, pero no de la manera esperada. Recordemos que su contenido anterior era «10», debería ser ahora «11» pero no, es «0» (en caso de que solo acepte números).
Si revisamos el código SQL de nuestra ejecución en consola, a la columna «pj» se le está asignando como valor un string: «pj + 1», y es que para ejecutar código SQL en el valor de una propiedad del método values
se debe recurrir a la función strToSql
, contenida en el modelo, que deberá ser pasada a la propiedad correspondiente como su valor:
(async () => {
try {
// Se le indica, en el string, que incremente el valor de la columna «pj» en uno
const increment = model.strToSql('pj + 1');
const result = await model
.update('users')
.values({ pj: increment })
.where('id', '=', 4)
.exec();
console.log(result);
} catch (err) {
console.log(err);
}
})();
Ejecución:
************************************* MODO ESTRICTO: ACTIVADO ************************************
QUERY:
UPDATE `users` SET `pj` = `pj` + 1 WHERE `id` = 4 AND `users`.`state` = 1;
**************************************************************************************************
OkPacket {
fieldCount: 0,
affectedRows: 1,
insertId: 0,
serverStatus: 2,
warningCount: 0,
message: '(Rows matched: 1 Changed: 1 Warnings: 0',
protocol41: true,
changedRows: 1
}
Ahora es diferente, el valor que se le asigna a la columna no es un string como anteriormente lo era. De igual manera podemos verificar la propiedad «changedRows» y ratifica la modificación. La información en base de datos, esta vez, ha sido exitosa.
Como obtenemos los datos mediante promesas, por defecto se puede utilizar esta nueva manera para resolverlas (como ya se ha hecho en varios ejemplos anteriormente).
Para poder utilizar esta nueva metodología, hay que anteponerle el prefijo «async» a la función donde estemos trabajando:
async function test() {
// code
}
// Función flecha
const test = async () => {
// code
};
Casi siempre será así, pero hay otros casos donde no, no estaremos en el ámbito de una función y no es factible su uso. En estos caso se puede utilizar funciones autoimbocables, funciones anónimas que se ejecutarán al mismo tiempo de ser declaradas:
(async function() {})(); // Paréntesis ejecutan la función, pueden recibir parámetros para su uso interno.
(async () => {})();
Y una de las ventanjas de esta forma es el manejo de errores. Será de manera más clara y con el convencional bloque «try catch»:
( async () => {
try {
const result = await model.select(['nick']).from('users').limit(4).exec();
console.log(result));
} catch(err) {
console.log(err);
}
})();
Otra de las palabras claves es «await», esta indica que esperará a que la promesa sea resuelta y luego así asignar su valor, dado el caso, en una variable.
Para finalizar, hay que entender que en toda función que se use con «async - await» automáticamente estará retornando una promesa, concepto clave cuando pretendemos devolver valores y asignarlos a variables de manera tradicional.
A partir de la versión 0.9.4
es posible el uso en TypeScript.
import { createPool, santzModel, PoolConfig, QueryResult } from 'santz';
const poolConfig: PoolConfig = {
host: '127.0.0.1',
user: 'root',
password: '',
database: 'santz',
};
const pool = createPool(poolConfig);
const model = santzModel({
pool,
strict: true,
columnNameState: 'state',
showQuery: true,
nestTables: true
});
Aprovechando el tipado de TypeScript, en los métodos que establecen datos (insert, update, destroy, hidden...) es posible utilizar un tipo de valor de retorno en la promesa obtenida:
// Interfaz a utilizar
import { QueryResult } from 'santz';
(async () => {
let newValues = { pass: 'newPassword' };
const result = <QueryResult>await model
.update('users')
.values(newValues)
.where('id', '=', 12)
.exec();
})();
Ahora escribiendo result
, se autocompletarán todas las propiedades disponibles.
// Propiedades de la interfaz QueryResult
interface QueryResult {
fieldCount: number;
affectedRows: number;
insertId: number;
serverStatus: number;
warningCount: number;
message: string;
protocol41: boolean;
changedRows: number;
}
Chris Santiz, 2019