zowのプログラムな日々

日々のプログラミングとか

DBに落としこんでみる

今回作る静的CMSは1サイト1ファイルのSQLiteで作る。その方が管理もバックアップも楽だからだ。まずは、前回考えた構成をDBに落としこんでみる。

CREATE TABLE site (
  `name` TEXT,
  `label` TEXT,
  `title` TEXT, -- サイト タイトル
  `url` TEXT, -- サイトURL
  `category_filename_prefix` TEXT DEFAULT 'category_', -- Categoryファイル名接頭語
  `category_filename_suffix` TEXT, -- Categoryファイル名接尾語
  `tag_filename_prefix` TEXT DEFAULT 'tag_', -- Tagファイル名接頭語
  `tag_filename_suffix` TEXT, -- Tagファイル名接尾語
  `page_filename_prefix` TEXT, -- Pageファイル名接頭語
  `page_filename_suffix` TEXT, -- Pageファイル名接尾語
  `added_at` TEXT DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE pages (
  `page_id` INTEGER PRIMARY KEY AUTOINCREMENT,
  `page_name` TEXT, -- ページ名(識別用)
  `page_filename` TEXT, -- 生成するファイル名
  `page_label` TEXT, -- ページへリンクする場合のアンカーテキスト
  `page_title` TEXT, -- ページ タイトル
  `page_disabled` INT DEFAULT 0, -- 1でdisabled
  `page_description` TEXT, -- Description記述
  `page_template` TEXT, -- ページに適用するテンプレート名記述
  `page_group_id` INT, -- 所属page_group_id記述
  `page_category` TEXT, -- カンマ区切りでカテゴリ名記述
  `page_tags` TEXT, -- カンマ区切りでタグ名記述
  `page_place_holder_1` TEXT DEFAULT 'header_top', -- 予約:ヘッダ上(header_top)
  `page_place_holder_2` TEXT DEFAULT 'header_main', -- 予約:ヘッダコンテンツ(header_main)
  `page_place_holder_3` TEXT DEFAULT 'header_bottom', -- 予約:ヘッダ下(header_bottom)
  `page_place_holder_4` TEXT DEFAULT 'main', -- 予約:メインコンテンツ(main)
  `page_place_holder_5` TEXT DEFAULT 'side1', -- 予約:サイドコンテンツ(side1)
  `page_place_holder_6` TEXT DEFAULT 'side2', -- 予約:サイドコンテンツ2(side2)
  `page_place_holder_7` TEXT DEFAULT 'footer', -- 予約:フッタコンテンツ(footer)
  `page_place_holder_8` TEXT, -- 任意
  `page_place_holder_9` TEXT, -- 任意
  `page_place_holder_10` TEXT, -- 任意
  `page_place_holder_11` TEXT, -- 任意
  `page_place_holder_12` TEXT, -- 任意
  `page_place_holder_13` TEXT, -- 任意
  `page_place_holder_14` TEXT, -- 任意
  `page_place_holder_15` TEXT, -- 任意
  `page_place_holder_16` TEXT, -- 任意
  `page_place_holder_17` TEXT, -- 任意
  `page_place_holder_18` TEXT, -- 任意
  `page_place_holder_19` TEXT, -- 任意
  `page_place_holder_20` TEXT, -- 任意
  `added_at` TEXT DEFAULT CURRENT_TIMESTAMP
);

-- menu_itemsにはpages,tags,categoriesにある全レコードを登録する。更に外部リンクも登録できるようにする
CREATE TABLE menu_items (
  `menu_item_id` INTEGER PRIMARY KEY AUTOINCREMENT,
  `menu_item_name` TEXT, -- メニュー項目名(識別用)
  `menu_item_label` TEXT, -- メニュー表示名
  `menu_item_disabled` INT DEFAULT 0, -- 1 でdisabled
  `menu_item_type_id` INT, -- item_typeに紐づくID
  `menu_item_type` TEXT, -- 種別判別(page,tag,category,ext_link)
  `menu_item_link_to` TEXT, -- リンク先
  `menu_item_link_nofollow_flg` INT DEFAULT 0, -- 1 でnofollow
  `added_at` TEXT DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE contents (
  `content_id` INTEGER PRIMARY KEY AUTOINCREMENT,
  `content_name` TEXT, --コンテンツ名(識別用)
  `content_label` TEXT, --コンテンツ表示名
  `content_disabled` INT DEFAULT 0, -- 1 でdisabled
  `content_type` TEXT, -- コンテンツの種類を記述(menu等)
  `content_template` TEXT, -- コンテンツ表示テンプレート
  `content_data` TEXT, -- content_typeによってdataの扱いを変える。menuの場合はmenu_items_id列記等
  `added_at` TEXT DEFAULT CURRENT_TIMESTAMP,
  `updated_at` TEXT
);

CREATE TABLE contents_group (
  `contents_group_id` INTEGER PRIMARY KEY AUTOINCREMENT,
  `contents_group_name` TEXT, --コンテンツグループ名(識別用)
  `contents_group_label` TEXT, --コンテンツグループ表示名
  `contents_ids` TEXT, --所属するコンテンツ(カンマ区切りでcontents_idを表示順に列記)
  `added_at` TEXT DEFAULT CURRENT_TIMESTAMP,
  `updated_at` TEXT
);

CREATE TABLE categories (
  `category_id` INTEGER PRIMARY KEY AUTOINCREMENT,
  `category_name` TEXT, --カテゴリ名(識別用)
  `category_filename` TEXT, --カテゴリ一覧ファイル名
  `category_label` TEXT, --カテゴリ表示名
  `category_title` TEXT, --カテゴリ一覧ページタイトル
  `category_disabled` INT DEFAULT 0, -- 1 でdisabled
  `category_description` TEXT, -- Description記述
  `category_template` TEXT, -- カテゴリ一覧ページに適用するテンプレート名記述
  `added_at` TEXT DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE tags (
  `tag_id` INTEGER PRIMARY KEY AUTOINCREMENT,
  `tag_name` TEXT, --タグ名(識別用)
  `tag_filename` TEXT, --タグ一覧ファイル名
  `tag_label` TEXT, --タグ表示名
  `tag_title` TEXT, --タグ一覧ページタイトル
  `tag_disabled` INT DEFAULT 0, -- 1 でdisabled
  `tag_description` TEXT, -- Description記述
  `tag_template` TEXT, -- タグ一覧ページに適用するテンプレート名記述
  `added_at` TEXT DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE page_group (
  `page_group_id` INTEGER PRIMARY KEY AUTOINCREMENT,
  `page_group_name` TEXT, --ページグループ名(識別用)
  `page_group_filename` TEXT DEFAULT 'index.html', --ページグループファイル名(index.html)
  `page_group_pathname` TEXT, --ページグループ パス名
  `page_group_label` TEXT, --ページグループ表示名
  `page_group_title` TEXT, --ページグループ ページタイトル
  `page_group_disabled` INT DEFAULT 0, -- 1 でdisabled
  `page_group_description` TEXT, -- Description記述
  `page_group_template` TEXT, -- ページグループ一覧ページに適用するテンプレート名記述
  `added_at` TEXT DEFAULT CURRENT_TIMESTAMP
);

今回作る静的CMSのキモは、まさにこのSQLiteのDBなので、判りやすい様にコメントとか付けてみた。

あくまでも前回の構成から脳内で考えて作った物なので、恐らく現状ではまだ不十分だと思う。これからどんどんフィールド追加すると思う。

  • site:サイト全体に関連する設定等
  • pages:ページ情報を格納
  • menu_items:メニュー項目情報を格納
  • contents:コンテンツ情報を格納
  • contents_group:コンテンツグループ情報を格納
  • categories:カテゴリ情報を格納
  • tags:タグ情報を格納
  • page_group:ページグループ情報を格納

現状では上記8テーブルの構成になる。コンテンツの集合がコンテンツグループで、このコンテンツグループをページ情報のプレースホルダに割り当ててやる。メインコンテンツだけでなく、ページ内に配置する全てのフィールドを(サイドバーのメニューやヘッダータイトル等も)コンテンツとして扱う。なので、各ページには表示する場所に応じたプレースホルダを用意してやって、そことコンテンツグループを紐付ける感じになってる。今気づいたが、タグとカテゴリの一覧ページとページグループのインデックスページにはプレースホルダを用意していないので、ここにもじきに追加することになる。

カテゴリとタグについてはそのまんま。ページグループはページの集合になる。あとはmenu_itemsだが、ここに全てのページへのリンクを用意してやって、コンテンツとして任意の構成のメニューを作る事を考えている。

多分、このSQL見ただけじゃまだ掴めない人のほうが多いと思う。そういう人は、ページヘのリンクの集まりがメニューで、コンテンツの集合がページで、ページの集合がサイトってのを頭に入れてから見ると少しだけ判るかもしれない。

そして、今回のDBはこれで終わらない。トリガとか作ってみた。

CREATE TRIGGER tri_page_menu_add AFTER INSERT ON pages
BEGIN
  UPDATE pages SET `page_filename` = (
    SELECT
    (SELECT `page_filename_prefix` FROM site) ||
    `page_name` ||
    (SELECT `page_filename_suffix` FROM site) ||
    '.html'
    FROM pages
    WHERE ROWID = new.ROWID
  ) WHERE ROWID = new.ROWID;

  INSERT INTO menu_items (
    `menu_item_type_id`,
    `menu_item_name`,
    `menu_item_label`,
    `menu_item_title`,
    `menu_item_link_to`
  ) SELECT
      `page_id`,
      `page_name`,
      `page_label`,
      `page_title`,
      CASE
        WHEN new.`page_group_id` IS NULL
        THEN `page_filename`
        WHEN new.`page_group_id` IS NOT NULL
        THEN
          (SELECT `page_group_pathname`
          FROM page_group
          WHERE `page_group_id` = new.`page_group_id`) || '/' ||`page_filename`
      END AS filepath
    FROM pages
    WHERE ROWID = new.ROWID;

  UPDATE menu_items SET `menu_item_type` = 'page'
  WHERE ROWID = last_insert_rowid();
END;

CREATE TRIGGER tri_page_menu_del AFTER DELETE ON pages
BEGIN
  DELETE FROM menu_items
  WHERE `menu_item_type` = 'page'
  AND `menu_item_type_id` = old.`page_id`;
END;


CREATE TRIGGER tri_page_menu_disable AFTER UPDATE OF `page_disabled` ON pages
  FOR EACH ROW
    WHEN new.`page_disabled` = 1
BEGIN
  UPDATE menu_items
  SET `menu_item_disabled` = 1
  WHERE `menu_item_type` = 'page'
  AND `menu_item_type_id` = new.`page_id`;
END;

CREATE TRIGGER tri_page_menu_enable AFTER UPDATE OF `page_disabled` ON pages
  FOR EACH ROW
    WHEN new.`page_disabled` = 0
BEGIN
  UPDATE menu_items
  SET `menu_item_disabled` = 0
  WHERE `menu_item_type` = 'page'
  AND `menu_item_type_id` = new.`page_id`;
END;


CREATE TRIGGER tri_contents_update AFTER UPDATE ON contents
BEGIN
  UPDATE contents
  SET `updated_at` = CURRENT_TIMESTAMP
  WHERE `content_id` = new.'content_id';

  UPDATE contents_group
  SET `updated_at` = CURRENT_TIMESTAMP
  WHERE `content_ids` LIKE '%' ||new.`content_id` ||'%';

END;


CREATE TRIGGER tri_category_menu_add AFTER INSERT ON categories
BEGIN
  UPDATE categories SET `category_filename` = (
    SELECT
      (SELECT `category_filename_prefix` FROM site) ||
      `category_name` ||
      (SELECT `category_filename_suffix` FROM site) ||
      '.html'
    FROM categories
    WHERE ROWID = new.ROWID
  ) WHERE ROWID = new.ROWID;

  INSERT INTO menu_items (
    `menu_item_type_id`,
    `menu_item_name`,
    `menu_item_label`,
    `menu_item_title`,
    `menu_item_link_to`
  ) SELECT
      `category_id`,
      `category_name`,
      `category_label`,
      `category_title`,
      `category_filename`
    FROM categories
    WHERE ROWID = new.ROWID;

  UPDATE menu_items SET `menu_item_type` = 'category'
  WHERE ROWID = last_insert_rowid();
END;

CREATE TRIGGER tri_category_menu_del AFTER DELETE ON categories
BEGIN
  DELETE FROM menu_items
  WHERE `menu_item_type` = 'category'
  AND `menu_item_type_id` = old.`category_id`;
END;

CREATE TRIGGER tri_category_menu_disable AFTER UPDATE OF `category_disabled` ON categories
  FOR EACH ROW
    WHEN new.`category_disabled` = 1
BEGIN
  UPDATE menu_items
  SET `menu_item_disabled` = 1
  WHERE `menu_item_type` = 'category'
  AND `menu_item_type_id` = new.`category_id`;
END;

CREATE TRIGGER tri_category_menu_enable AFTER UPDATE OF `category_disabled` ON categories
  FOR EACH ROW
    WHEN new.`category_disabled` = 0
BEGIN
  UPDATE menu_items
  SET `menu_item_disabled` = 0
  WHERE `menu_item_type` = 'category'
  AND `menu_item_type_id` = new.`category_id`;
END;


CREATE TRIGGER tri_tag_menu_add AFTER INSERT ON tags
BEGIN
  UPDATE tags SET `tag_filename` = (
    SELECT
    (SELECT `tag_filename_prefix` FROM site) ||
    `tag_name` ||
    (SELECT `tag_filename_suffix` FROM site) ||
    '.html'
    FROM tags
    WHERE ROWID = new.ROWID
  ) WHERE ROWID = new.ROWID;

  INSERT INTO menu_items (
    `menu_item_type_id`,
    `menu_item_name`,
    `menu_item_label`,
    `menu_item_title`,
    `menu_item_link_to`
  ) SELECT
      `tag_id`,
      `tag_name`,
      `tag_label`,
      `tag_title`,
      `tag_filename`
    FROM tags
    WHERE ROWID = new.ROWID;

  UPDATE menu_items SET `menu_item_type` = 'tag'
  WHERE ROWID = last_insert_rowid();
END;

CREATE TRIGGER tri_tag_menu_del AFTER DELETE ON tags
BEGIN
  DELETE FROM menu_items
  WHERE `menu_item_type` = 'tag'
  AND `menu_item_type_id` = old.`tag_id`;
END;

CREATE TRIGGER tri_tag_menu_disable AFTER UPDATE OF `tag_disabled` ON tags
  FOR EACH ROW
    WHEN new.`tag_disabled` = 1
BEGIN
  UPDATE menu_items
  SET `menu_item_disabled` = 1
  WHERE `menu_item_type` = 'tag'
  AND `menu_item_type_id` = new.`tag_id`;
END;

CREATE TRIGGER tri_tag_menu_enable AFTER UPDATE OF `tag_disabled` ON tags
  FOR EACH ROW
    WHEN new.`tag_disabled` = 0
BEGIN
  UPDATE menu_items
  SET `menu_item_disabled` = 0
  WHERE `menu_item_type` = 'tag'
  AND `menu_item_type_id` = new.`tag_id`;
END;


CREATE TRIGGER tri_page_group_menu_add AFTER INSERT ON page_group
BEGIN
  UPDATE page_group SET `page_group_pathname` = (
    SELECT `page_group_name`
    FROM page_group
    WHERE ROWID = new.ROWID
  ) WHERE ROWID = new.ROWID;

  INSERT INTO menu_items (
    `menu_item_type_id`,
    `menu_item_name`,
    `menu_item_label`,
    `menu_item_title`,
    `menu_item_link_to`
  ) SELECT
      `page_group_id`,
      `page_group_name`,
      `page_group_label`,
      `page_group_title`,
      `page_group_pathname`|| '/' || `page_group_filename`
    FROM page_group
    WHERE ROWID = new.ROWID;

  UPDATE menu_items SET `menu_item_type` = 'page_group'
  WHERE ROWID = last_insert_rowid();
END;

CREATE TRIGGER tri_page_group_menu_del AFTER DELETE ON page_group
BEGIN
  DELETE FROM menu_items
  WHERE `menu_item_type` = 'page_group'
  AND `menu_item_type_id` = old.`page_group_id`;
END;

CREATE TRIGGER tri_page_group_menu_disable AFTER UPDATE OF `page_group_disabled` ON page_group
  FOR EACH ROW
    WHEN new.`page_group_disabled` = 1
BEGIN
  UPDATE menu_items
  SET `menu_item_disabled` = 1
  WHERE `menu_item_type` = 'page_group'
  AND `menu_item_type_id` = new.`page_group_id`;

  UPDATE pages
  SET `page_disabled` = 1
  WHERE `page_group_id` = new.`page_group_id`;

END;

CREATE TRIGGER tri_page_group_menu_enable AFTER UPDATE OF `page_group_disabled` ON page_group
  FOR EACH ROW
    WHEN new.`page_group_disabled` = 0
BEGIN
  UPDATE menu_items
  SET `menu_item_disabled` = 0
  WHERE `menu_item_type` = 'page_group'
  AND `menu_item_type_id` = new.`page_group_id`;

  UPDATE pages
  SET `page_disabled` = 0
  WHERE `page_group_id` = new.`page_group_id`;
END;

CREATE TRIGGER tri_page_group_update_path AFTER UPDATE OF `page_group_pathname` ON page_group
BEGIN
  UPDATE menu_items
  SET `menu_item_link_to` = new.`page_group_pathname`|| '/' || new.`page_group_filename`
  WHERE `menu_item_type` = 'page_group'
  AND `menu_item_type_id` = new.`page_group_id`;

  UPDATE menu_items
  SET `menu_item_link_to` = REPLACE(`menu_item_link_to`, old.`page_group_pathname`, new.`page_group_pathname`)
  WHERE `menu_item_type` = 'page';
END;

今回のCMSではテーブル間の連携とかはトリガを活用していこうと思う。出来る限りプログラムでの動作を減らしたい。プログラム側で書くのはシンプルなCRUDぐらいにして、SQLで出来る部分は出来る限りSQLで書くって事にしてる。普段書き慣れていないので、調べながら書いていて結構ストレスが溜まったりもするのだが、多分に意味がある事だと思うので我慢して書いている。どういう意味があるのかは勘の良い人ならピンとくるんじゃなかろうか。ヒントはjinja2とTwigかな。細かい事は次回記事で説明したいと思う。