Convertir un Oracle 11g XE a un juego de caracteres single byte
Esto me ha costado un poco, así que lo documento por aquí, por si alguien necesita hacerlo.
Lo primero, ¿por qué querríamos convertir un Oracle a un juego de caracteres single byte (por ejemplo WE8MSWIN1252)?
Vaya por delante que esto no es algo que esté recomendado hacer para una máquina en producción. Por defecto, el juego de caracteres de Oracle 11g Express Edition es multi byte (UTF8, creo). En este juego de caracteres, cada carácter utiliza entre 1 y 3 bytes y a priori esto no tiene nada de malo. Mi problema es que en ocasiones tengo que trabajar con bases de datos de clientes que tienen un Oracle single byte y al hacer la importación a mi sistema algunos datos superan la capacidad de la columna (en la bbdd del cliente 40 caracteres ocupan 40 bytes mientras que en la mía 40 caracteres pueden ocupar 40+x bytes, superando el tamaño de la columna de la tabla). La versión anterior (10g) se podía descargar en versión single byte, pero en la versión 11g esto ya no es posible.
La solución es convertir Oracle a un juego de caracteres single byte nada más instalarlo. Hay varias formas de hacer esto. A continuación indico cómo hacerlo a las bravas (recuerda, esto es una bbdd de desarrollo)
Antes de nada, si necesitas saber cual es juego de caracteres por defecto de tu base de datos:
select value from NLS_DATABASE_PARAMETERS where parameter=’NLS_CHARACTERSET’;
Nos conectamos como sysdba
y ejecutamos las siguientes sentencias:
sqlplus sys as sydba
SQL> shutdown immediate;
SQL> startup restrict
SQL> ALTER DATABASE CHARACTER SET INTERNAL_USE WE8MSWIN1252 ;
SQL> shutdown immediate;
SQL> startup
Después de esto ya tendremos la bbdd en el juego de caracteres single byte WE8MSWIN1252.
Me encontré con un problema después de esto, y es que al intentar importar con impdp
me daba el siguiente error:
"ORA-39213: Metadata processing is not available"
Para solucionarlo hay que hacer lo siguiente:
SQL> execute dbms_metadata_util.load_stylesheets
Pero (error dentro del error) al intentar hacerlo me daba el siguiente error:
BEGIN dbms_metadata_util.load_stylesheets; END;
*
ERROR at line 1:
ORA-31609: error loading file "kucolumn.xsl" from file system directory
"C:\oraclexe\app\oracle\product\11.2.0\server\rdbms\xml\xsl"
ORA-06512: at "SYS.DBMS_METADATA_UTIL", line 2397
ORA-06512: at line 1
Se queja de que no existe un fichero en el directorio $SERVER/rdbms/xml/xsl
y es que el problema es que la versión XE de Oracle 11g realmente no incluye ese directorio. La solución es copiar ese directorio de otra instalación de Oracle (no XE) de donde lo podamos coger. ¿Y si no tenemos una instalación así a mano? Ese era mi problema, y encontré la solución en este foro. Se trata de descargar el fichero win32_11gR2_database_1of2.zip
del sitio de Oracle (ahora mismo está aquí) y coger el fichero \database\stage\Components\oracle.rdbms\11.2.0.1.0\1\DataFile\filegroup21.jar
de su interior. A su vez abres ese fichero (lo puedes hacer con 7-Zip) y en su interior puedes encontrar ese directorio xsl
.
¿Eso es todo? No, porque, rizando el rizo, aún nos faltará un fichero dentro de ese directorio xsl
. El fichero que falta es kupostdt.xsl
, el cual tienes que crear a mano con el siguiente contenido:
<?xml version="1.0"?>
<!--
NAME
kupostdt.xsl
DESCRIPTION
XSLT stylesheet for XML => DDL conversion of ku$_post_data_table_t ADTs
NOTES
Do NOT modify this file under any circumstance. Copy the file
if you wish to use this stylesheet with an external XML/XSL parser
MODIFIED MM/DD/YY
sdavidso 06/08/09 - new stylesheet - for minimize records_per_bock
-->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Import required scripts -->
<xsl:import href="kucommon.xsl"/>
<!-- Top-level parameters -->
<xsl:param name="PRETTY">1</xsl:param>
<xsl:param name="SQLTERMINATOR">1</xsl:param>
<!-- params for parse -->
<xsl:param name="PRS_DDL">0</xsl:param>
<xsl:param name="PRS_DELIM">\{]`</xsl:param>
<xsl:param name="PRS_VERB">0</xsl:param>
<xsl:param name="PRS_OBJECT_TYPE">0</xsl:param>
<xsl:param name="PRS_SCHEMA">0</xsl:param>
<xsl:param name="PRS_NAME">0</xsl:param>
<xsl:param name="PRS_GRANTEE">0</xsl:param>
<xsl:param name="PRS_GRANTOR">0</xsl:param>
<xsl:param name="PRS_BASE_OBJECT_TYPE">0</xsl:param>
<xsl:param name="PRS_BASE_OBJECT_SCHEMA">0</xsl:param>
<xsl:param name="PRS_BASE_OBJECT_NAME">0</xsl:param>
<xsl:template match="POST_DATA_TABLE_T">
<!-- *******************************************************************
Template: POST_DATA_TABLE_T - template for table attributes
which can only be set after table data is loaded.
******************************************************************** -->
<xsl:call-template name="DoParse">
<xsl:with-param name="Verb">ALTER</xsl:with-param>
<xsl:with-param name="ObjectType">POST_DATA_TABLE</xsl:with-param>
<xsl:with-param name="SchemaNode" select="SCHEMA_OBJ/OWNER_NAME"/>
<xsl:with-param name="NameNode" select="SCHEMA_OBJ/NAME"/>
</xsl:call-template>
<xsl:if test="(SPARE1 mod 65536)>=32768">
<xsl:text> ALTER TABLE </xsl:text>
<xsl:apply-templates select="SCHEMA_OBJ"/>
<xsl:text>MINIMIZE RECORDS_PER_BLOCK</xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Y fin. Con esto ya podremos ejecutar nuestro:
SQL> execute dbms_metadata_util.load_stylesheets
… y después ya nos funcionará el import.
Integrando una aplicación que tengo en CakePHP con una base de datos en Oracle de otra aplicación me encontré con un problema parecido.
Un campo de la base de datos tenía una longitud máxima de 6 caracteres (por ejemplo). Al intentar recuperar un registro que contuviera “camion” no había ningún problema, sin embargo si el registro contenía “camión” el CakePHP daba una advertencia de que el contenido excedía la longitud del campo y nos truncaba el resultado a “cami” o algo similar… Una opción era especificar a Oracle que te devolviera los datos en win1252, pero las “ñ” te las convertía a “n” y no era plan de quedarse sin “bañera”…
Al final lo que pude comprobar es que el problema estaba en que atacaba al Oracle con el driver PDO de PHP y tiene un bug para códigos multibyte. Lo solucioné utilizando el driver OCI de PHP que funciona correctamente con códigos multibyte. En el fork del driver de CakePHP para OraclePDO que tengo hay más información: https://github.com/alphp/cakephp-2.x_oracle-driver.
También he realizado un fork del driver de CakePHP para OracleOCI, porque el original no soportaba campos virtuales, https://github.com/alphp/Oracle-cake2
Bueno es saberlo, Fernando. ¿Y el driver PDO que falla es de CakePHP o general de PHP? ¿Fallaría igual con otra aplicación que use PDO?
El driver que tiene el bug es el PDO del PHP:
https://bugs.php.net/bug.php?id=54379
Y hay que tener cuidado precisamente por lo que mencionas, cualquier aplicación en php que conecte con Oracle vía PDO se encontrará con ese problema si la base de datos está en UTF-8 (como en mi caso) o cualquier otra codificación multibyte.
Lo peor es que hablamos de un bug que sigue abierto desde php-5.3.6 y yo lo he sufrido con php-5.6.x y tiene un par de pull-request para solucionarlo esperando quién sabe que…
Comentarios cerrados para este artículo