import z bazy z błędnym kodowaniem

Ostatnio musiałem importować dane z bazy danych z kodowaniem w której zachodnioeuropejskiego w której były przechowywane polskie znaki. Było z tym trochę problemów, więc opiszę rozwiązanie

Zagadnienie Baza A – źródłowa, u klienta. Kodowanie zachodnioeuropejskie WE8DEC. Przechowywane są w niej jednak polskie znaki w kodowaniu EE8ISO8859P2 czyli ISO-8859-2. Takie kodowanie stosuje klient bazy. Oracle pozwala na takie kombinacje. Miałem kiedyś do czynienia z bazą danych w kodowaniu US7ASCII, która też przechowywała polskie znaki, mimo że to kodowanie zawiera tylko na znaki do kodu 127 a więc bez narodowych. Można zapisywać lub odczytywać dane z poziomu klienta tak aby konwersje nie miały miejsca – dzięki temu obce kodowanie nie przeszkadza. Problem pojawił się przy dostępie przez DB linki, ale o tym za chwilę. Baza B – docelowa, u nas. Optymalne kodowanie AL32UTF8 pozwalające przechowywać wszystkie znaki łącznie ze wschodnioazjatyckimi.

Próba rozwiązania Pierwsza próba importu polegała na wykonaniu insert into /tabela z B/ select … from /tabela z A/. Przy takiej operacji Oracle robił konwersję znaków z kodowania zachodnioeuropejskiego na unicode. W efekcie otrzymywaliśmy “krzaczki”, ale były to różne “krzaczki”, tak się przynajmniej na początkowo wydawało. Skoro literka Ą ma w kodowaniu ISO 8859-2 kod 0xA1 a pod tym kodem w ISO 8859-1 widnieje znak ¡, więc importował się np. ZWI¡ZEK. Wydawało się, że wystarczy tylko dopisać funkcję konwertującą odpowiedni “krzaczek” na odpowieni polski znak i będzie po kłopocie. Okazało się jednak, że literki Ś i Ż konwertują się na ten sam symbol � oznaczający nieznany znak. Rozwiązanie Pojawił się pomysł, aby funkcją utl_raw.cast_to_raw skonwertować oryginalny VARCHAR2 z systemu A na tym RAW, potem zaimportować do B i tam funkcją utl_raw.cast_to_varchar2 przekształcić z RAW na VARCHAR2 omijając po drodze konwersję. Okazało się jednak, że podczas odwoływania się z B do A poprzez dblink konwersja następuje przed wywołaniem  utl_raw.cast_to_raw i to mimo wymuszenia, aby funkcja się wykonywała na zdalnym serwerze. Konieczne okazało się utworzenie na A widoków zwracających wynik operacji utl_raw.cast_to_raw na polach z polskimi znakami. Dzięki temu poprzez dblink importowane są z A do B tablice bajtów typu RAW. Potem można w B przekonwertować wpis z RAW do VARCHAR2, ale najpierw trzeba przekształcić odebraną tablicę bajtów aby tekst zapisany w niej w kodowaniu EE8ISO8859P2 został przekształcony na tekst zapisany w kodowaniu bazy B – AL32UTF8. Polecenie wygląda więc następująco utl_raw.cast_to_varchar2(utl_raw.convert(kolumna_z_polskimi_znakami, ‘AMERICAN_AMERICA.AL32UTF8’, ‘AMERICAN_AMERICA.EE8ISO8859P2’)) i rozwiązało problem konwersji.

You May Also Like

Simple trick to DRY your Grails controller

Grails controllers are not very DRY. It's easy to find duplicated code fragments in default generated controller. Take a look at code sample below. It is duplicated four times in show, edit, update and delete actions:

class BookController {
def show() {
def bookInstance = Book.get(params.id)
if (!bookInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'book.label', default: 'Book'), params.id])
redirect(action: "list")
return
}
[bookInstance: bookInstance]
}
}

Why is it duplicated?

There is a reason for that duplication, though. If you move this snippet to a method, it can redirect to "list" action, but it can't prevent controller from further execution. After you call redirect, response status changes to 302, but after method exits, controller still runs subsequent code.

Solution

At TouK we've implemented a simple trick to resolve that situation:

  1. wrap everything with a simple withStoppingOnRender method,
  2. whenever you want to render or redirect AND stop controller execution - throw EndRenderingException.

We call it Big Return - return from a method and return from a controller at once. Here is how it works:

class BookController {
def show(Long id) {
withStoppingOnRender {
Book bookInstance = Book.get(id)
validateInstanceExists(bookInstance)
[bookInstance: bookInstance]
}
}

protected Object withStoppingOnRender(Closure closure) {
try {
return closure.call()
} catch (EndRenderingException e) {}
}

private void validateInstanceExists(Book instance) {
if (!instance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'book.label', default: 'Book'), params.id])
redirect(action: "list")
throw new EndRenderingException()
}
}
}

class EndRenderingException extends RuntimeException {}

Example usage

For simple CRUD controllers, you can use this solution and create some BaseController class for your controllers. We use withStoppingOnRender in every controller so code doesn't look like a spaghetti, we follow DRY principle and code is self-documented. Win-win-win! Here is a more complex example:

class DealerController {
@Transactional
def update() {
withStoppingOnRender {
Dealer dealerInstance = Dealer.get(params.id)
validateInstanceExists(dealerInstance)
validateAccountInExternalService(dealerInstance)
checkIfInstanceWasConcurrentlyModified(dealerInstance, params.version)
dealerInstance.properties = params
saveUpdatedInstance(dealerInstance)
redirectToAfterUpdate(dealerInstance)
}
}
}

Agile Skills Project at my company

Unfulfilled programmers Erich Fromm, a famous humanist, philosopher and psychologist strongly believed that people are basically good. If he was right, then either our society is a mind-breaking dystopia or we have a great misfortune of working i... Unfulfilled programmers Erich Fromm, a famous humanist, philosopher and psychologist strongly believed that people are basically good. If he was right, then either our society is a mind-breaking dystopia or we have a great misfortune of working i...