2014年7月31日木曜日

EclipseでJSLintを使う

Javascriptの構文チェックを行う方法はいくつかあると思いますが、
今回、既存システムに対して改めて構文チェックを行おうと思い、
Eclipseのプラグインも提供されているJSLintを使う事にしました。

【事前準備】

Eclipseマーケットプレースより、JSLintのプラグインをインストール
※インストール後にEclipseの再起動が必要です。

Eclipseマーケットプレースの使いかたについては別記事で紹介していますので併せて参照ください。

【チェック方法】

1.メニューバーより「プロジェクト」→「プロパティ」を選択
2.「JSHint」を選択し、「Enable JSHint for~~」の欄で「追加」ボタンを押す
3.対象とするファイル、およびフォルダの指定ができるので、必要なファイル、フォルダを指定

プロジェクトをビルドすると、問題ビューに警告が表示されるようになります。

【警告を除外する方法】

JSHintが出すいくつかの警告は、構文の問題というよりもお作法(規約)に近いものがあり、
開発スタイルによっては無意味です。

設定で、出力する警告を制御する事が出来ます。


やり方は2つ。プロジェクト単位の設定か、Eclipse全体の設定か。を選べます。

1.プロジェクト単位
 「プロジェクト」→「プロパティ」→「JSHint」→「Configuration」→
 「Enable Project~」のチェックOnで設定を記載

2.全体
 「ウインドウ」→「設定」→「JSHint」→「構成」で設定を記載


いずれも、以下のような構文で記載します。
設定可能なオプションは公式ドキュメントを参照ください。
(日本語訳してくれている方もいるので併せて参考で)

○json形式


{
"laxcomma":true,
"laxbreak":true,
"smarttabs":true,
"evil":true
}

パラメータで抑止できないエラーについては、以下の対処法で対応可能です。

1) jshint.jsより、該当のメッセージを探す。
2) メッセージコードをメモ
3) パラメータに、以下形式で記載

{
"asi":true,
"evil":true,
"laxcomma":true,
"laxbreak":true,
"smarttabs":true,
"sub":true,
"-W009":false, //The array literal notation [] is preferable.
"-W099":false, //Mixed spaces and tabs
"-W041":false  //Use '{a}' to compare with '{b}'
}


2014年7月30日水曜日

Eclipseでプラグインを簡単にインストールできるEclipseマーケットプレース

Eclipseでプラグインをインストールしたいと思い、過去の記憶から色々と記事等を探していたところ、
GUIより簡単に指定、インストールできる仕組みがありました。


VisualStudioにもNuGetパッケージ管理という方法がありますが、それと同様のようです。


【手順】

1.「ヘルプ」より、「Eclipseマーケットプレース」を選択。
 ※ここで出てこない場合には、Exclipseのバージョン、利用しているパッケージの違いのようです。
 ※Eclipseマーケットプレース自体もプラグインのようなので、追加インストールは可能と思われます。

2.画面が出てきます。検索キーワードを入れると、対象のプラグインが表示されます。
 選択して、「次へ」

3.ライセンス条項等が出るので確認して「次へ」

4.いくつかメッセージが出るかもしれませんが、適時対処

これだけでインストールできます。


従来型「ヘルプ」→「新規ソフトウェアのインストール」でも同じことはできるはずです。
今回、この方法にたどり着けず、たどり着いても欲しいプラグインが出てこず、という状況で
方法を探していたところマーケットプレースへたどり着いた。という状況でした。

今はもう更新されていないのかもしれません・・・(未確認です

2014年7月29日火曜日

Eclipse開発環境で表示するリソースを絞る方法

膨大なソース、モジュールの中から、必要な分だけを絞って表示する方法です。


・プロジェクトエクスプローラの右上にある▼をクリックして、「ワーキングセットの選択」を選択
・「新規」を押して、表示したいリソースを選択
・名前を付けて保存

次回より、▼の中に登録したワーキングセットが表示されるようになります。

2014年7月7日月曜日

MySQLのGROUP_CONCAT という関数がものすごく便利な件

簡単に言ってしまうと、複数行のデータを、ぐちゃっと1行に纏めてくれる関数です。

SQL


select group_concat(column_name) from information_schema.columns
where table_schema = 'information_schema' and table_name = 'COLLATIONS'

結果


COLLATION_NAME,CHARACTER_SET_NAME,ID,IS_DEFAULT,IS_COMPILED,SORTLEN


ちなみに、group_concatを使わずに取れるデータがこれ。

-------------------------------
COLLATION_NAME
CHARACTER_SET_NAME
ID
IS_DEFAULT
IS_COMPILED
SORTLEN
-------------------------------
複数行のデータが、1行にまとまっていることがわかります。


親子の関連を持つデータで、このデータを横並び表示したい時とかにも活用できそうです。

注意点

●戻せるデータのサイズが決まってます。(設定で変更可能)
[my.ini]の[mysqld]セクションに以下を記載すると反映されます。(要再起動
set group_concat_max_len = 2048000;
※2MBにしています

●NULLを含むデータの場合、NULLが除外されます。

応用

・区切り文字をタブへ変えて、並び順を指定しています
・NULLは、ブランクへ置き換える事により、除外されることを防止しています



select group_concat(CASE WHEN column_name IS NULL THEN '' ELSE column_name END order by ordinal_position separator '\t') from information_schema.columns
where table_schema = 'information_schema' and table_name = 'COLLATIONS'

補足

Oracleでも、11gより同等の関数があるようです。

2014年7月4日金曜日

MySQLでスキーマ情報を取得する

汎用な処理が必要になったので、あるだろうと思い調べてみたらありました。
MySQL 5.5で確認しています。

テーブル名を取得する

SELECT * FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'データベーススキーマ名' ORDER BY TABLE_NAME;

Viewも取れてしまうので、あとは工夫で絞ると良いと思います。

カラム情報を取得する

SELECT * FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'データベーススキーマ名' AND TABLE_NAME = 'テーブル名'
ORDER BY ORDINAL_POSITION;

汎用的にする

Function化して返すようにしています。
この処理は、CSVのヘッダ情報をカンマ区切り、ダブルクォーテーション付きで返すものです

---------------------------------------------------

CREATE FUNCTION GetColumnCommentsOfCSV(schemaName VARCHAR(255) , tableName VARCHAR(255)) RETURNS VARCHAR(2000)
BEGIN
  DECLARE done INT DEFAULT 0;
  DECLARE vColName VARCHAR(255);
  DECLARE vColComment VARCHAR(255);
  DECLARE vOutChar VARCHAR(2000);
  DECLARE curRec CURSOR FOR
    SELECT
        COLUMN_NAME
        ,COLUMN_COMMENT
    FROM
        INFORMATION_SCHEMA.COLUMNS
    WHERE 
        UPPER(TABLE_SCHEMA)   = UPPER(schemaName)   collate utf8_general_ci
      AND UPPER(TABLE_NAME)    = UPPER(tableName)    collate utf8_general_ci
    ORDER BY
        TABLE_NAME,ORDINAL_POSITION
    ;
  DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
  SET vOutChar = '';
  OPEN curRec;
  REPEAT
    FETCH curRec INTO vColName , vColComment;
    IF NOT done THEN
      IF LENGTH(vOutchar) > 0 THEN
        SET vOutchar = CONCAT(vOutChar,',');
      END IF;
      SET vOutchar = CONCAT(vOutChar,'"');
      SET vOutChar = CONCAT(vOutChar,vColComment);
      SET vOutchar = CONCAT(vOutChar,'"');
    END IF;
  UNTIL done END REPEAT;
  CLOSE curRec;
  RETURN vOutChar;
END;
//
DELIMITER ;

Oracleとか、SQLserverでもやってた記憶があるので、今度載せます。

2014年7月3日木曜日

Windows8.1 にて過負荷とのたたかい (続き) まだ未解決

先日の記事で、Windows8.1のディスクアクセスが100%に張り付いてしまうという件を書いたのですが、いったん収まったと思ったらまだ引き続き起きているようなので、
抜本的なOS不具合解消のため、いくつか情報をあつめ、対処をしてみました。


○windowsタスクのホストプロセス が、ディスクアクセスを占有する

 
しばらく様子を見ていたところ、いきなり登場してハードディスクを占有しました。
これで調べてみると、Windows8.1のバックアップが悪さをしているかもとの事。

タスクを調べてみました。

コントロールパネル -> タスクのスケジュール -> Microsoft -> Windows -> WindowsBackup

見てみると、「ファイルがありません」のエラー

ま、動いていないんでしょう。タスクを「無効」にしてみました。


○イベントログでエラーが連続的に起きている。


【エラー内容】
Diagnostic Service Host サービスを、次のエラーが原因で開始できませんでした:
サービスが適切に機能するために必要な特権が、サービス アカウント構成に存在しません。 サービス Microsoft 管理コンソール (MMC) スナップイン (services.msc) とローカル セキュリティ設定 MMC スナップイン (secpol.msc) を使って、サービス構成とアカウント構成を表示することができます。

2014/6/6から発生していました。WindowsUpdateの直後かも。


同様のお悩みの方がいました。MSにも情報あり
http://social.technet.microsoft.com/Forums/windows/ja-JP/fc74eaec-99bc-44ae-97f4-ece5d3cc76a5/diagonostic-service-host-id-7000?forum=w7itprogeneralja

[コントロールパネル] -> [グループポリシーの編集] -> [コンピューターの構成] -> [Windows の設定] -> [セキュリティの設定] -> [ローカル ポリシー] ->
[ユーザー権利の割り当て] -> [システム パフォーマンスのプロファイル]

修正しようとしたら、「ユーザーまたはグループの追加」ボタンが押せませんでした。
ドメイン参加PCなので制御されているのかも。と思い、LocalのAdministratorで
ログインしても、ボタンが押せませんでした。


なのでいったん断念

まだ続きがありそうです。


----------------------------------------------------------
2014/08/23追記

しばらく放置していましたが、またディスク占有が発生したので
思いきって、Diagnostic System Hostを無効へ設定しました。

そうすると、
「Diagnostic Service Host サービスを、次のエラーが原因で開始できませんでした」
のエラーは出なくなりました。

ただ、ディスクアクセスは相変わらずです。
⇒数分後、おとなしくなりました。

これでしばらく様子を見てみようと思います。

MySQLで、カーソルを使って変数バインドでデータを返す

mysqlでカーソルを使ってあれこれやろうとした時に作ったサンプルを置いておきます。

mysqlの公式ドキュメントに載っているやり方のようです。

カーソルで、値をバインドしたい時には、ユーザー変数を利用するとできます。

プロシージャサンプル


----------------------------------------------------------------
drop procedure if exists sample_cursor;
DELIMITER ;;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `sample_cursor` (IN schemaNm VARCHAR(100) , IN tableNm VARCHAR(100) , IN pos VARCHAR(10))
  READS SQL DATA
BEGIN
  DECLARE done INT DEFAULT 0;
  DECLARE val VARCHAR(100);
  DECLARE cur CURSOR FOR
  SELECT
      column_name
  FROM
      information_schema.columns
  where
      TABLE_SCHEMA   = @table_schema collate utf8_unicode_ci
    and TABLE_NAME    = @table_name collate utf8_unicode_ci
    and ORDINAL_POSITION = @ordinal_position collate utf8_unicode_ci
  ;
  DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
  select schemaNm , tableNm , pos into @table_schema ,@table_name , @ordinal_position;
  open cur;
  REPEAT
    FETCH cur into val;
    -- データが存在する場合
    IF NOT done THEN
      select val;
    END IF;

  UNTIL done END REPEAT;
  close cur;

end
;
;;;
DELIMITER ;

----------------------------------------------------------------

以下実行結果



mysql> call sample_cursor('information_schema','COLUMNS',1);

+---------------+
| val      |
+---------------+
| TABLE_CATALOG |
+---------------+
1 row in set

Query OK, 0 rows affected

mysql> call sample_cursor('information_schema','COLUMNS',2);

+--------------+
| val          |
+--------------+
| TABLE_SCHEMA |
+--------------+
1 row in set

Query OK, 0 rows affected

mysql> call sample_cursor('information_schema','COLUMNS',3);

+------------+
| val        |
+------------+
| TABLE_NAME |
+------------+
1 row in set

Query OK, 0 rows affected




まだまだいろいろとできそうです。

2014年7月2日水曜日

MySQLでMerge文

MERGE , UPSERTといわれる、INSERTとUPDATEを組み合わせた処理。

MySQLでは、[REPLACE]または[INSERT ON DUPLICATE KEY UPDATE]で
実装する事が出来ます。

[REPLACE]
http://dev.mysql.com/doc/refman/4.1/ja/replace.html

[記述例]

REPLACE INTO TARGET_TABLE (COL1,COL2,COL3) VALUES ('1','2','3');

内部的にはDELETE INSERTということなので、タイムスタンプ等の保持で工夫は必要。

ただ、圧倒的に楽にはなりそうです。
(実際、DELETEINSERTを書くところだったので

MySQLで動的DDL

見るよりやってみる。が分かりやすいかもしれません。

キーワード
PREPAREステートメント , executeステートメント

Procedureの実装

drop procedure if exists cust_createtable;
DELIMITER ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `cust_createtable` (IN table_nane varchar(100))
  READS SQL DATA
BEGIN
  declare col varchar(4000);
  set col = 'col1 varchar(100)';
  select CONCAT('create table ',table_nane,' (' , col , ')') into @qry;
  PREPARE ddl_stmt from @qry;
  execute ddl_stmt;
end
;
;;
DELIMITER ;

呼び出し

call cust_createtable('test3')

確認


show tables like 'test%'

javaでzipファイルを作成する

javaでzipな要件が出てきたのでちょっと書いてみました。

javaに関してはド素人なので文法やお約束などすっとばしてる可能性大ですがご容赦ください。


【やること】

・zipファイル名、対象のファイル、格納する際のパス加工を指定してzip処理を行う。
・元のファイルは特にいじらない。
・ディレクトリ指定された場合、ディレクトリ内のすべてのファイルを対象とする。


【実施環境】

CentOS6.5
java version "1.6.0_30"
OpenJDK Runtime Environment (IcedTea6 1.13.3) (rhel-5.1.13.3.el6_5-x86_64)
OpenJDK 64-Bit Server VM (build 23.25-b01, mixed mode)


【注意点】

いろんな情報を見ていると、JRE1.6系では全角のファイル名が扱えないとか
何とか、いろんな情報が出ているようです。
その為の検証だったのですが、結果、問題ありませんでした。
⇒その後の検証で原因発覚。利用している解凍ツールが対応していただけでした。


ant.jarが持つ、Zip圧縮ツールを使うサンプルです。

【ソースの中身でちょっと分かりづらい仕様の説明】

・存在しないファイルを指定した場合、ゴミZipだけが残る事象が確認されたため
 回避するためにNotFoundExceptionを発生させています。
・絶対パス指定の場合、私の環境ではきれいにフォルダ構成まで作られてしまったため
 それを回避するためにZipEntryへは相対パス以下のフォルダ指定をするようにしました。
・ライブラリを、java.io.zipへ戻せるように、Encodingは外から指定可能とし、
 指定された場合のみ使うようにしました。



【ソース】


汎用性を持たせるため、2つに分けました。

----------------------------------------------------------------
ZipTest.java
----------------------------------------------------------------

import java.io.*;
import java.lang.*;
import java.util.*;
import java.util.ArrayList;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import org.apache.tools.zip.ZipOutputStream;

public class ZipTest{
  public static void main(String[] args){
    File  file;                 //作成するzipファイルの名前
    List<File> files = new ArrayList<File>();
    String baseDirectory = "";
    if (args != null && args.length > 2)
    {
      //引数1 zipファイル名
      file = new File(args[0]);
       
      //引数2 基準ディレクトリ
      baseDirectory = args[1];

      //引数3以降 対象ファイルのパス
      for (int i= 2;i<args.length;i++)
      {
        files.add(new File(args[i]));
      }
      System.out.println(files.size());
    }
    else
    {
      //demo
      file = new File("sample.zip");
      baseDirectory = "/tmp/sample/csv/";
      //圧縮対象を相対パスで指定
      files.add(new File("/tmp/sample/csv/あ.txt"));
      files.add(new File("/tmp/sample/csv/い.txt"));
      files.add(new File("/tmp/sample/csv/ああ.txt"));
      files.add(new File("/tmp/sample/csv/いい.txt"));
    }

    ZipCompressUtils zip;
    zip = new ZipCompressUtils(baseDirectory);

    try{
      zip.Compress(file,files);
    }
    catch (Exception e){
    }
  }
}

----------------------------------------------------------------


実行処理
----------------------------------------------------------------
ZipCompressUtils.java
----------------------------------------------------------------


import java.io.*;
import java.lang.*;
import java.util.*;
//import java.util.zip.ZipEntry;
//import java.util.zip.ZipOutputStream;
import org.apache.tools.zip.*;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipOutputStream;

public class ZipCompressUtils
{
  //Private Declare
  static String _rootPath = null;
  static String _encoding = "MS932";
   
  //Constructor
  public ZipCompressUtils()
  {
  }
  public ZipCompressUtils(String rootPath)
  {
    _rootPath = rootPath;
  }
  public ZipCompressUtils(String rootPath , String encoding)
  {
    _rootPath = rootPath;
    _encoding = encoding;
  }
  public boolean Compress(File target , List<File> files) throws Exception {
    try{
      ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(target));
     encode(zos, files);
      if (_encoding != null)
      {
        zos.setEncoding(_encoding);
      }
     zos.close();
      return true;
    }
    catch (Exception e){
      target.delete();
      throw e;
    }
  }
  static void encode(ZipOutputStream zos, List<File> files) throws Exception {
    byte[] buf = new byte[1024];

    //File配列のLoop
    for (File f : files) {
      if (!(f.exists())){
        throw new FileNotFoundException();
      }
      //ディレクトリなら再帰
      if (f.isDirectory()) {
        encode(zos, Arrays.asList(f.listFiles()));
      } else {
        //ファイルの場合の処理
        String zipEntryPath = f.getPath().replace(_rootPath,"").toString().replace("\\", "/");
        ZipEntry entry = new ZipEntry(zipEntryPath);
        zos.putNextEntry(entry);
        InputStream is = new BufferedInputStream(new FileInputStream(f));
        for (;;) 
        {
          int len = is.read(buf);
          if (len < 0) break;
          zos.write(buf, 0, len);
        }
      }
    }
  }
}