Windows SearchのデータベースはWindows11からSQLite3を使ったものに変更されているそうです。そんなことが色々検索をしていたときにふと目に入ったので実際にどうなっているのか調べてみました。
データベースは C:\ProgramData\Microsoft\Search\Data\Applications\Windows\
にあります。Windows10だとここには Windows.edb
というファイルを中心としたファイル群があるのですが、Windows11だと Windows.db
、 Windows-gather.db
、 Windows-usn.db
というファイルがあります。
早速見て行きましょう……と言いたいところですが、私のメインPCいまだにWindows10なので、Windows11のノートPCからコピーしてきました。
sqlite3コマンドを使ってデータベースの中身を調べてみました。
テーブルの一覧
Windows.db
CatalogStorageManager SystemIndex_1 SystemIndex_1_DATA_14 SystemIndex_1_OCC_14 SystemIndex_1_DATA_35 SystemIndex_1_OCC_35 SystemIndex_1_DATA_53 SystemIndex_1_OCC_53 SystemIndex_1_DATA_74 SystemIndex_1_OCC_74 SystemIndex_1_DATA_91 SystemIndex_1_OCC_91 SystemIndex_1_DATA_112 SystemIndex_1_OCC_112 SystemIndex_1_DATA_113 SystemIndex_1_OCC_113 SystemIndex_1_DATA_114 SystemIndex_1_OCC_114 SystemIndex_1_DATA_115 SystemIndex_1_OCC_115 SystemIndex_1_DATA_116 SystemIndex_1_OCC_116 SystemIndex_1_DATA_117 SystemIndex_1_OCC_117 SystemIndex_1_DATA_118 SystemIndex_1_OCC_118 SystemIndex_1_DATA_119 SystemIndex_1_OCC_119 SystemIndex_1_DATA_120 SystemIndex_1_OCC_120 SystemIndex_1_DATA_121 SystemIndex_1_OCC_121 SystemIndex_1_DATA_122 SystemIndex_1_OCC_122 SystemIndex_1_DATA_123 SystemIndex_1_OCC_123 SystemIndex_1_DATA_124 SystemIndex_1_OCC_124 SystemIndex_1_DATA_125 SystemIndex_1_OCC_125 SystemIndex_1_DATA_126 SystemIndex_1_OCC_126 SystemIndex_1_DATA_127 SystemIndex_1_OCC_127 SystemIndex_1_DATA_128 SystemIndex_1_OCC_128 SystemIndex_1_DATA_129 SystemIndex_1_OCC_129 SystemIndex_1_Properties SystemIndex_1_PropertyStore SystemIndex_1_PropertyStore_Metadata
Windows-gather.db
SystemIndex_Gthr SystemIndex_GthrAppOwner SystemIndex_GthrPth
Windows-usn.db
ChangeTracking
各テーブルの定義
どれが何だか分かっていませんが、とりあえず全部載せておきます。
Windows.db
CREATE TABLE CatalogStorageManager ( CatalogName TEXT_TEXT(127) DEFAULT NULL, CatalogID INTEGER PRIMARY KEY, IsDeleted LONG_INTEGER DEFAULT 0, UNIQUE (CatalogName COLLATE "UNICODE_en-US" ASC, CatalogID ASC) ); CREATE TABLE SystemIndex_1_Properties ( IndexID LONG_INTEGER DEFAULT NULL, PropertyID LONG_INTEGER DEFAULT NULL, Property LONGBINARY_BLOB(2147483647) DEFAULT NULL, PRIMARY KEY (IndexID ASC, PropertyID ASC) ) WITHOUT ROWID; CREATE TABLE SystemIndex_1_PropertyStore_Metadata ( Id INTEGER PRIMARY KEY, UniqueKey TEXT NOT NULL UNIQUE, Name TEXT NOT NULL, PropertyId INTEGER NOT NULL, VariantType INTEGER NOT NULL, StorageType INTEGER NOT NULL, MaxSize INTEGER, Flags INTEGER NOT NULL ); CREATE TABLE SystemIndex_1_PropertyStore ( WorkId INTEGER NOT NULL, ColumnId INTEGER NOT NULL, Value BLOB NOT NULL, PRIMARY KEY (WorkId, ColumnId) ) WITHOUT ROWID; CREATE TABLE SystemIndex_1 ( IndexID INTEGER PRIMARY KEY, IndexDescription LONGBINARY_BLOB(5104) DEFAULT NULL ); CREATE TABLE SystemIndex_1_DATA_14 ( Partition UNSIGNEDLONG_INTEGER DEFAULT NULL, szKey LONGBINARY_BLOB(1664) DEFAULT NULL, PID UNSIGNEDSHORT_INTEGER DEFAULT NULL, WidStart UNSIGNEDLONG_INTEGER DEFAULT NULL, Data LONGBINARY_BLOB(32686) DEFAULT NULL, PRIMARY KEY (Partition ASC, szKey ASC, PID ASC, WidStart ASC) ) WITHOUT ROWID; CREATE TABLE SystemIndex_1_OCC_14 (OccID UNSIGNEDLONG_INTEGER DEFAULT NULL, OccPage LONGBINARY_BLOB(32650) DEFAULT NULL, PRIMARY KEY (OccID ASC) ) WITHOUT ROWID; CREATE TABLE SystemIndex_1_DATA_35 ( Partition UNSIGNEDLONG_INTEGER DEFAULT NULL, szKey LONGBINARY_BLOB(1664) DEFAULT NULL, PID UNSIGNEDSHORT_INTEGER DEFAULT NULL, WidStart UNSIGNEDLONG_INTEGER DEFAULT NULL, Data LONGBINARY_BLOB(32686) DEFAULT NULL, PRIMARY KEY (Partition ASC, szKey ASC, PID ASC, WidStart ASC)) WITHOUT ROWID; CREATE TABLE SystemIndex_1_OCC_35 ( OccID UNSIGNEDLONG_INTEGER DEFAULT NULL, OccPage LONGBINARY_BLOB(32650) DEFAULT NULL, PRIMARY KEY (OccID ASC)) WITHOUT ROWID; /* 以下 35の部分が53,74,91,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129 と同様に続く */
Windows-gather.db
CREATE TABLE SystemIndex_GthrAppOwner (OwnerId UNSIGNEDLONG_INTEGER NOT NULL, UserSid TEXT_TEXT(64) NOT NULL, AppUserModelId LONGTEXT_TEXT(130) NOT NULL, PRIMARY KEY (OwnerId ASC)) WITHOUT ROWID; CREATE TABLE SystemIndex_Gthr (ScopeID UNSIGNEDLONG_INTEGER NOT NULL, FileName LONGTEXT_TEXT(512) DEFAULT NULL, DocumentID UNSIGNEDLONG_INTEGER NOT NULL, SDID UNSIGNEDLONG_INTEGER DEFAULT NULL, UserData LONGBINARY_BLOB(26214400) DEFAULT NULL, AppOwnerId UNSIGNEDLONG_INTEGER DEFAULT NULL, LastModified BINARY_BLOB(8) DEFAULT NULL, RequiredSIDs BINARY_MULTI_BLOB(68) DEFAULT NULL, DeletedCount LONG_INTEGER DEFAULT NULL, TransactionFlags LONG_INTEGER DEFAULT NULL, TransactionExtendedFlags LONG_INTEGER DEFAULT NULL, CrawlNumberCrawled LONG_INTEGER DEFAULT NULL, StartAddressIdentifier UNSIGNEDSHORT_INTEGER DEFAULT NULL, Priority UNSIGNEDBYTE_INTEGER DEFAULT NULL, RunTime UNSIGNEDLONG_INTEGER DEFAULT NULL, FailureUpdateAttempts UNSIGNEDBYTE_INTEGER DEFAULT NULL, ClientID UNSIGNEDLONG_INTEGER DEFAULT NULL, LastRequestedRunTime UNSIGNEDLONG_INTEGER DEFAULT NULL, StorageProviderId LONGTEXT_TEXT(130) DEFAULT NULL, CalculatedPropertyFlags UNSIGNEDLONG_INTEGER DEFAULT NULL, PRIMARY KEY (ScopeID ASC, FileName COLLATE "UNICODE_en-US_LINGUISTIC_IGNORECASE" ASC)) WITHOUT ROWID; CREATE TABLE SystemIndex_GthrPth (Scope UNSIGNEDLONG_INTEGER DEFAULT NULL, Parent UNSIGNEDLONG_INTEGER DEFAULT NULL, Name LONGTEXT_TEXT(1000) DEFAULT NULL, PRIMARY KEY (Scope ASC)) WITHOUT ROWID;
Windows-usn.db
CREATE TABLE ChangeTracking (Client UNSIGNEDSHORT_INTEGER NOT NULL, Batch UNSIGNEDBYTE_INTEGER NOT NULL, Path LONGTEXT_TEXT(1000) DEFAULT NULL, CaseMap LONGTEXT_TEXT(1000) DEFAULT NULL, CurrentEntry BINARY_BLOB(56) DEFAULT NULL, MoveSource LONGTEXT_TEXT(1000) DEFAULT NULL, OldEntry BINARY_BLOB(56) DEFAULT NULL, MoveDestination LONGTEXT_TEXT(1000) DEFAULT NULL, RangeInformation LONGBINARY_BLOB(2147483647) DEFAULT NULL, PRIMARY KEY (Client ASC, Batch ASC, Path ASC)) WITHOUT ROWID;
プロパティ(メタデータ)に関する部分
全部くまなく解析するつもりも無いので、とりあえずパッと見一番分かりやすいファイルのプロパティに関する部分を見てみます。
SystemIndex_1_PropertyStore_Metadata
テーブル SystemIndex_1_PropertyStore_Metadata
の定義は次のようになっています。
CREATE TABLE SystemIndex_1_PropertyStore_Metadata ( Id INTEGER PRIMARY KEY, UniqueKey TEXT NOT NULL UNIQUE, Name TEXT NOT NULL, PropertyId INTEGER NOT NULL, VariantType INTEGER NOT NULL, StorageType INTEGER NOT NULL, MaxSize INTEGER, Flags INTEGER NOT NULL );
実際の中身を見てみると次のようになっています。
select * from SystemIndex_1_PropertyStore_Metadata limit 40
Id | UniqueKey | Name | PropertyId | VariantType | StorageType | MaxSize | Flags |
1 | 4703-System_Null | System.Null | 4703 | 1 | 13 | 512 | 22 |
2 | 4583-System_Photo_GainControlText | System.Photo.GainControlText | 4583 | 31 | 11 | 512 | 55 |
3 | 4224-System_Contact_BusinessHomePage | System.Contact.BusinessHomePage | 4224 | 31 | 11 | 512 | 55 |
4 | 4435-System_IsIncomplete | System.IsIncomplete | 4435 | 11 | 8 | 54 | |
5 | 4449-System_ItemSubType | System.ItemSubType | 4449 | 31 | 11 | 512 | 55 |
6 | 4412-System_History_SelectionCount | System.History.SelectionCount | 4412 | 19 | 4 | 22 | |
7 | 4413-System_History_TargetUrlHostName | System.History.TargetUrlHostName | 4413 | 31 | 11 | 512 | 55 |
8 | 4622-System_SDID | System.SDID | 4622 | 19 | 4 | 22 | |
9 | 4359-System_DRM_IsProtected | System.DRM.IsProtected | 4359 | 11 | 8 | 54 | |
10 | 4345-System_Contact_SpouseName | System.Contact.SpouseName | 4345 | 31 | 11 | 256 | 55 |
11 | 4358-System_DRM_IsDisabled | System.DRM.IsDisabled | 4358 | 11 | 8 | 54 | |
12 | 4361-System_DateAcquired | System.DateAcquired | 4361 | 64 | 12 | 8 | 54 |
13 | 4434-System_IsFolder | System.IsFolder | 4434 | 11 | 8 | 54 | |
14 | 4513-System_Message_BccAddress | System.Message.BccAddress | 4513 | 65 | 13 | 5120 | 119 |
15 | 4472-System_MIMEType | System.MIMEType | 4472 | 31 | 11 | 512 | 55 |
16 | 4430-System_IsDeleted | System.IsDeleted | 4430 | 11 | 8 | 54 | |
17 | 4397-System_FilePlaceholderStatus | System.FilePlaceholderStatus | 4397 | 19 | 4 | 22 | |
18 | 4660-System_StorageProviderFileIdentifier | System.StorageProviderFileIdentifier | 4660 | 31 | 11 | 512 | 54 |
19 | 4514-System_Message_BccName | System.Message.BccName | 4514 | 65 | 13 | 5120 | 119 |
20 | 4661-System_StorageProviderFileVersion | System.StorageProviderFileVersion | 4661 | 31 | 11 | 512 | 54 |
21 | 27F-System_Search_Rank | System.Search.Rank | 27 | 3 | 3 | 10 | |
22 | 4515-System_Message_CcAddress | System.Message.CcAddress | 4515 | 65 | 13 | 5120 | 119 |
23 | 4407-System_GPS_LatitudeRef | System.GPS.LatitudeRef | 4407 | 31 | 11 | 512 | 54 |
24 | 4370-System_Document_Contributor | System.Document.Contributor | 4370 | 65 | 13 | 10240 | 119 |
25 | 28-System_Search_HitCount | System.Search.HitCount | 28 | 3 | 3 | 6 | |
26 | 4630F-System_Search_GatherTime | System.Search.GatherTime | 4630 | 64 | 12 | 8 | 58 |
27 | 4516-System_Message_CcName | System.Message.CcName | 4516 | 65 | 13 | 5120 | 119 |
28 | 4256-System_Contact_HomeAddress1Street | System.Contact.HomeAddress1Street | 4256 | 31 | 11 | 2048 | 55 |
29 | 4624-System_Search_AccessCount | System.Search.AccessCount | 4624 | 19 | 4 | 22 | |
30 | 4440-System_ItemFolderPathDisplay | System.ItemFolderPathDisplay | 4440 | 31 | 11 | 65536 | 51 |
31 | 4253-System_Contact_HomeAddress1Locality | System.Contact.HomeAddress1Locality | 4253 | 31 | 11 | 2048 | 55 |
32 | 4241-System_Contact_EmailAddress2 | System.Contact.EmailAddress2 | 4241 | 31 | 11 | 256 | 55 |
33 | 4447-System_ItemPathDisplay | System.ItemPathDisplay | 4447 | 31 | 11 | 65536 | 51 |
34 | 4255-System_Contact_HomeAddress1Region | System.Contact.HomeAddress1Region | 4255 | 31 | 11 | 2048 | 55 |
35 | 4632-System_Search_LastIndexedTotalTime | System.Search.LastIndexedTotalTime | 4632 | 5 | 7 | 22 | |
36 | 4252-System_Contact_HomeAddress1Country | System.Contact.HomeAddress1Country | 4252 | 31 | 11 | 2048 | 55 |
37 | 4254-System_Contact_HomeAddress1PostalCode | System.Contact.HomeAddress1PostalCode | 4254 | 31 | 11 | 2048 | 55 |
38 | 4174-System_Communication_AccountName | System.Communication.AccountName | 4174 | 31 | 11 | 512 | 55 |
39 | 33-System_ItemUrl | System.ItemUrl | 33 | 31 | 11 | 65536 | 51 |
40 | 4436-System_IsRead | System.IsRead | 4436 | 11 | 8 | 54 |
select count(*) from SystemIndex_1_PropertyStore_Metadata
605
全部で605行ありました。(Windows Searchは拡張可能な仕組みなので環境によって異なると思います)
これらはプロパティの種類に関する情報でしょう。プロパティのID、名前、型といった情報が見て取れます。
Nameに書かれている文字列には見覚えがあります。Windows Search SQLで検索したときにはこのプロパティ名を列名として指定しました(Windows Search SQL 構文の概要 - Win32 apps | Microsoft Learn)。
VariantTypeはWindows開発ではおなじみのやつですね。Windows SDKのWTypes.hから抜粋。
enum VARENUM { VT_EMPTY = 0, VT_NULL = 1, VT_I2 = 2, VT_I4 = 3, VT_R4 = 4, VT_R8 = 5, VT_CY = 6, VT_DATE = 7, VT_BSTR = 8, VT_DISPATCH = 9, VT_ERROR = 10, VT_BOOL = 11, VT_VARIANT = 12, VT_UNKNOWN = 13, VT_DECIMAL = 14, VT_I1 = 16, VT_UI1 = 17, VT_UI2 = 18, VT_UI4 = 19, VT_I8 = 20, VT_UI8 = 21, VT_INT = 22, VT_UINT = 23, VT_VOID = 24, VT_HRESULT = 25, VT_PTR = 26, VT_SAFEARRAY = 27, VT_CARRAY = 28, VT_USERDEFINED = 29, VT_LPSTR = 30, VT_LPWSTR = 31, VT_RECORD = 36, VT_INT_PTR = 37, VT_UINT_PTR = 38, VT_FILETIME = 64, VT_BLOB = 65, VT_STREAM = 66, VT_STORAGE = 67, VT_STREAMED_OBJECT = 68, VT_STORED_OBJECT = 69, VT_BLOB_OBJECT = 70, VT_CF = 71, VT_CLSID = 72, VT_VERSIONED_STREAM = 73, VT_BSTR_BLOB = 0xfff, VT_VECTOR = 0x1000, VT_ARRAY = 0x2000, VT_BYREF = 0x4000, VT_RESERVED = 0x8000, VT_ILLEGAL = 0xffff, VT_ILLEGALMASKED = 0xfff, VT_TYPEMASK = 0xfff } ;
こいつをEmacs Lispから引けるようにしておくと便利でしょう。
(defvar my-vt-alist '((0 . EMPTY) (1 . NULL) (2 . I2) (3 . I4) (4 . R4) (5 . R8) (6 . CY) (7 . DATE) (8 . BSTR) (9 . DISPATCH) (10 . ERROR) (11 . BOOL) (12 . VARIANT) (13 . UNKNOWN) (14 . DECIMAL) (16 . I1) (17 . UI1) (18 . UI2) (19 . UI4) (20 . I8) (21 . UI8) (22 . INT) (23 . UINT) (24 . VOID) (25 . HRESULT) (26 . PTR) (27 . SAFEARRAY) (28 . CARRAY) (29 . USERDEFINED) (30 . LPSTR) (31 . LPWSTR) (36 . RECORD) (37 . INT_PTR) (38 . UINT_PTR) (64 . FILETIME) (65 . BLOB) (66 . STREAM) (67 . STORAGE) (68 . STREAMED_OBJECT) (69 . STORED_OBJECT) (70 . BLOB_OBJECT) (71 . CF) (72 . CLSID) (73 . VERSIONED_STREAM) (#xfff . BSTR_BLOB) (#x1000 . VECTOR) (#x2000 . ARRAY) (#x4000 . BYREF) (#x8000 . RESERVED) )) (defun my-vt-name (vt-id) (alist-get vt-id my-vt-alist)) ;; TEST: (my-vt-name 31) => LPWSTR
これをorg-modeの表にcolumn formulaとして設定すれば次のような表も簡単に生成できます。
| Id | Name | VariantType | (VT Name)| StorageType | MaxSize | Flags | |----+---------------------------------------+-------------+----------+-------------+---------+-------| | 25 | 'System.Search.HitCount' | 3 | I4 | 3 | NULL | 6 | | 26 | 'System.Search.GatherTime' | 64 | FILETIME | 12 | 8 | 58 | | 27 | 'System.Message.CcName' | 65 | BLOB | 13 | 5120 | 119 | | 28 | 'System.Contact.HomeAddress1Street' | 31 | LPWSTR | 11 | 2048 | 55 | | 29 | 'System.Search.AccessCount' | 19 | UI4 | 4 | NULL | 22 | | 30 | 'System.ItemFolderPathDisplay' | 31 | LPWSTR | 11 | 65536 | 51 | | 31 | 'System.Contact.HomeAddress1Locality' | 31 | LPWSTR | 11 | 2048 | 55 |
さらにEmacs 29で加わった組み込みSQLite機能を使ってプロパティの Id
から型情報を割り出せるようにしておきましょう。
(defvar my-wds-property-alist (let ((db (sqlite-open "Windows.db"))) (unwind-protect (sqlite-select db "select * from SystemIndex_1_PropertyStore_Metadata") (sqlite-close db)))) (defun my-wds-property-name (id) (nth 2 (assq id my-wds-property-alist))) (defun my-wds-property-vt (id) (nth 4 (assq id my-wds-property-alist))) (defun my-wds-property-vt-name (id) (my-vt-name (my-wds-property-vt id))) ;; TEST: (cons (my-wds-property-name 30) (my-wds-property-vt-name 30)) => ("System.ItemFolderPathDisplay" . LPWSTR)
後はMaxSizeは良いとして、StorageTypeやFlagsは私ではすぐには思い当たらないです。
SystemIndex_1_PropertyStore
肝心要の実際のプロパティ値はどこにあるのかというと SystemIndex_1_PropertyStore
というテーブルにあります。
定義は次の通り。
CREATE TABLE SystemIndex_1_PropertyStore ( WorkId INTEGER NOT NULL, ColumnId INTEGER NOT NULL, Value BLOB NOT NULL, PRIMARY KEY (WorkId, ColumnId) ) WITHOUT ROWID;
実際の中身ですが、こちらはBLOBが入っているせいで文字化けしたので .dump
した結果から抜粋しました。
WorkId | ColumnId | Value |
---|---|---|
1 | 13 | 1 |
1 | 17 | 6 |
1 | 26 | X'2925c18d31b6d901' |
1 | 29 | 0 |
1 | 30 | 'C:\ProgramData\Microsoft\Windows' |
1 | 33 | 'C:\ProgramData\Microsoft\Windows\スタート メニュー' |
1 | 35 | 0.0 |
1 | 39 | 'file:C:/ProgramData/Microsoft/Windows/Start Menu' |
1 | 188 | X'00ea2bc7d261d801' |
1 | 295 | 0 |
1 | 306 | 0 |
1 | 308 | X'00ea2bc7d261d801' |
1 | 342 | X'66006f006c00640065007200' |
1 | 349 | X'5600f5bf02b2cf41' |
1 | 376 | 'file' |
1 | 414 | 'Windows' |
1 | 420 | 'ファイル フォルダー' |
1 | 432 | 'スタート メニュー' |
1 | 438 | 8209 |
1 | 441 | X'3868c8f55df9d801' |
1 | 444 | X'00ea2bc7d261d801' |
1 | 445 | X'00b352c6d261d801' |
1 | 448 | X'3868c8f55df9d801' |
1 | 449 | X'e3efa68d31b6d901' |
1 | 462 | 'スタート メニュー' |
1 | 463 | 'フォルダー' |
1 | 464 | 'Windows (C:\ProgramData\Microsoft)' |
1 | 466 | 'スタート メニュー' |
1 | 470 | 'スタート メニュー' |
1 | 564 | 'スタート メニュー (C:\ProgramData\Microsoft\Windows)' |
1 | 567 | 'Directory' |
1 | 581 | 'Start Menu' |
1 | 585 | 'Start Menu' |
1 | 587 | 1887437183 |
1 | 596 | X'28126512' |
1 | 597 | X'ba0a9bc7980a1de2357c65c73ae167b6' |
2 | 13 | 1 |
2 | 17 | 6 |
2 | 26 | X'b7a34f8331b6d901' |
2 | 29 | 0 |
2 | 30 | 'C:\' |
2 | 33 | 'C:\ユーザー' |
2 | 35 | 0.109375 |
2 | 39 | 'file:C:/Users' |
以降もこの調子で1行に1つのプロパティが大量に並んでいます。
WorkId
というのがオブジェクトの識別番号のようです。
ColumnId
は先ほど見たテーブル SystemIndex_1_PropertyStore_Metadata
の Id
列に対応しています。どうも旧形式のデータベースでは一つの行が一つのファイルで、プロパティは一つ一つの列だったのだとか。SQLiteに移行したときに設計を変えたようです。プロパティは存在しないものが多いのでプロパティを列にするとnullだらけになってしまいます。それでもインタフェースとしてはそちらの方が直感的ですよね。
Value
はプロパティの値です。テーブルの定義上ではBLOB型(Value BLOB NOT NULL)となっているのですが、実際に格納されている型は様々なようで、 SystemIndex_1_PropertyStore_Metadata
の VariantType
列に書かれている形式で格納されているみたいです。
これも先ほどやったようにorg-modeの表でプロパティ名と型名を補ってみると次のようになります。
| WorkId | ColumnId | (PropertyName) | (VTName) | Value | |--------+----------+------------------------------------+----------+------------------------------------------------------| | 1 | 13 | System.IsFolder | BOOL | 1 | | 1 | 17 | System.FilePlaceholderStatus | UI4 | 6 | | 1 | 26 | System.Search.GatherTime | FILETIME | X'2925c18d31b6d901' | | 1 | 29 | System.Search.AccessCount | UI4 | 0 | | 1 | 30 | System.ItemFolderPathDisplay | LPWSTR | 'C:\ProgramData\Microsoft\Windows' | | 1 | 33 | System.ItemPathDisplay | LPWSTR | 'C:\ProgramData\Microsoft\Windows\スタート メニュー' | | 1 | 35 | System.Search.LastIndexedTotalTime | R8 | 0.0 | | 1 | 39 | System.ItemUrl | LPWSTR | 'file:C:/ProgramData/Microsoft/Windows/Start Menu' | | 1 | 188 | System.DateImported | FILETIME | X'00ea2bc7d261d801' | | 1 | 295 | System.IsAttachment | BOOL | 0 | | 1 | 306 | System.IsEncrypted | BOOL | 0 | | 1 | 308 | System.ItemDate | FILETIME | X'00ea2bc7d261d801' | | 1 | 342 | System.Kind | BLOB | X'66006f006c00640065007200' | | 1 | 349 | System.ThumbnailCacheId | UI8 | X'5600f5bf02b2cf41' | | 1 | 376 | System.Search.Store | LPWSTR | 'file' | | 1 | 414 | System.ItemFolderNameDisplay | LPWSTR | 'Windows' | | 1 | 420 | System.ItemTypeText | LPWSTR | 'ファイル フォルダー' | | 1 | 432 | System.ItemNameDisplay | LPWSTR | 'スタート メニュー' |
System.ItemUrl
が完全なパスを表しています。 System.ItemPathDisplay
は Display と付いているように一部のファイル名がローカライズされてしまうので使いづらいんですよね。以前EmacsからWindows Searchするものを作ったときに困った記憶があります。
WorkId=1はスタートメニューフォルダを表しているようですが、あまりピンとこないので何か写真のプロパティを取得してみましょう。
まずはファイルを検索。といってもこのデータベースはノートPCのものなのでインデックスの範囲は極力絞ってありますし、その範囲に写真は入っていません。とりあえずテスト画像を作ってPicturesフォルダに入れて認識させ、再度データベースをメインPCにコピー。そして次のSQLで検索。
select * from SystemIndex_1_PropertyStore where ColumnId=39 and Value LIKE 'file:C:/Users/USERNAME/Pictures/%.jpg';
WorkId | ColumnId | Value |
3338 | 39 | file:C:/Users/USERNAME/Pictures/PXL_20241025_040712150.jpg |
新しい写真の WorkId
は 3338
になったようです。これをキーに全プロパティを取得してみましょう。
select * from SystemIndex_1_PropertyStore where WorkId=3338;
……と、実際やってみましたが、これもBLOBが文字化けしてしまったのでダンプしたデータから抜粋します(ついでにプロパティ名と型名も補っておきます)。
WorkId | ColumnId | (PropertyName) | (VTName) | Value |
---|---|---|---|---|
3338 | 13 | System.IsFolder | BOOL | 0 |
3338 | 15 | System.MIMEType | LPWSTR | 'image/jpeg' |
3338 | 17 | System.FilePlaceholderStatus | UI4 | 6 |
3338 | 23 | System.GPS.LatitudeRef | LPWSTR | 'N' |
3338 | 26 | System.Search.GatherTime | FILETIME | X'b183b6f95232db01' |
3338 | 29 | System.Search.AccessCount | UI4 | 0 |
3338 | 30 | System.ItemFolderPathDisplay | LPWSTR | 'C:\ユーザー\USERNAME\ピクチャ' |
3338 | 33 | System.ItemPathDisplay | LPWSTR | 'C:\ユーザー\USERNAME\ピクチャ\PXL_20241025_040712150.jpg' |
3338 | 35 | System.Search.LastIndexedTotalTime | R8 | 0.0 |
3338 | 39 | System.ItemUrl | LPWSTR | 'file:C:/Users/USERNAME/Pictures/PXL_20241025_040712150.jpg' |
3338 | 47 | System.ItemParticipants | BLOB | X'5400610072006f000000480061006e0061006b006f00' |
3338 | 60 | System.Photo.FlashText | LPWSTR | 'フラッシュなし (強制)' |
3338 | 75 | System.Photo.ExposureTime | R8 | 0.00665899999999999998 |
3338 | 85 | System.Photo.FNumber | R8 | 2.20000000000000017 |
3338 | 93 | System.FileOwner | LPWSTR | 'MYCOMPUTER\USERNAME' |
3338 | 110 | System.Image.HorizontalSize | UI4 | 4080 |
3338 | 114 | System.Photo.MeteringModeText | LPWSTR | '中央重点測光' |
3338 | 116 | System.Image.VerticalSize | UI4 | 3072 |
3338 | 122 | System.Image.HorizontalResolution | R8 | 96.0 |
3338 | 126 | System.Image.VerticalResolution | R8 | 96.0 |
3338 | 130 | System.Image.BitDepth | UI4 | 24 |
3338 | 143 | System.Photo.WhiteBalance | UI4 | 0 |
3338 | 153 | System.Image.Dimensions | LPWSTR | '4080 x 3072' |
3338 | 172 | System.GPS.Date | FILETIME | X'0020d86ad526db01' |
3338 | 185 | System.GPS.LongitudeRef | LPWSTR | 'E' |
3338 | 188 | System.DateImported | FILETIME | X'00a2f2dd5032db01' |
3338 | 204 | System.Media.ClassPrimaryID | LPWSTR | '{6FB2E74A-B8CB-40BB-93F3-FAC5F00FA203}' |
3338 | 270 | System.Media.DlnaProfileID | BLOB | X'4a005000450047005f004c0052004700' |
3338 | 279 | System.NotUserContent | BOOL | 0 |
3338 | 287 | System.Photo.FocalLengthInFilm | UI2 | 16 |
3338 | 290 | System.Photo.OrientationText | LPWSTR | '標準' |
3338 | 294 | System.ItemAuthors | BLOB | X'5400610072006f000000480061006e0061006b006f00' |
3338 | 295 | System.IsAttachment | BOOL | 0 |
3338 | 306 | System.IsEncrypted | BOOL | 0 |
3338 | 308 | System.ItemDate | FILETIME | X'60bb495e9326db01' |
3338 | 313 | System.Photo.CameraManufacturer | LPWSTR | 'Google' |
3338 | 316 | System.Photo.CameraModel | LPWSTR | 'Pixel 7' |
3338 | 322 | System.Photo.Orientation | UI2 | 1 |
3338 | 342 | System.Kind | BLOB | X'7000690063007400750072006500' |
3338 | 349 | System.ThumbnailCacheId | UI8 | X'e7698c11b08a5156' |
3338 | 356 | System.Photo.ShutterSpeed | R8 | 7.23000000000000042 |
3338 | 360 | System.VolumeId | CLSID | X'89cae2bf542043d99ecc540d185f11f5' |
3338 | 361 | System.Photo.Aperture | R8 | 2.2799999999999998 |
3338 | 366 | System.Photo.ExposureBias | R8 | 0.0 |
3338 | 370 | System.GPS.LatitudeDecimal | R8 | 35.6262194166666645 |
3338 | 374 | System.Photo.SubjectDistance | R8 | 4294967295.0 |
3338 | 376 | System.Search.Store | LPWSTR | 'file' |
3338 | 377 | System.Photo.MeteringMode | UI2 | 2 |
3338 | 384 | System.Photo.Flash | UI1 | 16 |
3338 | 386 | System.Photo.FocalLength | R8 | 2.35000000000000008 |
3338 | 387 | System.Photo.ExposureProgram | UI4 | 2 |
3338 | 396 | System.Photo.ISOSpeed | UI2 | 470 |
3338 | 407 | System.ContentType | LPWSTR | 'image/jpeg' |
3338 | 414 | System.ItemFolderNameDisplay | LPWSTR | 'ピクチャ' |
3338 | 424 | System.Title | LPWSTR | 'Senburi near Icchodaira' |
3338 | 429 | System.Author | BLOB | X'5400610072006f000000480061006e0061006b006f00' |
3338 | 430 | System.Keywords | BLOB | X'530065006e0062007500720069000000540061006b0061006f00' |
3338 | 432 | System.ItemNameDisplay | LPWSTR | 'PXL_20241025_040712150.jpg' |
3338 | 434 | System.FileExtension | LPWSTR | '.jpg' |
3338 | 436 | System.Size | UI8 | X'bfdb4d0000000000' |
3338 | 438 | System.FileAttributes | UI4 | 32 |
3338 | 441 | System.DateModified | FILETIME | X'00e4a1b35232db01' |
3338 | 444 | System.Document.DateCreated | FILETIME | X'00a2f2dd5032db01' |
3338 | 445 | System.DateCreated | FILETIME | X'ce669cdd5032db01' |
3338 | 448 | System.Document.DateSaved | FILETIME | X'00e4a1b35232db01' |
3338 | 449 | System.DateAccessed | FILETIME | X'919582f95232db01' |
3338 | 459 | System.Photo.ProgramModeText | LPWSTR | '標準のプログラム' |
3338 | 461 | System.ApplicationName | LPWSTR | 'HDR+ 1.0.641377693zd' |
3338 | 462 | System.ItemName | LPWSTR | 'PXL_20241025_040712150.jpg' |
3338 | 463 | System.KindText | LPWSTR | 'ピクチャ' |
3338 | 464 | System.ItemFolderPathDisplayNarrow | LPWSTR | 'ピクチャ (C:\ユーザー\USERNAME)' |
3338 | 466 | System.ItemNameDisplayWithoutExtension | LPWSTR | 'PXL_20241025_040712150' |
3338 | 469 | System.GPS.LongitudeDecimal | R8 | 139.230677777777771 |
3338 | 477 | System.GPS.Latitude | BLOB | X'000000000080414000000000008042400cb81e85eb314140' |
3338 | 478 | System.Photo.MaxAperture | R8 | 2.2799999999999998 |
3338 | 482 | System.GPS.Longitude | BLOB | X'00000000006061400000000000002a40f91f85eb51384940' |
3338 | 483 | System.Photo.TagViewAggregate | BLOB | X'530065006e0062007500720069000000540061006b0061006f00' |
3338 | 491 | System.Photo.ExposureProgramText | LPWSTR | '標準' |
3338 | 493 | System.Photo.SaturationText | LPWSTR | '標準' |
3338 | 520 | System.Photo.WhiteBalanceText | LPWSTR | '自動' |
3338 | 557 | System.ComputerName | LPWSTR | 'MYCOMPUTER' |
3338 | 564 | System.ItemPathDisplayNarrow | LPWSTR | 'PXL_20241025_040712150 (C:\ユーザー\USERNAME\ピクチャ)' |
3338 | 567 | System.ItemType | LPWSTR | '.jpg' |
3338 | 572 | System.Photo.SharpnessText | LPWSTR | '標準' |
3338 | 579 | System.Photo.DateTaken | FILETIME | X'60bb495e9326db01' |
3338 | 580 | System.Photo.ContrastText | LPWSTR | '標準' |
3338 | 581 | System.FileName | LPWSTR | 'PXL_20241025_040712150.jpg' |
3338 | 584 | System.Photo.DigitalZoom | R8 | 0.0 |
3338 | 585 | System.ParsingName | LPWSTR | 'PXL_20241025_040712150.jpg' |
3338 | 587 | System.SFGAOFlags | UI4 | 1077936503 |
3338 | 596 | InvertedOnlyPids | BLOB | X'28126512' |
3338 | 597 | InvertedOnlyMD5 | BLOB | X'0ace870441760f5a3225b96131c5f0ba' |
様々なメタデータが格納されていることが分かります。
例えば System.Title
を見ると 'Senburi near Icchodaira'
となっています。この写真は先日高尾山でセンブリの花を撮ったものだったので、あらかじめexiftoolでテスト的にタイトルを付けておいたのでした。データベースの中でどうなっているのか知りたかったので。x-default(英語)、英語、日本語と三つの言語でタイトルを付けておいたのですが、x-default(英語)のみが収集されたみたいです。
System.Author
を見るとBLOB型で X'5400610072006f000000480061006e0061006b006f00' となっています。BLOBと言っていますがどう見てもWSTR(UTF-16LE)ですよね。次のようなEmacs Lispを使えばデコード出来ます。
(defun my-decode-wstr (hexstr) (decode-coding-string (apply 'unibyte-string (cl-loop for i below (length hexstr) by 2 collect (string-to-number (substring hexstr i (+ i 2)) 16))) 'utf-16le))
実際にデコードすると次のようになります。
(my-decode-wstr "5400610072006f000000480061006e0061006b006f00")
"Taro\0Hanako"
あらかじめexiftoolで作者(dc:creator)をTaroとHanakoの二名にしておいたのですが、NUL文字区切りで記録されていました。なるほどだからBLOBにしているのかもしれませんね。(追記:SQLiteは文字列にNUL文字を含めることができるそうです。一部の文字列関数が最初のNUL文字までしか認識しないといった制限はあるみたいですが。なので必ずしもNULが入っているからといってBLOBにする必要は無かったかもしれません。参考:NUL Characters In Strings - sqlite.org)
System.Keywords
も同様にデコードしてみましょう。
(my-decode-wstr "530065006e0062007500720069000000540061006b0061006f00")
"Senburi\0Takao"
こちらもSenburiとTakaoの二つをdc:subjectに設定しておいたのですが、このようにNUL文字区切りで記録されていました。
GPS情報なんかも格納されているので是非場所を割り出してみてください。何かプレゼントでも隠しておけたら面白かったのですが(笑) って、Decimalバージョンもあるのね。
というわけで、最低限プロパティの部分がどのように格納されているかは分かったと思います。
org-mode文書にSQLiteのクエリと実行結果を埋め込む方法
ちなみに今回の記事はorg-modeのSQLiteソースコードブロックを使用して書きました。詳しい書き方については次のページをご覧下さい。
SQLite Source Code Blocks in Org Mode
上の方では実際に次のように書いていました。
#+begin_src sqlite :db Windows.db :header :exports both :eval no-export select * from SystemIndex_1_PropertyStore_Metadata limit 40 #+end_src | Id | UniqueKey | Name | PropertyId | VariantType | StorageType | MaxSize | Flags | | 1 | 4703-System_Null | System.Null | 4703 | 1 | 13 | 512 | 22 | ...略...
:header
を付けると見出しが付きます(デフォルトでは付きません)。
デフォルトはコードのみのエクスポートだったので、 :exports both
を指定しました。
エクスポート時に勝手に評価しないように :eval no-export
を指定しました。
Emacs Lispからsqliteにアクセスする
また上の方でも少し使いましたが、Emacs 29から組み込みのsqlite関数が入っています。
Database (GNU Emacs Lisp Reference Manual)
使用例:
(let ((db (sqlite-open "~/test-sqlite.db"))) (unwind-protect (progn ;; ファイル情報テーブルを作る (sqlite-execute db "create table if not exists files ( id integer primary key, path text not null unique, mod_time real not null, size integer not null)") ;; カレントディレクトリにある全ファイルのフルパス、更新日時、サイズを挿入する ;; (本当は一文でやった方が速いみたい values (?,?,?),(?,?,?)...) (cl-loop for (file . attrs) in (directory-files-and-attributes "." t) do (sqlite-execute db "insert or replace into files (path,mod_time,size) values (?,?,?)" (list file (float-time (file-attribute-modification-time attrs)) (file-attribute-size attrs)))) ;; 更新日時降順に ((パス 更新日時)...) を取得する (sqlite-execute db "select path,datetime(mod_time,'unixepoch','localtime') from files where mod_time >= ? order by mod_time desc" ;; 過去30日分取得 (list (- (float-time) (* 30 24 60 60))))) ;; エラーが出てもちゃんと閉じるよ(しなくてもGCのタイミングで閉じるけど) (sqlite-close db)))
……というわけで色々やってみましたが、終わってみるとこれはEmacsのSQLite関連機能を学ぶためのちょうど良いチュートリアルでした。