Translate

jueves, 19 de mayo de 2011

Tipos de datos en MySQL

Vamos a ver los tipos de datos en MySQL.

MySQL soporta una gran variedad de datos, uno para cada necesidad.


Cadenas de caracteres

Los subtipos de datos existentes aquí son CHAR, VARCHAR, BLOB, TEXT, ENUM y SET.


CHAR y VARCHAR

Son muy similares, y quizás la diferencia más notable sea la forma de almacenamiento: cuando definimos una columna tipo CHAR de tamaño N, e ingresamos un valor (de menos de N caracteres) en esa columna, MySQL rellenará con espacios lo que sobra, mientras que si hacemos lo mismo con una columna de tipo VARCHAR, en este caso no se rellenará con espacios. Cuando obtenemos información a través de una consulta SQL, no obtenemos los espacios sobrantes: MySQL los remueve.

Si se ingresa una cadena de mayor cantidad de caracteres que el tamaño prefijado al definir la columna, la cadena se truncará al llegar al límite.
Por defecto, las comparaciones (en cualquiera de los dos tipos de datos) son insensibles a mayúsculas y minúsculas.

BLOB y TEXT

Se usan para cadenas con un rango que dependerá del tamaño que queramos almacenar.

La diferencia entre ambos es que TEXT permite comparar dentro de su contenido sin distinguir mayúsculas y minúsculas, y BLOB las distingue. Otra diferencia podría ser su uso. TEXT tiende a ser usado para cadenas de texto plano (sin formato), mientras que BLOB se usa para objetos binarios, o sea, cualquier tipo de datos o información, desde un archivo de texto con todo su formato, hasta imágenes, archivos de sonido o video.

BLOB (es un acrónimo de Binary Large OBject, objeto binario de gran tamaño) se subdivide en cuatro tipos que difieren sólo en la capacidad máxima de almacenamiento.

Con TEXT sucede lo mismo, e incluso, hay correspondencia entre la capacidad máxima de almacenamiento de unos y otros.

ENUM

Este tipo de string puede seleccionar su valor únicamente de una lista finita (máximo de 65.535 elementos) de opciones definidas por el usuario, y otras dos por defecto (índice 0 que significa error por ingresos fuera de rango, por ejemplo y está representado por "", e índice NULL con valor NULL).

Por ejemplo:

Definiendo columnas de tipo ENUM("argentina","mexico","paraguay"):

CREATE TABLE user (
nombre enum('juan','pedro') NOT NULL,
pais enum('argentina','paraguay', 'mexico')
);



SET

Similar a ENUM en su funcionamiento, sólo que aquí se puede seleccionar ninguno
o más de un valor de la lista (hasta 64, los muestran separados por comas).

Al ser ENUM semejante a una lista, podemos realizar consultas tales como:

SELECT * FROM nom_tabla WHERE set_col LIKE '%value%';
SELECT * FROM nom_tabla WHERE FIND_IN_SET('value',set_col)>0;
SELECT * FROM nom_tabla WHERE set_col = 'val1, val2';



Numéricos

Se definen los subtipos DECIMAL (o NUMERIC, o DEC), INTEGER (o INT), TINYINT, BIT, BOOL, MEDIUMINT, BIGINT, SMALLINT, FLOAT y DOUBLE (o DOUBLE PRECISION, o REAL).

Todos los tipos numéricos pueden definirse con dos parámetros opcionales: UNSIGNED (impide que los campos numéricos acepten signo negativo, es decir, sólo se aceptarán el cero y los valores positivos) y ZEROFILL (completa con ceros a la izquierda hasta la longitud máxima). La forma de uso es:

TIPO_DATO [UNSIGNED] [ZEROFILL]

Veamos las características de cada tipo de dato:

TINYINT[(M)]

M es el número de dígitos que serán visibles al mostrar el contenido del campo. Si
el campo que se va a mostrar sobrepasa los M dígitos, se mostrarán M dígitos.

  • Si se omite o se sobrepasa la capacidad de TINYINT, se toma la cantidad máxima soportada por este tipo de dato.
  • Si se define con signo va desde -128 a 127.
  • Sin signo (UNSIGNED) va desde 0 hasta 255.
BIT

Es un TINYINT de un dígito (TINYINT(1)).

BOOL

Es un TINYINT de un dígito (TINYINT(1)).


SMALLINT[(M)]

M es el número de dígitos que serán visibles al mostrar el contenido del campo. Si
el campo que se va a mostrar sobrepasa los M dígitos, se mostrarán M dígitos.

  • Si se omite o se sobrepasa la capacidad de SMALLINT, se toma la cantidad máxima soportada por este tipo de dato.
  • Si se define con signo va desde -32768 a 32767.
  • Sin signo (UNSIGNED) va desde 0 hasta 65535.


MEDIUMINT[(M)]

M es el número de dígitos que serán visibles al mostrar el contenido del campo. Si
el campo que se va a mostrar sobrepasa los M dígitos, se mostrarán M dígitos.


  • Si se omite o se sobrepasa la capacidad de MEDIUMINT, se toma la cantidad máxima soportada por este tipo de dato.
  • Si se define con signo va desde -8388608 a 8388607.
  • Sin signo (UNSIGNED) va desde 0 hasta 16777215.

INT[(M)]

M es el número de dígitos que serán visibles al mostrar el contenido del campo. Si
el campo que se va a mostrar sobrepasa los M dígitos, se mostrarán M dígitos.

  • Si se omite o se sobrepasa la capacidad correspondiente a INT, se toma la cantidad máxima soportada por este tipo de dato.
  • Si se define con signo va desde -2147483648 a 2147483647.
  • Sin signo (UNSIGNED) va desde 0 hasta 4294967295.

INTEGER[(M)]

Sinónimo de INT.


BIGINT[(M)]

M es el número de dígitos que serán visibles al mostrar el contenido del campo. Si
el campo que se va a mostrar sobrepasa los M dígitos, se mostrarán M dígitos.

  • Si se omite o se sobrepasa la capacidad correspondiente a BIGINT, se toma la
  • cantidad máxima soportada por este tipo de dato.
  • Si se define con signo va desde -9223372036854775808 a 9223372036854775807.
  • Sin signo (UNSIGNED) va desde 0 hasta 18446744073709551615.

Todas las funciones matemáticas trabajan internamente con valores BIGINT.

FLOAT[(M,D)]

Sirven para definir números con coma, con menos precisión que DOUBLE.
M es el número de dígitos que serán visibles al mostrar el contenido del campo. Si
el campo que se va a mostrar sobrepasa los M dígitos, se mostrarán M dígitos.

El rango de posibles valores va de -3.402823466E+38 a -1.175494351E-38 , 0, y desde 1.175494351E-38 hasta 3.402823466E+38.

DOUBLE[(M,D)]

Sirven para definir números con coma, con más precisión que FLOAT.

M es el número de dígitos que serán visibles al mostrar el contenido del campo. Si
el campo que se va a mostrar sobrepasa los M dígitos, se mostrarán M dígitos.

El rango de posibles valores va:
de -1.7976931348623157E+308 a -2.2250738585072014E-308, y de 2.2250738585072014E-308 a 1.7976931348623157E+308.

DOUBLE PRECISION[(M,D)]

Sinónimo de DOUBLE.

REAL[(M,D)]

Sinónimo de DOUBLE.

DECIMAL[(M[,D])]

Debemos saber que aquí el número es almacenado internamente como una cadena de caracteres (un carácter por cada dígito). Ni el separador decimal (,) ni el
signo menos (-) para números negativos son parte de M.

Si no se le da ningún argumento, por defecto M es igual a 10, tomando un rango de -9999999999 a 99999999999 para números con signo.

Y por defecto, D es igual a 0.

DEC[(M[,D])]

Sinónimo de DECIMAL.

NUMERIC[(M[,D])]

Sinónimo de DECIMAL.

Fecha y hora

Los subtipos de datos existentes son DATETIME, DATE, TIMESTAMP, TIME y YEAR.

DATETIME

DATETIME: Se utiliza cuando se necesita trabajar con fechas y horarios a la vez. El
formato por defecto es ' YYYY-MM-DD HH:MM:SS' pero se le puede dar uno diferente si por algún motivo necesitáramos hacerlo, por ejemplo: 'YYYY/MM/DD HH%MM%SS'.

El rango va desde:

'1000-01-01 00:00:00' hasta '9999-12-31 23:59:59'.

Si al momento de ingresar un DATETIME definimos un valor inválido (por ejemplo,
minuto superior a 60) se almacenará la fecha nula. Por ejemplo: para el formato
'YYYY-MM-DD HH:MM:SS' sería '0000-00-00 00:00:00'.

DATE

DATE se utiliza cuando se necesita trabajar sólo con fechas. El formato por defecto
es 'YYYY-MM-DD' pero se le puede dar un formato diferente si por algún motivo necesitáramos hacerlo, por ejemplo: 'YYYY*MM*DD'. Rango '1000-01-01' a '9999-12-31'.

Si al momento de ingresar un DATE definimos un valor inválido (por ejemplo, mes
superior a 12) se almacenará la fecha nula. Por ejemplo: para el formato 'YYYY-MM-DD' sería '0000-00-00'.

TIMESTAMP

Combinación de fecha y hora. El rango va desde el 01-enero-1970 al año 2037. El formato de almacenamiento depende del tamaño del campo y se visualiza como un número.

Si al momento de crear la tabla se define un TIMESTAMP mayor a 14, se redondea a 14; si se define un número impar, se redondea al par inmediatamente superior.

Si al momento de ingresar un TIMESTAMP definimos un valor inválido (por ejemplo, minuto superior a 60) se almacenará la fecha nula. Por ejemplo:

Para TIMESTAMP(2) sería 00.

Para TIMESTAMP(12) sería 000000000000.

Otro punto importante es que al ingresar una fecha o un horario se toman los datos no ingresados como ceros: esto supone un problema para las fechas, ya que si
tenemos un TIMESTAMP(2) no podemos ingresar sólo el año porque eso supondría
algo como 990000 (día y mes no pueden ser cero, estarían fuera de rango). En cambio, la hora, los minutos y los segundos sí pueden ser cero. Es decir que no podemos insertar cadenas de menos de 6 caracteres.

En PHP hay una gran cantidad de funciones que precisan trabajar con TIMESTAMP.

TIME

Debemos saber que TIME se utiliza cuando se necesita trabajar sólo con horarios. El formato por defecto es 'HH:MM:SS' (aunque también soporta 'HHH:MM:SS'
para períodos largos de tiempo), pero se le puede dar un formato diferente si por
algún motivo necesitáramos hacerlo, por ejemplo: 'HH*MM*SS'.

El rango va de '-838:59:59' a '838:59:59' (es importante que el hecho de poder almacenar TIME negativos nos da la pauta de que existen más usos que el de simplemente guardar el horario de un determinado suceso).

  • Si al momento de ingresar un TIME definimos un valor inválido (por ejemplo, minuto superior a 60) se almacenará la fecha nula. Por ejemplo: para el formato 'HH:MM:DD' sería '00:00:00'.
  • Si ingresamos valores fuera de rango, éstos se terminan reemplazando por el extremo más cercano.

YEAR

Se usa para representar años. Su formato es por defecto YYYY' (puede definirse como 'YY'). El rango va desde 1901 hasta 2155.

  • Si se representa el año con sólo dos dígitos, surge la siguiente particularidad: si se define el campo tipo YEAR como un número, no podemos representar el año 2000 con 00 (sería interpretado como el año 0000); debemos hacerlo con la cadena 00 o con 0.
  • Si ingresamos un valor ilegal, éste será convertido a 0000.

martes, 17 de mayo de 2011

Mi nuevo Linux


Por cuestiones personales he cambiado mi linux antiguo (un debian) por nuevo linux. Y he elegido Linux Mint; por que? Su simpleza, facilidad de uso y como es de uso hogareño. Es excelente para mi.

Si consultamos la wikipedia nos dira:

Linux Mint es una distribución del sistema operativo GNU/Linux, basado en la distribución Ubuntu (que a su vez está basada en Debian). A partir del 7 de septiembre de 2010 también está disponible una edición basada en Debian, es compatible con ésta última y comparte los mismos repositorios.

Linux Mint mantiene un inventario actualizado, un sistema operativo estable para el usuario medio, con un fuerte énfasis en la usabilidad y facilidad de instalación. Es reconocido por ser fácil de usar, especialmente para los usuarios sin experiencia previa en Linux.

Ademas de esto su comunidad es muy activa, encontre una web de habla hispana.

Dejo Links:
http://www.linuxmint-hispano.com/
http://www.linuxmint.com/

domingo, 15 de mayo de 2011

AppFuse



Appfuse es una aplicación web base donde el trabajo de conectar todos los frameworks ya está hecho (por Matt Raible, lo que es garantía de bastante calidad). Es muy útil para ciertos proyectos, sobre todo aquellos que tienen gran cantidad de reglas de negocio asociadas a los usuarios y a sus permisos en la aplicación. Sin embargo, para lograr esto, el “pegamento” usado es demasiado fuerte, y es difícil modificar el comportamiento de lo que se conoce como el “core” de Appfuse.

Para aplicaciones más simples, o para controlar más el proceso de desarrollo, existe Appfuse Light. Siempre en torno a Spring como implementación de inyección de dependencias, es posible elegir entre 60 combinaciones de frameworks MVC y de persistencia.

Appfuse utiliza Maven para toda la automatización, desde la compilación y generación del WAR, hasta la ejecución de las pruebas unitarias o de la propia aplicación en un contenedor Jetty.

Lo que se debe hacer es ir a la siguiente URL y buscar el framework que necesitamos. Esta consulta nos devolvera el archetype de maven para crear nuestro proyecto.

Por ejemplo si quiero un proyecto con tapestry multimodulo el archetype de maven sería:

mvn archetype:generate -B -DarchetypeGroupId=org.appfuse.archetypes -DarchetypeArtifactId=appfuse-basic-tapestry-archetype -DarchetypeVersion=2.1.0 -DgroupId=com.mycompany -DartifactId=myproject -DarchetypeRepository=http://oss.sonatype.org/content/repositories/appfuse

Dejo links:

Ajax con Prototype


Prototype, uno de los frameworks JavaScript que implementa Ajax. Es orientado a objetos y de propósito general, es decir que puede utilizarse incluso en proyectos en los que no intervenga Ajax.

Prototype introduce un objeto llamado Ajax que, como todos los demás objetos y todas las demás funciones busca eliminar las incompatibilidades entre los navegadores. En el caso de Ajax, las diferencias entre el objeto XMLHttpRequest de Mozilla Firefox y el objeto ActiveX de Microsoft Internet Explorer. Internamente, Prototype inicializa un objeto XMLHttpRequest de la siguiente manera:

var Ajax = {
getTransport: function() {
return Try.these(
function() {return new XMLHttpRequest()},
function() {return new ActiveXObject('Msxml2.XMLHTTP')},
function() {return new ActiveXObject('Microsoft.XMLHTTP')}
) || false;
},
activeRequestCount: 0
};

Externamente, deberemos invocar los métodos necesarios para realizarle peticiones al servidor desde el lado cliente:

url = ‘servidor.php’;
var peticion = new Ajax.Request(
url,
{
method: ‘get’,
parameters: ‘a=1&b=2’,
onComplete: funcionReceptora
}
);


El método Request recibe dos argumentos: la URL destino en donde se resolverá la petición (página, servicio web, archivo plano, etcétera) y una serie de datos en notación JSON. Algunos de los posibles son:
  • method: método de comunicación (POST o GET).
  • parameters: parámetros.
  • onComplete: función que se invoca cuando se completa la transacción.
  • asynchronous: comunicación asincrónica (true por defecto) o sincrónica (false).
  • onSuccess: función por invocar cuando se complete satisfactoriamente la transacción.
  • onFailure: función por invocar si surge algún inconveniente durante la transacción.
  • evalScripts: true para evaluar el código JavaScript de la respuesta.

La función que tomará el control cuando se complete la transacción (conocida como funcionReceptora) automáticamente toma la respuesta como argumento:

function funcionReceptora (respuesta) {
$(‘resultado’).innerHTML = respuesta.responseText;
}

La respuesta admite las propiedades del objeto XMLHttpRequest, por ejemplo:

  • responseText (respuesta devuelta por el servidor).
  • responseXML (respuesta devuelta por el servidor, se valida como XML).
  • status (estado de la respuesta).
  • statusText (descripción del estado).

Esto fue un pequeño resumen del uso de Prototype, dejo links:


sábado, 14 de mayo de 2011

Programar se aprende programando


Muchas veces hemos escuchado la frase “Programar se aprende programando” y personalmente creo que nada más lejos de la realidad. El programar es un juego de estrategia, como el ajedrez, es necesario estudiar. Porque cualquier persona puede mover las piezas de ajedrez pero jugar bien con fundamentos y conociendo las diferentes jugadas, muy pocos.

También es cierto que hay gente que nace con la capacidad de resolver problemas más fácilmente o aprender, pero de igual forma esto no justifica dejar de estudiar.

La experiencia es una gran herramienta del programador pero no la única.

Dejo una frase:
El inteligente aprende de sus errores; el sabio de los errores ajenos.

jueves, 12 de mayo de 2011

Frase

La persona inteligente se recupera de un fracaso, la que no lo es nunca se recupera de un éxito.

...Y tampoco del éxito de los demás.

domingo, 8 de mayo de 2011

Concurrencia en Erlang


Ya hemos hablado de este tema anteriormente:


Pero vamos a hacer un repaso, Erlang es un lenguaje de programación concurrente y un sistema de ejecución que incluye una máquina virtual y bibliotecas. Fue diseñado en la compañía Ericsson para realizar aplicaciones distribuidas, tolerantes a fallos, soft-real-time y de funcionamiento ininterrumpido. Proporciona el cambio en caliente de código de forma que éste se puede cambiar sin parar el sistema. Originalmente, Erlang era un lenguaje propietario de Ericsson, pero fue cedido como software de código abierto en 1998. La implementación de Ericsson es, principalmente interpretada pero también incluye un compilador HiPE (sólo soportado en algunas plataformas).

Entre los mayores logros de la plataforma podemos destacar que el chat de facebook y la base documental CouchDB.

Sin dudas una cosa que hace muy especial a Erlang es como maneja la concurrencia. Erlang no maneja la concurrencia con thread como nos tiene acostumbrado C, C++ o Java. Erlang soluciona la programación concurente mediante el modelo de actores. El modelo de actores es un modelo de concurrencia computacional que trata a los "actores" como los primitivos universal de la computación digital en paralelo: en respuesta a un mensaje que recibe, un actor puede tomar decisiones locales, crear más actores, envía más mensajes, y determinar cómo responder al siguiente mensaje recibido.

Parece complicado, pero no lo es. Con un ejemplo vamos a aclarar el tema. Supongamos que queremos hacer un actor saludador, pero no muy simpático; que salude solo a los conocidos; en erlang sería así:

-module(saludador).
-export([loop/0]).
loop() ->
receive
% Saluda a un conocido
"conocido" ->
io:format("Hola!! " ),
loop();
% Un desconocido, no lo saluda
_ ->
io:format(" ... " ),
loop()
end.

En la primera linea declaramos el modulo; luego importamos la función loop, con la cual definimos una función vacía y iteramos para siempre. Con receive recibimos un mensaje, es similar al swich, espera a recibir un mensaje y al recibir un mensaje ejecuta la estructura de codigo que corresponde al mensaje enviado y el “_” es como el default en c, c++ o java.

Primero compilamos:

1> c(saludador).
{ok,saludador}

Con spawn se puede generar un proceso, spawn nos devolverá el PID del proceso, que nos servirá para enviarle mensajes.

2> Pid = spawn(fun saludador:loop/0).
<0.38.0>

Ahora le vamos a mandar un mensaje:

4> Pid ! "conocido".
Hola!! "conocido"

5> Pid ! "Pepe".
... "Pepe"

Como era de esperar solo dice hola a los conocidos. Con el operador ! enviamos mensajes a un actor a partir de su Pid.

Este es un pequeño ejemplo de manejo de concurrencia en erlang. La forma en que maneja la concurrencia Scala fue inspirada en Erlang.

UrlRewrite

Una buena practica en publicar nuestras url en formato restful. Donde los datos de la consulta son parte de la url, esto mayor aceptación de parte del usuario y los buscadores. Por ejemplo mi sitio publica el profile de mis usuarios, si quiesiera publicar el profile de Juan su url sería algo así:

http://misitio.com/profile/juan

Esto es más amigable que esto:

http://misitio.com/profile?name=juan

Creo que en esto estamos todos deacuerdo.

Muchas veces en nuestros proyectos java no podemos hacer esto, dado que el framework web no lo permite. Para estos casos existe UrlRewrite, que permite reescribir nuestras url como queramos; ojo no solo se puede usar para url restful sino para cualquier cambio que quisiéramos hacer a nuestras urls.

Primero bajamos el jar o si usamos maven agregamos esto en el pom.xml

 

org.tuckey
urlrewritefilter
3.1.0



Luego tenemos que agregar el siguiente filtro en el archivo web.xml :




UrlRewriteFilter
org.tuckey.web.filters.urlrewrite.UrlRewriteFilter


UrlRewriteFilter
/*




Ahora debemos escribir un archivo que contenga el cambio de las urls, el archivo se debe llamar urlrewrite.xml:










/profile/([a-z]+)$
/misitio/profile/index.htm?name=$1






En el archivo podemos ver que existe un from (la url que escribe el usuario) y un to(la url real de nuestro sistema) con $1 estamos indicando que el primer parámetro va a la variable de la url destino.

De esta foma armamos nuestras url para que queden más bonitas.

Dejo links:

http://www.tuckey.org/urlrewrite/
http://urlrewritefilter.googlecode.com/svn/trunk/src/doc/manual/3.2/index.html


jueves, 5 de mayo de 2011

Ceylon




Ceylon es el nuevo lenguaje que corre sobre jvm. Es un lenguaje hecho por Jboss/Red Hat ; el principal impulsor es ni más, ni menos Gavin King el creador de Hibernate.

Fue creado para correr sobre JVM, utiliza escritura estática e incluye soporte para funciones de alto orden, manteniendo un fuerte enfoque sobre la facilidad para aprenderlo y para leerlo. La principal premisa que se tuvo en cuenta para su diseño fue: Como sería Java si se escribiera hoy?

Prometo hacer un post decente de este lenguaje, por ahora solo dejo una serie de tutoriales que hizo su creador en su blog:

miércoles, 4 de mayo de 2011

Procedimientos almacenados y funciones en MySQL




Una de las grandes novedades de la versión 5 de MySQL es sin dudas la inclusión de soporte para procesos almacenados. A continuación veremos los fundamentos teóricos y este tema más algunos ejemplos básicos.

Si ya usamos bases de datos como Oracle, Interbase / Firebird, PostgreSQL, seguro escuchamos hablar de procedimientos almacenados. Sin embargo, en MySQL esto es toda una novedad y un paso enorme para que esta base de datos se convierta en un verdadero sistema gestor de bases de datos.

Ahora bien, ¿qué son en realidad los procedimientos almacenados? Luego de sumergirnos en este tema veremos que el nombre es plenamente identificatorio y casi explica lo que es un procedimiento almacenado.

Los procedimientos almacenados son un conjunto de instrucciones SQL más una serie de estructuras de control que nos permiten dotar de cierta lógica al procedimiento. Estos procedimientos están guardados en el servidor y pueden ser accedidos a través de llamadas, como veremos más adelante.

Para crear un procedimiento, MySQL nos ofrece la directiva CREATE PROCEDURE. Al crearlo éste es ligado o relacionado con la base de datos que se está usando, tal como cuando creamos una tabla, por ejemplo.

Para llamar a un procedimiento lo hacemos mediante la instrucción CALL. Desde un procedimiento podemos invocar a su vez a otros procedimientos o funciones.

Un procedimiento almacenado, al igual cualquiera de los procedimientos que podamos programar en nuestras aplicaciones utilizando cualquier lenguaje, tiene:
  • Un nombre.
  • Puede tener una lista de parámetros.
  • Tiene un contenido (sección también llamada definición del procedimiento: aquí
  • se especifica qué es lo que va a hacer y cómo).
  • Ese contenido puede estar compuesto por instrucciones sql, estructuras de control, declaración de variables locales, control de errores, etcétera.

MySQL sigue la sintaxis SQL:2003 para procedimientos almacenados, que también usa IBM DB2.

En resumen, la sintaxis de un procedimiento almacenado es la siguiente:
  CREATE PROCEDURE nombre (parámetro)
[características] definición


Puede haber más de un parámetro (se separan con comas) o puede no haber ninguno (en este caso deben seguir presentes los paréntesis, aunque no haya nada dentro).

Los parámetros tienen la siguiente estructura: modo nombre tipo

Donde:
  • modo: es opcional y puede ser IN (el valor por defecto, son los parámetros que el procedimiento recibirá), OUT (son los parámetros que el procedimiento podrá modificar) INOUT (mezcla de los dos anteriores).
  • nombre: es el nombre del parámetro.
  • tipo: es cualquier tipo de dato de los provistos por MySQL.
  • Dentro de características es posible incluir comentarios o definir si el procedimiento obtendrá los mismos resultados ante entradas iguales, entre otras cosas.
  • definición: es el cuerpo del procedimiento y está compuesto por el procedimiento en sí: aquí se define qué hace, cómo lo hace y bajo qué circunstancias lo hace.

Así como existen los procedimientos, también existen las funciones. Para crear una función, MySQL nos ofrece la directiva CREATE FUNCTION.

La diferencia entre una función y un procedimiento es que la función devuelve valores. Estos valores pueden ser utilizados como argumentos para instrucciones SQL, tal como lo hacemos normalmente con otras funciones como son, por ejemplo, MAX() o COUNT().

Utilizar la cláusula RETURNS es obligatorio al momento de definir una función y sirve para especificar el tipo de dato que será devuelto (sólo el tipo de dato, no el dato).

Su sintaxis es:
  CREATE FUNCTION nombre (parámetro)
RETURNS tipo
[características] definición


Puede haber más de un parámetro (se separan con comas) o puede no haber ninguno (en este caso deben seguir presentes los paréntesis, aunque no haya nada dentro). Los parámetros tienen la siguiente estructura:nombre tipo

Donde:
  • nombre: es el nombre del parámetro.
  • tipo: es cualquier tipo de dato de los provistos por MySQL.
  • Dentro de características es posible incluir comentarios o definir si la función devolverá los mismos resultados ante entradas iguales, entre otras cosas.
  • definición: es el cuerpo del procedimiento y está compuesto por el procedimiento en sí: aquí se define qué hace, cómo lo hace y cuándo lo hace.

Para llamar a una función lo hacemos simplemente invocando su nombre, como se hace en muchos lenguajes de programación.

Desde una función podemos invocar a su vez a otras funciones o procedimientos.

  mysql> delimiter //
mysql> CREATE PROCEDURE procedimiento (IN cod INT)
-> BEGIN
-> SELECT * FROM tabla WHERE cod_t = cod;
-> END
-> //
Query OK, 0 rows affected (0.00 sec)
mysql> delimiter ;
mysql> CALL procedimento(4);


En el código anterior lo primero que hacemos es fijar un delimitador. Al utilizar la línea de comandos de MySQL vimos que el delimitador por defecto es el punto y coma (;): en los procedimientos almacenados podemos definirlo nosotros.

Lo interesante de esto es que podemos escribir el delimitador anterior; sin que el procedimiento termine. Más adelante, en este mismo código volveremos al delimitador clásico. Luego creamos el procedimiento con la sintaxis vista anteriormente y ubicamos el contenido entre las palabras reservadas BEGIN y END.

El procedimiento recibe un parámetro para luego trabajar con él, por eso ese parámetro es de tipo IN. Definimos el parámetro como OUT cuando en él se va aguardar la salida del procedimiento. Si el parámetro hubiera sido de entrada y salida a la vez, sería de tipo denominado INOUT.

El procedimiento termina y es llamado luego mediante la siguiente instrucción:

  mysql> CALL procedimento(4);


Otro ejemplo:

  CREATE PROCEDURE procedimiento2 (IN a INTEGER)
BEGIN
DECLARE variable CHAR(20);
IF a > 10 THEN
SET variable = ‘mayor a 10’;
ELSE
SET variable = ‘menor o igual a 10’;
END IF;
INSERT INTO tabla VALUES (variable);
END


• El procedimiento recibe un parámetro llamado a que es de tipo entero.
• Se declara una variable para uso interno que se llama variable y es de tipo char.
• Se implementa una estructura de control y si a es mayor a 10 se asigna a variable un valor. Si no lo es se le asigna otro.
• Se utiliza el valor final de variable en una instrucción SQL.

Recordemos que para implementar el ultimo ejemplo se deberán usar nuevos delimitadores, como se vio anteriormente.

Observemos ahora un ejemplo de funciones:
  mysql> delimiter //
mysql> CREATE FUNCTION cuadrado (s SMALLINT) RETURNS SMALLINT
-> RETURN s*s;
-> //
Query OK, 0 rows affected (0.00 sec)
mysql> delimiter ;
mysql> SELECT cuadrado(2);


Otras bases de datos como PostgreSQL implementan procedimientos almacenados y brindan la posibilidad de programarlos utilizando lenguajes como PHP o Java.

En MySQL hay intenciones de implementar lo mismo y seguramente en las próximas versiones lo veremos, pero más importante que utilizar un lenguaje u otro es entender para qué podrían servirnos los procedimientos almacenados.

En definitiva hemos dado un recorrido por el mundo de la programación de procedimientos almacenados en MySQL. Es importante que se trata de un mundo que está en pleno desarrollo y que promete evolucionar.

Dejo Link:

sábado, 30 de abril de 2011

Tipos de Tablas en MySQL


Cuando se trabaja con MySQL, existe la opción de variar el tipo de tabla después de creada (salvo con las tablas del sistema llamadas MySQL y test , que por defecto son MyISAM y no se recomienda modificarlas).

Para indicar el tipo de tabla al crearla usamos la siguiente sintaxis:

CREATE TABLE tabla1 (
campo1 INT(4) UNSIGNED,
campo2 VARCHAR(25) NOT NULL
) ENGINE=MYISAM;




Si se omite la opción ENGINE=... por defecto se crea una tabla MyISAM.

Importante: según MySQL, la opción TYPE (similar a ENGINE) es soportada hasta la versión 4.x de MySQL (dejará de usarse a partir de la 5). La opción ENGINE fue añadida en la versión 4.0.18 del lenguaje (para las series 4.x) y en la versión 4.1.2 (correspondiente a las versiones 4.1).

Si se intenta crear un tipo de tabla no disponible en nuestra versión, MySQL optará por crear una tabla tipo MyISAM.

A continuación, haremos una reseña de los tipos de tablas soportados.

ISAM

En un principio, el gestor de bases de datos MySQL empezó utilizando este tipo de tablas y, actualmente, se las considera en desuso. Entre sus desventajas figura el hecho de no poder transportar archivos entre máquinas con distinta arquitectura (tiene un formato diferente para cada arquitectura / sistema operativo, lo cual resulta más rápido, pero presenta el problema de la incompatibilidad) y el de no manejar archivos de tablas superiores a 4 GB.

Los índices se guardan en archivos .ISM y los datos, en archivos .ISD. MySQL recomienda actualizar este tipo de tablas hacia las de tipo MyISAM. Esto puede hacerse con la siguiente instrucción SQL:


ALTER TABLE nombre_de_la_tabla ENGINE = MYISAM;



MyISAM

Es el tipo de tabla por defecto en MySQL desde la versión 3.23 y está basada sobre las tablas ISAM, por supuesto que ofreciendo más opciones que éstas. Cuando se usan estas tablas, los datos se almacenan físicamente en dos archivos: uno que tiene la extensión .MYI (MYISAM INDEX), en donde se almacenan los índices, y otro .MYD (MYISAM DATA), donde se almacenan los datos.

Se encargan de proveer un almacenamiento independiente: esto significa que se pueden copiar tablas de una máquina a otra de distinta plataforma.

Si estamos trabajando con MySQL en forma local, normalmente, se podrá ver en el propio equipo que estos archivos se almacenan en una carpeta que tiene por nombre una base de datos (estas carpetas están en el directorio data dentro del directorio en donde se instaló MySQL, y allí hay una carpeta por cada base de datos). Esto vale también para otros tipos de tablas.

Además:
• Soportan archivos de gran tamaño (63 bits, archivos de tablas superiores a 4 GB)
en comparación con los que soportaban las ISAM.
• Están optimizadas para sistemas operativos de 64 bits.
• Posibilidad de indexar campos BLOB y TEXT.
• Se permiten valores NULL en columnas indexadas.
• Cada tabla guarda un registro que indica si fue cerrada correctamente o no, y al iniciar MySQL existe la opción de indicarle que se verifique ese registro, y se repare la tabla de ser necesario, de forma automática. Esto se logra iniciando MySQL con la opción:

--myisam-recover

Si se encontró un error, trata de repararlo de forma rápida ordenando los registros de la tabla. Si éste persiste, se vuelve a crear el archivo que contiene la tabla. Y si continúa existiendo, se intenta repararlo escribiendo los registros sin un ordenamiento.
• Inserts concurrentes (se pueden insertar varios registros al mismo tiempo).

MERGE

Este tipo de tabla es muy utilizada en los casos en que se precise tratar un número N de tablas MyISAM (de idéntica estructura y pertenecientes a la misma base de datos, de la que también deberá formar parte la tabla MERGE) como si fuera una sola.

Esto podría aplicarse si la tabla MyISAM original es de gran tamaño, y acceder a su contenido llevará una cantidad considerable de tiempo y recursos. Obviamente, estamos hablando de una tabla realmente muy grande.

Otras características:
• Sólo se pueden aplicar instrucciones SELECT, DELETE y UPDATE.
• La definición de la tabla se almacena en un archivo .FRM, y el listado de las tablas MyISAM, en un archivo .MRG (aquí hay en realidad un índice de los archivos .MYI usados. Ver tablas MYISAM).
• Permiten de alguna forma burlar el tamaño máximo de una tabla y el tamaño máximo de un archivo en un sistema operativo específico.
• Debemos saber que si queremos borrar una de las tablas MyISAM que forma parte de la tabla MERGE, no podremos hacerlo bajo el sistema operativo Windows, ya que éste no permite borrar archivos que estén abiertos, y la tabla, al formar parte de la tabla MERGE, se considera abierta. Por la misma razón se producen inconvenientes al aplicar instrucciones tales como DROP TABLE, ALTER TABLE, DELETE FROM sin WHERE, REPAIR TABLE, TRUNCATE TABLE, OPTIMIZE TABLE y ANALYZE TABLE. Una forma de solucionar esto es borrar el contenido de la tabla MERGE (aplicando la instrucción DELETE sin WHERE a una tabla MERGE, no se borra el contenido de las tablas MyISAM sino que se las quita del listado de componentes de la tabla MERGE). Así estas tablas se considerarán no abiertas.

Cuando se crea una tabla de este tipo hay que especificar (con la instrucción UNION) la lista de tablas asociadas. Veamos un ejemplo.

CREATE TABLE t1 (a INT AUTO_INCREMENT PRIMARY KEY, mensaje CHAR(20));
CREATE TABLE t2 (a INT AUTO_INCREMENT PRIMARY KEY, mensaje CHAR(20));

INSERT INTO t1 (mensaje) VALUES ("uno");
INSERT INTO t1 (mensaje) VALUES ("dos");
INSERT INTO t1 (mensaje) VALUES ("tres");
INSERT INTO t2 (mensaje) VALUES ("cuatro");
INSERT INTO t2 (mensaje) VALUES ("cinco");
INSERT INTO t2 (mensaje) VALUES ("seis");

CREATE TABLE total (
a INT AUTO_INCREMENT PRIMARY KEY,
mensaje CHAR(20)
)
ENGINE=MERGE UNION=(t1,t2) INSERT_METHOD=LAST;

SELECT * FROM total;




Devolvería algo como:

1 uno
2 dos
3 tres
1 cuatro
2 cinco
3 seis

El parámetro INSERT_METHOD especifica en qué tabla se realizarán los INSERTS; si en la primera de la lista (poniendo INSERT_METHOD=FIRST) o en la última (poniendo INSERT_METHOD=LAST). En nuestro caso la tabla t1 es la primera y t2 es la última.

HEAP

Este tipo de tablas tienen una particularidad que las hace diferentes del resto: son tablas en memoria, son temporales y desaparecen cuando el servidor se cierra.

Es importante saber que esto las hace realmente rápidas y que, a diferencia de una tabla TEMPORARY, que sólo puede ser accedida por el usuario que la crea, una tabla HEAP puede ser utilizada por diversas personas.

Algunas otras particularidades:
• No soportan columnas de tipo BLOB o TEXT.
• No soportan columnas de tipo AUTO_INCREMENT.
• No se permiten valores NULL en las columnas que han sido indexadas (antes de MySQL en la versión correspondiente a la 4.0.2).
• Siempre conviene especificar el número máximo de filas (MAX_ROWS) cuando se crea la tabla, para no usar toda la memoria disponible.

InnoDB

Debemos saber que estas tablas, al igual que las BerkeleyDB, son TST: este término significa Transactions Safe Tables, o tablas para transacciones seguras. Las tablas tipo TST son menos rápidas y ocupan más memoria, pero a cambio ofrecen mayor seguridad frente a fallos durante la consulta.

Además, las tablas InnoDB tienen las siguientes características:
• Proveen la posibilidad de transacciones seguras. ACID (Atomicidad; Consistencia; Separación, en inglés Isolation y Durabilidad).
• Recuperación ante fallos.
• Soporta FOREIGN KEY (Claves foráneas). Primera vez que se da esto en MySQL.
• Bloqueo a nivel de fila.
• Permite realizar copias de seguridad mientras la base está funcionando.
• Gran eficacia en el procesamiento de grandes volúmenes de información.
• No permite crear claves sobre columnas de tipo BLOB o TEXT.
• Una tabla no puede tener más de 1000 columnas.
• Al borrar todas las filas de una tabla las borra una por una –lo que produce problemas relacionados a la velocidad. Hasta ahora puede truncar tablas.

Fueron agregadas en la versión 4.0 de MySQL.

BerkeleyDB

Estas tablas pueden ser usadas independientemente de MySQL: están desarrolladas por otra empresa (Sleepycat) y el gestor de bases de datos MySQL ofrece una interfaz para trabajar con ellas como una posibilidad más.
• Soportan operaciones COMMIT y ROLLBACK.
• Es de tipo denominado TST (Transactions Safe Tables). Podemos ver tablas INNODB para obtener más información acerca de este tema.
• Normalmente, para instalarlas hay que buscar una versión de MySQL que incluya soporte para este tipo de tablas, y habilitar la opción al momento de la instalación (––with*berkeley–dboption).
• En el archivo en donde se guardan los datos también se guarda la ruta a ese mismo archivo, de modo que no es posible cambiar la base de directorio.

¿En qué casos usar cada una?

Como siempre, la respuesta depende de lo que tengamos que hacer. Las tablas que normalmente se usan hoy en día son las MyISAM, pero pronto (quizás muy pronto) se comenzarán a usar las INNODB, especialmente por la posibilidad de crear relaciones entre tablas (fundamental en el modelo relacional) y ofrecer mayores prestaciones respecto de la seguridad, además de las transacciones.

Las ISAM están prácticamente en desuso (incluso la empresa que desarrolla MySQL admite la posibilidad de que en su versión 5 ya no estén disponibles), y las demás tienen usos muy específicos e incluso compatibles con otros tipos: la clave está en estudiar los problemas que se necesita solucionar, y ver en cada caso qué conviene.

Envío de datos con PHP


En PHP es posible generar script para imitar el envío de datos a través de formularios que utilizan el método POST usando las cabeceras necesarias para que el navegador reconozca la petición. Veamos un ejemplo:




//Lleno los datos a enviar.
$contenido[] = “x=$x”;
$contenido[] = “y=$y”;
$contenido[] = “z=$z”;

$contenido = implode(“&”,$contenido);

$logitud = strlen($contenido);

$peticion .= “POST /pagina.php HTTP/1.1\r\n”;
$peticion .= “Host: xservidor.com\r\n”;
$peticion .= “Content-Type: application/x-www-form-urlencoded\r\n”;
$peticion .= “Content-Length: $logitud\r\n”;
$peticion .= “Connection close\r\n”;
$peticion .= “\r\n”;
$peticion .= $contenido;

if ($manejador = fsockopen(“servidor.com”, 80)) {

fputs($manejador, $peticion);

while (!feof($manejador)) {
$respuesta .= fgets($manejador, 1024);
}

fclose($manejador);
}



Con este script podremos testear nuestros formularios o se podría usar para ingreso de datos masivos. Pero también para ataque a nuestro sitios por lo que deberíamos usar un captcha para prevenir estos ataques.

jueves, 28 de abril de 2011

Autenticación en JSF

Como todos sabemos la seguridad se puede dividir en 3 aspectos:

  • Autenticación: que el usuario sea realmente quien dice ser.
  • Autorización: que el usuario pueda hacer solo lo que el tenga permiso de hacer.
  • Seguridad de Datos: que el usuario vea los datos que el realmente puede ver.

No todas las aplicaciones necesitan todas estos aspectos, por ejemplo una aplicación menor de una intranet no necesariamente necesita la seguridad de datos.

Vamos a atacar la autenticación. Con JSF podemos autenticar a nuestros usuarios de diferentes maneras.

Una forma es por medio del contenedor, todos los contenedores web manejan el concepto de seguridad y la posibilidad de autenticar a un usuario. Muchas aplicaciones solo necesitan la autenticación; por lo tanto lo podemos hacer a nivel contenedor. Este provee tres formas: basic, form-based, y client certificate. Veamos un ejemplo de autenticación basic, agregando las siguientes lineas al web.xml :








BASIC
UserDatabase





La autenticación básica trajo con sigo el concepto de realm. Un realm es un objeto que representa al usuario y su autenticación. Realm no es un concepto estándar y se implementa de forma diferente en los contenedores. Por ejemplo en Apache Tomcat es un simple archivo xml:














Otra posible autenticación es basado en formularios, esta solución fue creada antes de JSF y no es recomendada si la autenticación debe estar integrada a la aplicación:



FORM
UserDatabase

/faces/login.jsp
/faces/loginError.jsp




Otra posibilidad es integrar el login a nuestra aplicación. Vamos a profundizar esta opción que es la más utilizada.

Lo que deberíamos hacer es una aplicación la cual no se pueda acceder a ninguna pagina sin antes logearse, y si alguien intenta acceder a la pagina esta lo redireccionara a la pagina de login. En el login el usuario podrá logearse y así poder realizar sus tareas.

El concepto de servler filter, implementado en la versión 2.3 de servlet y redefinido en 2.4 permite operar sobre una request antes que esta sea procesada. Esto nos permite chequear si el usuario esta logueado cuando quiere acceder a alguna pagina. Entonces declaremos nuestro filtro en el web.xml:



Require that the user log in before accessing any page
other than the entry pages

ForcedLoginFilter
org.assembly.util.ForcedLoginFilter



ForcedLoginFilter
*.jsp
REQUEST
FORWARD



Y la clase ForcedLoginFilter es la siguiente:


package org.assembly.util;

import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

public class ForcedLoginFilter implements Filter {
private static final String LOGIN_JSP = "login.jsp";

public ForcedLoginFilter() {
}

private static boolean checkLoginState(ServletRequest request,
ServletResponse response) throws IOException, ServletException {
boolean isLoggedIn = false;
HttpSession session = ((HttpServletRequest) request).getSession(false);
UserBean managedUserBean = null;
// If there is a UserBean in the session, and it has
// the isLoggedIn property set to true.
if (null != session
&& (null != (managedUserBean = (UserBean) session
.getAttribute("UserBean")))) {
if (managedUserBean.isIsLoggedIn()) {
isLoggedIn = true;
}
}
return isLoggedIn;
}

public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {

boolean isLoggedIn = checkLoginState(request, response);

if (isRedirect((HttpServletRequest) request) && !isLoggedIn) {
String loginURI = LOGIN_JSP;

RequestDispatcher requestDispatcher = request
.getRequestDispatcher(loginURI);

// Force the login
requestDispatcher.forward(request, response);
return;
} else {
try {
chain.doFilter(request, response);
} catch (Throwable t) {
// A production quality implementation will
// deal with this exception.
}
}
}

private boolean isRedirect(HttpServletRequest request) {
String requestURI = request.getRequestURI();

return (!requestURI.contains(LOGIN_JSP));
}

@Override
public void destroy() {
// TODO Auto-generated method stub

}

@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub

}
}




Las clases que son filtros deben implementar la interfaz Filter. En este caso lo que hace el filtro es verificar que existe el usuario en la session y si la url es redirecciónable, dado que si al ir a login redireccionamos se formara un bucle infinito.

El objeto UserBean es el encargado de representar un usuario.


package org.assembly.util;

public class UserBean {

private String userName;

private String userPassword;

public UserBean(String userName, String userPassword) {
this.userName = userName;
this.userPassword = userPassword;
}

public boolean isIsLoggedIn() {
return true;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public String getUserPassword() {
return userPassword;
}

public void setUserPassword(String userPassword) {
this.userPassword = userPassword;
}

}





Entonces ahora debemos crear la pagina login.jsp que sera como la siguientes



<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html"%>

< html>
< head>
< meta http-equiv="Content-Type" content="text/html; charset=UTF-8">


Base de Contribuyentes


< /head>
< body background="black">











action="#{loginBean.validate}" />







< /body>

< /html>




Y luego debemos crear el managed bean que sera como este:


package org.assembly.util.login;

import javax.faces.context.FacesContext;
import org.assembly.util.UserBean;

public class LoginBean {

private String userName;

private String userPassword;

public String validate() {

// Validar si la contraseña y pass son correctas.
// si hay un error return null;

UserBean user = new UserBean(userName, userPassword);

FacesContext.getCurrentInstance().getExternalContext().getSessionMap()
.put("UserBean", user);

return "index";
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public String getUserPassword() {
return userPassword;
}

public void setUserPassword(String userPassword) {
this.userPassword = userPassword;
}

}



Luego debemos declarar en el face-config.xml nuestro bean y la url login.



loginBean
org.assembly.util.login.LoginBean
request

...


login
/login.jsp




Este fue un pequeño ejemplo de login en jsf.

miércoles, 27 de abril de 2011

Pese al avance de los sistemas libres, el 78% de las computadoras usa Windows



Publico esta noticia de clarin, que la verdad no me gusta nada:

A pesar del avance del software libre, el Windows de Microsoft continuó dominando en 2010 el mundo de las computadoras con una cuota del 78,6% en el sector de los sistemas operativos, según el analista de mercado Gartner.

El porcentaje supera al de 2009, cuando Microsoft tenía un 77,9%. En total, el sistema operativo Windows permitió facturar a la compañía US$ 30.400 millones, lo que supone un incremento interanual del 7,8%.

Por su parte, Apple pudo elevar los aportes de sus sistemas operativos a la facturación total de 1,6 a 1,7 por ciento. Actualmente las computadoras Mac están experimentando un "boom", según Gartner, mientras que Microsoft se benefició del cambio del impopular Vista al nuevo Windows 7.

De acuerdo con Gartner, además de PCs y Mac, para su estudio tuvo también en cuenta las grandes computadoras corporativas, los llamados "servidores", donde IBM, HP o Oracle tienen mayor presencia. Entre los operadores de servidores el más popular resultó ser Linux.

Por que no cambiar a Linux?

Dejo link:

http://www.ieco.clarin.com/economia/Pese-sistemas-libres-computadoras-Windows_0_235200025.html

domingo, 24 de abril de 2011

Encriptar password con MySQL


Quien no tuvo que encriptar una password antes de guardarla en la base de datos? Como sabrán es una muy buena práctica guardar las password encriptadas dado que esto permite minimizar los daños a nuestros usuarios en caso que que un intruso consiga la forma de consultar la base y a la vez permite que los administradores del sistema no tengan conocimiento de las mismas, ni forma de saberlas.

Cuando trabajamos con MySQL, podemos usar las funciones md5 o sha1. Para probarlas podemos hacerlo de la siguiente forma:

> select md5('Holass');

> select sha1('Holass');

Recordemos que los algoritmos de encriptación md5 y sha1 son los más usados.

Para insertas un usuario con su password encriptado usando md5 podremos hacer lo siguiente:

> insert into usuario (nombre, clave) values ('nombre', md5('password'));

y si queremos verificar el usuario:

> select * from usuario where nombre = 'nombre' and clave = md5('password');

Además MySQL trae su propia función para encriptar llamade password:

> select password('Holass');

Dejo links: