Записки жертвы высоких технологий

    Здравствуйте! Мой блог посвящен в большей своей части моей профессиональной деятельности на поприще использования технологий ORACLE для разработки баз данных OLTP и OLAP хранилищ данных. В заметках я периодически размещаю разнообразные SQL, PL/SQL и Java скрипты написанные мной и не только мной, ссылки на интересные источники в сети либо другую полезную информацию, которая каким либо образом касается моей работы с замечательными продуктами компании ORACLE.
    Вы можете связаться со мной по контактному емейлу, если у вас есть какие-либо вопросы, связанные с разработкой баз данных на основе продуктов ORACLE, буду рад помочь вам, если это будет в моих силах.

2 окт. 2009 г.

Простой Java CSV парсер для ORACLE

В проекте для ХД МосОблГаз'а пришлось делать парсер для загрузки CSV файлов, размещенных в определенной папке на диске сервера (10.2.0.4, Win32). Папка заполняется извне инструментами Cognos'а в виде набора *.csv файлов. Данные из файлов парсятся и грузятся в определенную буферную таблицу, предварительно очищаемую. После загрузки файлы удаляются. Папка определена в виде объекта DIRECTORY, поэтому считывается из DBA_DIRECTORIES. В качестве разделителя используется ";", в первой строке каждого файла находятся названия столбцов. Они тоже парсятся, чтобы потом использовать их порядок при INSERT'е в целевую таблицу

Сначала сделал на PL/SQL'е, потом решил переделать под встроенную Java. В приведенном коде много несуразностей, но он был написан за час-полтора работы, поэтому прошу строго не судить. Вот что получилось:

create or replace and compile java source named pump_csv as
import java.sql.*;
import java.text.*;
import java.lang.*;
import java.util.*;
import java.io.*;
import oracle.jdbc.*; // Подгружаем драйвер ORACLE
import c_pump_tools.*;
public class c_pump_csv
{
  static boolean DEBUG = true; // Включает режим пассивной отладки (вывод диагностических сообщений)
  static String CSV_DIR_NAME = "CSV_DIR";
  static String ORA_URL = "jdbc:default:connection"; // URL соединения с MSSQL
  public static void run() throws Exception
  {
    // Грузим драйвер ORACLE
    DriverManager.registerDriver(new oracle.jdbc.OracleDriver());
    OracleConnection conORA = null;
    try
    {
      // Соединяемся с базой данных ORACLE по внутреннему соединению
      try
      {
        conORA = (OracleConnection)DriverManager.getConnection(ORA_URL);
      }
      catch(Exception e)
      {
        e.printStackTrace();
        throw new Exception("Невозможно соединиться с сервером-приемником данных по URL=" + ORA_URL);
      }
      conORA.setAutoCommit(false);// Устанавливаем отказ от autocommit
      log("Connected ORACLE on " + ORA_URL);
      String csvDirectoryName = null;
      OraclePreparedStatement stmtORA_InsertData = null;
      OracleStatement stmtORA_CheckStageClearance = null;
      try
      {
        // Получаем имя директории с CSV файлами
        try
        {
          OracleStatement stmtORA_GetDirectory = null;
          OracleResultSet ors=null;
          try
          {
            stmtORA_GetDirectory = (OracleStatement)conORA.createStatement();
            ors = (OracleResultSet)stmtORA_GetDirectory.executeQuery("select directory_path from dba_directories where directory_name = '"+CSV_DIR_NAME+"'");
            if(ors.next()) csvDirectoryName = ors.getString(1);
            if (csvDirectoryName == null)
            {
              log("Unable to resolve directory name " + CSV_DIR_NAME + ". Pumping cannot be initiated");
              throw new Exception ("Невозможно разрешить имя директории '" + CSV_DIR_NAME + "'");
            }
            log("Path to \"" + csvDirectoryName + "\" has been found to pump from");
          }
          finally
          {
            if (ors instanceof OracleResultSet)ors.close();
            if (stmtORA_GetDirectory instanceof OracleStatement) stmtORA_GetDirectory.close();
            log("Resources for resolving the CSV directory name has been released");
          }
        }
        catch(Exception e)
        {
          e.printStackTrace();
          throw new Exception("Невозможно получить путь к папке по имени директории " + CSV_DIR_NAME);
        }
        // Проверяем готовность целевой таблицы к приему новых данных
        try
        {
          stmtORA_CheckStageClearance = (OracleStatement)conORA.createStatement();
          OracleResultSet rstCheck = null;
          try
          {
            rstCheck = (OracleResultSet)stmtORA_CheckStageClearance.executeQuery("select count(*) cnt from target_table");
            int iCountRows = 0;
            if (rstCheck.next()) iCountRows = rstCheck.getInt(1);
            if (iCountRows>0)
            {
              log("WARNING! Source table TARGET_TABLE is not empty. Data cannot be loaded into non-empty table. Terminating data import");
              return;
            }
            log("Source table TARGET_TABLE is empty and ready to receive data");
          }
          finally
          {
            if (rstCheck != null) rstCheck.close();
          }
        }
        catch(Exception e)
        {
          e.printStackTrace();
          throw new Exception("Невозможно выполнить команду для проверки готовности целевой таблицы TARGET_TABLE сервера-приемника");
        }
        // Просматриваем содержимое папки на предмет наличия в ней CSV файлов
        File sourceFolder = new File(csvDirectoryName);
        FileFilter sourceFolderFilter = new FileFilter()
        {
          public boolean accept(File file)
          {
            return !file.isDirectory()&& file.getName().endsWith(".csv");
          }
        };
        File[]csvFiles = sourceFolder.listFiles(sourceFolderFilter);
        if (csvFiles == null || csvFiles.length == 0)
        {
          log("Source CSV files not found in directory " + CSV_DIR_NAME + ". Exiting process");
          return;
        }
        else
        {
          for (int iterFile=0; iterFile
          {
            // Открываем и построчно читаем текстовый файл
            File csvFile = csvFiles[iterFile];
            log("File " + csvFile.getCanonicalPath() + " has been found in source SSV folder");
            BufferedReader input = new BufferedReader(new FileReader(csvFile));
            try
            {
              // Инициализируем переменные для разбора строк
              StringBuffer rowColumnNames = new StringBuffer();
              StringBuffer rowColumnBinds = new StringBuffer();
              int columnCount = 0;
              String line = null;
              long rowIndex = 0;
              try
              {
                while (( line = input.readLine()) != null)
                {
                  rowIndex++;
                  if(rowIndex == 1)
                  {
                    // В первой строке находятся столбцы, для вставки данных
                    StringTokenizer st = new StringTokenizer(line, ";");
                    while(st.hasMoreTokens())
                    {
                      String fieldName = st.nextToken().trim().toUpperCase();
                      if (rowColumnNames.length() > 0) rowColumnNames.append(",");
                      rowColumnNames.append(fieldName);
                      if (rowColumnBinds.length() > 0) rowColumnBinds.append(",");
                      rowColumnBinds.append("?");
                      columnCount++;
                    }
                    if (columnCount==0) throw new Exception("В первой строке CSV файла не обнаружены названия столбцов. Импорт данных невозможен.");
                    log("Found " + new Integer(columnCount).toString() +" columns: " + rowColumnNames.toString() );
                    // Готовим команду для вставки данных
                    try
                    {
                      stmtORA_InsertData = (OraclePreparedStatement)conORA.prepareStatement("insert into target_table("+rowColumnNames.toString()+") values("+rowColumnBinds.toString()+")");
                    }
                    catch(Exception e)
                    {
                      e.printStackTrace();
                      throw new Exception("Невозможно подготовить команду для вставки данных из CSV источника");
                    }
                  }
                  else
                  {
                    // Разбор данных
                    StringTokenizer st = new StringTokenizer(line, ";");
                    ArrayList columnValues = new ArrayList(columnCount);
                    while(st.hasMoreTokens())
                    {
                      columnValues.add(st.nextToken());
                    }
                    if (columnValues.size() != columnCount) throw new Exception("Количество значений (" + columnValues.size()+ ") не соответствует количеству столбцов ("+new Integer(columnCount).toString()+")");
                    // Вставка данных в БД                   
                     if (columnCount>=1)
                    {
                      try{
                        stmtORA_InsertData.setString(1, (String)columnValues.get(0));
                      }catch(Exception e) {log("Error parsing data cell #1");throw e;}
                    }
                    if (columnCount>=2)
                    {
                      try{
                        stmtORA_InsertData.setString(2, (String)columnValues.get(1));
                      }catch(Exception e) {log("Error parsing data cell #2");throw e;}
                    }
                    if (columnCount>=3)
                    {
                      try{
                        stmtORA_InsertData.setString(3, (String)columnValues.get(2));
                      }catch(Exception e) {log("Error parsing data cell #3");throw e;}
                    }
                    if (columnCount>=4)
                    {
                      try{
                        stmtORA_InsertData.setString(4, (String)columnValues.get(3));
                      }catch(Exception e) {log("Error parsing data cell #4");throw e;}
                    }
                    if (columnCount>=5)
                    {
                      try{
                        stmtORA_InsertData.setString(5, (String)columnValues.get(4));
                      }catch(Exception e) {log("Error parsing data cell #5");throw e;}
                    }
                    if (columnCount>=6)
                    {
                      try{
                        stmtORA_InsertData.setString(6, (String)columnValues.get(5));
                      }catch(Exception e) {log("Error parsing data cell #6");throw e;}
                    }
                    if (columnCount>=7)
                    {
                      try{
                        stmtORA_InsertData.setString(7, (String)columnValues.get(6));
                      }catch(Exception e) {log("Error parsing data cell #7");throw e;}
                    }
                    if (columnCount>=8)
                    {
                      try{
                        stmtORA_InsertData.setString(8, (String)columnValues.get(7));
                      }catch(Exception e) {log("Error parsing data cell #8");throw e;}
                    }
                    stmtORA_InsertData.execute();
                  }
                } //while (read file)
                stmtORA_InsertData.execute("commit");
                log("Processed " + new Long(rowIndex).toString() + " rows. Pumping finished");
              }
              catch(Exception e)
              {
                e.printStackTrace();
                throw new Exception("\n\nФайл "+csvFile.getName()+"\nСбой закачки в строке " + rowIndex + ":\n"+e.getMessage()+"\n\n");
              }
            }
            finally
            {
              if (stmtORA_InsertData instanceof OraclePreparedStatement) stmtORA_InsertData.close();
              input.close();
              log("Input CSV stream closed");
              csvFile.delete();
              log("Source CSV file deleted");
            }
          } //iterate csvFiles
        } // csvFiles is not null
      }
      finally
      {
        // Освобождаем все используемые ресурсы
        if (stmtORA_InsertData != null) stmtORA_InsertData.close();
        if (stmtORA_CheckStageClearance != null) stmtORA_CheckStageClearance.close();
        log("Resources released");
      }
    }
    catch(Exception e)
    {
      log(e.getMessage());
      throw e;
    }
    finally
    {
      // Закрываем соединения с базами данных
      if (conORA != null && !conORA.isClosed()) conORA.close();
      log(Connections closed");
    }
  }
  // Пишем в LOG сервера
  public static void log(String msg)
  {
    if (DEBUG) c_pump_tools.log(msg);
  }
}

для корректной работы после компиляции необходимо будет с помощью DBMS_JAVA.GrantPermission(..) дать права на удаление файлов из папки-источника CSV файлов (java.io.FilePermission). Точный текст PL/SQL команды ORACLE напишет прямо в тексте сообщения об ошибке, если при попытке удалении файла такого гранта не обнаружит. Текст примерно такой:
call dbms_java.grant_permission('<пользователь,которому дать права>','SYS:java.io.FilePermission', '<объект для раздачи прав>', 'read,write,execute,delete')
Для обеспечению удаления любых файлов в папке вместо имени файла нужно указать "*", например C:\\CSVFOLDER\\*

Отключение устаревания паролей

В 11g какой то умник включил режим password expire по умолчанию в профиле DEFAULT. Выключить можно так:

ALTER PROFILE DEFAULT LIMIT FAILED_LOGIN_ATTEMPTS UNLIMITED PASSWORD_LIFE_TIME UNLIMITED;

1 окт. 2009 г.

Драйвера jTDS для непосредственного подключения к серверу MS SQL из Oracle

Для проекта хранилища писал ETL для сбора данных с внутренних MS SQL серверов и перекладки его в наше ORACLE-based хранилище. Решил попробовать взгромоздить проприетарный MS SQL JDBC драйвер на допотопный ORACLE 10.2.0.1 Win32, но не тут то было... Сервер начал всеми силами исторгать из себя ошибки resolving'a, отказываясь грузить пакеты, что, честно говоря, было для меня, новостью, т.к. до этого я с 10.2.0.1 не работал, а на 10.2.0.4 и 11g таких проблем с установкой у меня с этими драйверами не было. На 11g они у меня работали вообще без загрузки с loadjava, а простым копированием в %ORACLE_HOME%/jdbc (кстати, почему - не знаю, не разбирался).
Короче, колупался с ними, колупался... потом плюнул, написал PL/SQL'ный CSV парсер для других источников и уехал. Потом вспомнил про знаменитый jTDS драйвер с sourceforge.net Попробовал на своем сервере - грузится без проблем, есть под любые версии Java'ы... работает прекрасно. Переписал парсер под соединение через jTDS 1.2 - все заработало. Если кому надо будет минимальными затратами перекачать данные между ORACLE и Microsoft SQL (либо Sybase) то рекомендую воспользоваться этими замечательными драйверками. Единственный минус, отсутствие нормальной документации. Есть правда автоматически генерируемый javadoc, но он может помочь только как справочник, а вот как учебник, нет... но правда и изучать там особенно нечего, jTDS поддерживает JDBC, так что работать можно через стандартные объекты, только connection string надо будет сформировать правильно, но на сайте ее формат и параметры приведены.

Да здравствует свободное ПО!