mysqlのindex(key)は、nullありで指定しても結局nullレコードもindexされる

たまに忘れるので、メモ。

mysqlのindex(key)は、nullありで指定しても結局nullレコードもindexされる。

col_name IS NULLを使用した検索では、col_nameにインデックスが張られている場合にインデックスが使用されます。

MySQL :: MySQL 5.6 リファレンスマニュアル :: 8.3.1 MySQL のインデックスの使用の仕組み

これの何が問題なのかと言えば、一般的(SQL標準的?)にはNULLはインデクスされないものであり、相変わらずmysqlの動作がsql標準から微妙にずれているということである。

sqlの仕様は2・3万するのでちょっと読めていないが、多分sql標準を読んでいるだろう人の記事を引用。

また一般的に,B-treeはNULLをキー値として保持しないため,IS [NOT] NULLを指定した場合もインデックスは使われません。

第7回 性能改善の鍵,インデックスの特性を知る~B-treeとハッシュ (1)B-tree :SQLアタマアカデミー|gihyo.jp … 技術評論社

*1
(InnoDBMyISAMもBTREEのみななので、上の引用は要するにmsyqlのインデクスは一般的ではないと言っている事になる)

ストレージ エンジン 許容インデックス タイプ
MyISAM BTREE
InnoDB BTREE
MEMORY/HEAP HASH、BTREE

MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.1.13 CREATE INDEX 構文


実際には、以下のようなケースで困る。

-- indexデータからNULLのレコードを除く事でindexデータの軽量化を行う
CREATE TABLE item (
  status INT(1) NULL DEFAULT 1,
  name TINYTEXT NOT NULL,
  time_for_sorting DATETIME NOT NULL, -- 5.0以降はTIMESTAMPでよい。これもSQL標準を無駄に逸脱していて嫌な所(だった)
  KEY idx_a (active, time_for_sorting)
)
-- 以下の形でのアクセスが趨勢である場合、メモリにロードされるindexデータが少なくて済むはず
SELECT * FROM items WHERE status IN (1,2) ORDER BY time_for_sorting DESC
-- status を null にして、あえて検索する必要性の薄いレコードである事を表現
SELECT * FROM items AS hidden_items WHERE status IS NULL

では、mysqlはどうやってNULLを検索しているのか、といえば簡単な話で、NULL用の1bitを頭に入れている。なので、mysqlでは意図とは真逆にindexデータの量が膨らんでいるという事だ、ワオ。

つまり、mysqlでindexデータ絞り込みを行うにはパーティショニング(mysql 5.1から)でざっくり分割するしかないっぽい。これは、パーティショニングが行える5.1までは1テーブル内の全レコード分のindexが(場合によっては)insert/update毎に再構築される状況にあったというわけであり、(実際のところどうかはさておき)mysqlがまともに使えるようになったのは5.1からなんじゃないかと思われても仕方ないと思ったりする。

*1:他にも、T字型ERの中の人もnull-keyでindexの負荷を減らす方法をかなり有効な技法として推していたかと思います

CakePHP1.2 -> 1.3: Habtmのバリデーション

1.2にはなかったが、1.3では__validateWithModelsという関数ができて、Model::validatesの中で使われている。この関数は、habtmの中間テーブルで入力値バリデーションを行ってくれる、というものだ。

Cakeのhabtmデータは他のアソシエーションとは違い、アソシエーション先のモデルへデータを渡して処理してもらうのではなく、基本的にそのモデルの内部で処理する(ItemとCategoryがhabtm関係の時、categories_itemsテーブルの為のCategoriesItem中間モデルは必須ではないため)

<?php
class Category extends Model { ...... }
class Item extends Model {
......
  var $hasAndBelonsToMany = array(
    'Category' => array('with' => 'CategoriesItem'),
  );
}
class CategoriesItem extends Model {
  var $validate = array(
    'category_id' => 'idExists',
    'some_field' => array('notempty', ....),
  )
......
  function idExists(...) { ... バリデーション ... }
}
withオプションと中間テーブルモデルが実在しないと動作しない。

$Item = Itemモデルを生成する;
$Item->save(array('Category' => array(
  array('category_id' => '2', 'some_field' => 'hogehoge'),
  array('category_id' => '8', 'some_field' => 'mogemoge'),
));
もしくは
$Item->save(array('Category' => array(
  array('CategoryItem' => array('category_id' => '2', 'some_field' => 'hogehoge')),
  array('CategoryItem' => array('category_id' => '8', 'some_field' => 'mogemoge')),
));
ただし、以下では作動しない
$Item->save(array('Category' => array(
  'Category'  => array('2', '8'),
));

これで晴れて、中間テーブルに対して追加のデータを与えられるようになってさぞかし楽しいだろうと思っていたのだが、どうも、雲行きが怪しい。よくよく観察してみると、バリエーションに失敗した時、CategoriesItemのvalidationErrorsを取得する方法がないので、何のエラーで失敗したのかわからない(というか、CategoriesItemsがvalidation失敗したのか、あるいはItemモデル内部で何か起きたのかの別すらできない)

とはいえ、インチキなデータを受け取らないよう禁則処理できるようになったのはありがたい事だ。ちゃんとバリデーションを設定すれば、内部的なfieldへWebフォームからむりくり値を入力される危険はなくなる。

CakePHPでsession.cookie_secureを外す

1.2時代、セッションの初期化処理(CakeSession#__initSession)が2度以上走るとrequire_onceなせいで、設定のカスタマイズに色々問題があった(特にsession.cookie_secureはそこだけ他の設定項目とは別枠の処理になっていて手が出せない)

<?php
function __initSession() {
  $iniSet = function_exists('ini_set');

  if ($iniSet && env('HTTPS')) {
    ini_set('session.cookie_secure', 1); // なぜかこの項目だけ強制。
  }
// ... (略) ...
// session.cookie_lifetime等をカスタムするために、phpファイルをinclude可能にしてある。
  if (empty($_SESSION)) {
    $config = CONFIGS . Configure::read('Session.save') . '.php';
    if (is_file($config)) {
      require_once($config); // 1.2
      require($config); // 1.3
    }
  }
// ... (略) ...

しばらく前、バージョンが1.3に上がり、ここの処理がrequire_onceからrequireになってようやく設計意図通り、普通にconfig/custom_session.phpにini_set('session.xxxx', xxx);をつらつら並べてConfigure::write('Session.save', 'custom_session');とするだけでちゃんとセッションの設定をカスタマイズできるようになった。

かと思ったのだが、実はSessionComponentやSessionHelper等を読込んだ時に、$_SESSIONがemptyでない状態で__initSessionに突入している事に気づき、新たな問題が発覚。やっぱり、cookie_secureが上書きされる。

実は仕様なのか?と思い調べてみると、一応、cakePHP1.3-developmentでは解決しているようだった。

http://cakephp.lighthouseapp.com/projects/42648/tickets/731

とはいえ、現状ではどうしようもないので、フレームワーク内部コードを触りたくない場合はpatchを当てたcake_session.phpをconfig/bootstrup.phpで読込む事に……。(CakeのクラスはApp::importからロードされるので、フレームワークのコアでApp::import('Core', 'CakeSession');が呼ばれる前にCakeSessionクラスを定義しておけば、App::importはrequire/includeを行わない)

username@独自ドメインなアカウントでgoogle appengineを"Create an Application" する時は気をつけろ!

Google Appsで取ったusername@独自ドメインなアカウントからappengineを使う場合、注意する必要がある。
appIDを取得できる事にはできるのだが、取得後、AppIDの一覧画面に戻っても、表示は初期の空状態のままである。

どうもhttp://code.google.com/intl/ja/appengine/kb/general.html#signinに因ると、

Google Apps のアカウントの場合は、次の URL にアクセスします:

だそうで、http://appengine.google.com/から行ってAppIDとっても何の表示も警告も無いが、/a/example.comから入り直すと、取れている。

[python] 様々なtips

外部からattributeを持たせても、モジュールや継承先にも影響を及ぼす様。

--- a.py
class A(object): pass
--- ....py
import a
class B(a.A): pass
print B.x # -> AttributeError
setattr(a.A, 'x', '!!')
print B.x # -> '!!'

>>> class A(object): pass
>>> class B(A): pass
>>> setattr(A, 'a', lambda self: self.__class__.__name__)
>>> B().a
'B'

defは関数オブジェクトの生成と代入である様

>>> def a():
...   def b():
...     return 'b'
...   print b()
...   return 'a'
>>> b()
NameError
>>> a()
'b'
'a'
>>> b()
NameError
ruby
  • class/moduleは定数であり、namespaceである(定数なのでdef内に書くとsyntax error)
  • def/class/module宣言はnamespace内にてglobalである。
  • ともかくrubyは宣言内だろうがなんだろうが、すべての文が順次(上から下へ)実行される
>>> def a
...   def b
...     'b'
...   end
...   p b
...   'a'
... end
>>> b
NameError
>>> a
'b'
'a'
>>> b
'b'
SyntaxError: compile error

メタクラスメモ

>>> class Meta(type):
...   def __new__(name, *args):
...     print 'created'
...     name = 'Memeta!'
...     return type(name, *args)
>>> class A(object):
...   __metaclass__ = meta
'created'
>>> A.__name__
'Memeta!'

>>> def meta(name, *args):
...   print 'created'
...   name += '!'
...   return type(name, *args)
>>> class A(object):
...   __metaclass__ = meta
'created'
>>> A.__name__
'A!'

以下、py3kではできなくて悲しい。
>>> class A(object):
...   class __metaclass__(name, *args):
...     def __new__(name, *args):
...       print 'created'
...       name += '!' 
...       return type(name, *args)
'created'
>>> A.__name__
'A!'

>>> class A(object):
...   def __metaclass__(name, *args):
...     print 'created'
...     name += '!'
...     return type(name, *args)
'created'
>>> A.__name__
'A!'

メタクラス用メソッド

metaclsはメタクラスのクラスインスタンス(type)、nameはクラス名(Class.__name__)、basesは継承元(tuple)、dictはClass.__dict__(クラス変数やinstance method等)

  • Class定義時
    • Meta::__new__(metaclass, name, bases, dict) -> class object(type)
    • Meta::__init__(target_class, name, bases, dict) -> None
  • Instance生成時(Class call時)
    • Meta::__call__(target_class, *args, **kwds) -> instance(object)
      • # Metaで__call__を定義すると、以下2つは自動的に呼ばれなくなる*1
    • Class::__new__(target_class, *args, **kwds) -> instance(object)
    • Class::__init__(instance, *args, **kwds) -> None

*1:type::__call__の動作を上書きするってことだろう

URL/Endpoint Mapperの作り方に関して、

kayのURL mappingは以下の通り

--- root/urls.py
from werkzeug.routing import (
  Map, Rule, Submount,
  EndpointPrefix, RuleTemplate,
)

def make_rules():
  return [
    EndpointPrefix('app/', [
      Rule('/', endpoint='index'),
      Rule('/new', endpoint='new'),
      Rule('/create', endpoint='create'),
      Rule('/<string:key>/edit', endpoint='edit'),
      Rule('/<string:key>/update', endpoint='update'),
      Rule('/<string:key>/delete', endpoint='delete'),
    ]),
  ]

all_views = {
  'app/index': 'app.views.index',
  'app/new': 'app.views.new',
  'app/create': 'app.views.create',
  'app/edit': 'app.views.edit',
  'app/update': 'app.views.update',
  'app/delete': 'app.views.delete',
}


--- root/app/settings.py
APP_MOUNT_POINTS = {
  'app': '/app'
}


--- kay/app.py
...
      endpoint, values = local.url_adapter.match()
      view_func = self.views.get(endpoint, None)
      try:
        if isinstance(view_func, tuple):
          view_classname, args, kwargs = view_func
          view_cls = import_string(view_classname)
          view_func = view_cls(*args, **kwargs)
        elif isinstance(view_func, basestring):
          view_func = import_string(view_func)
        assert(callable(view_func))
...

settings.pyにAPP_MOUNT_POINTSというものをもうけて、INSTALLED_APPSとそこだけ変えればもってきたアプリが(たぶん)動作するという素敵な感じ。些細すぎるけれど、kayもmake_rulesで返すのは普通にMapで返して、iter_rulesでrule取る方が、ほんのり素敵かも、とそこだけは思った。


何となく、kayを知る以前にwerkzeugだけを使って書いてみた瞬間*1のコードも以下に。同じライブラリで作っているのだから当然だけれど、似たような感じになった。module直下の変数だと、リロード(?)されないから、make_rulesのように関数でかこっているのだろうか。

--- root/urls.py
from werkzeug.routing import *

def submap(name, key='url_map'):
  rules = __import__(name + '.urls').urls.__dict__[key]
  return include('/' + name.replace('.', '/'), name + '.', rules)

def include(submount, endpoint_prefix, rules):
  return EndpointPrefix(endpoint_prefix, [Submount(submount, list(rules.iter_rules()))])

url_map = Map([
  Rule('/', endpoint='app.views.index'),
  submap('app'),
#  submap('customer'),
])

--- root/app/urls.py
from werkzeug.routing import *

url_map = Map([EndpointPrefix('views.', [
  Rule('/', endpoint='index'),
  Rule('/new', endpoint='new'),
  Rule('/create', endpoint='create'),
  Rule('/<int:code>/edit', endpoint='edit'),
  Rule('/<int:code>/update', endpoint='update'),
  Rule('/<int:code>/delete', endpoint='delete'),
])])

--- root/main.py
from urls import url_map

@responder
def application(environ, start_response):
  request = Request(environ)
  adapter = url_map.bind_to_environ(environ)
  return adapter.dispatch(lambda endpoint, values: view(endpoint)(request, *values),
                          catch_http_exceptions=True)

def view(endpoint):
  endpoint = endpoint.split('.')
  action, view = endpoint[-1], '.'.join(endpoint[0:-1])
  return __import__(view, fromlist=[action]).__dict__[action]

*1:とき、と読む