2015-12-29

solr4tcl v0.1: A lightweight Tcl client interface to Apache Solr

Homepage


solr4tcl

About

A lightweight Tcl client interface to Apache Solr. The library consists of a single Tcl Module file.

solr4tcl is using Tcl built-in package http to send request to Aapache Solr.

This library requires package tdom.

Interface

The library has 1 TclOO class, Solr_Request.

Provide below things:
  • A simple search interface
  • A simple interface to add, delete and update documents to the index (using XML Formatted Index Updates)
  • Uploading file data by using Apache Tika

一些說明


經過閱讀 Apache Solr 的文件以後,使用 Tcl http package 送出 request 並且得到結果的記錄。應該是 work 的,起碼沒有出現網頁找不到或者是錯誤回應。

更新 2016/01/02:
雖然都是傳回 OK,但是 ping method 填的參數不是完全正確,我試著更新一份正確的。

2015-12-28

Apache Solr test code

solr4tcl-0.1.tm

# solr4tcl --
#

package require Tcl 8.6
package require TclOO
package require http
package require tdom

package provide solr4tcl 0.1


#
# Solr_Request class: handle send request to Apache Solr
#
oo::class create Solr_Request {
    variable server
    variable path
    variable solr_writer

    constructor {SERVER} {
        set server $SERVER
        set path ""

        set solr_writer "xml"
    }

    destructor {
    }

    method setDocumentPath {PATH} {
        set path $PATH
    }

    #
    # support type: xml, json and cvs
    #
    method setSolrWriter {WRITER} {
        set solr_writer $WRITER
    }

    method send_request {url method {headers ""} {data ""}} {
        variable tok

        if {[string length $data] < 1} {
            if {[catch {set tok [http::geturl $url -method $method \
                -headers $headers]}]} {
                return "error"
            }
        } else {
            if {[catch {set tok [http::geturl $url -method $method \
                -headers $headers -query $data]}]} {
                return "error"
            }
        }

        if {[string compare -nocase $method "HEAD"] == 1} {
            set res [http::data $tok]
        } else {
            set res [http::status $tok]
        }

        http::cleanup $tok
        return $res
    }

    #
    # Call the /admin/ping servlet
    #
    method ping {} {
        set myurl "$server/solr"

        if {[string length $path] < 1} {
            append myurl "/admin/ping"
        } else {
            append myurl "/$path/admin/ping"
        }

        set headerl ""
        set res [my send_request $myurl HEAD $headerl]
        return $res
    }

    #
    # Simple Search interface
    # params is a list, give this funcition name-value pair parameter
    #
    method search {query {offset 0} {limit 10} {params ""}} {
        set myurl "$server/solr"

        if {[string length $path] < 1} {
            append myurl "/select"
        } else {
            append myurl "/$path/select"
        }

        lappend params q $query
        lappend params wt $solr_writer
        lappend params start $offset
        lappend params rows $limit
        set querystring [http::formatQuery {*}$params]

        #
        # The return data format is defined by wt, $solr_writer setting.
        #
        set headerl [list Content-Type "application/x-www-form-urlencoded; charset=UTF-8"]
        set res [my send_request $myurl POST $headerl $querystring]

        return $res
    }

    #
    # parameters - a list include key-value pair
    #
    method add {parameters {OVERWRITE true} {BOOST "1.0"} {COMMIT true}} {
        # Try to build our XML document
        set doc [dom createDocument add]

        set root [$doc documentElement]
        $root setAttribute overwrite $OVERWRITE

        set docnode [$doc createElement doc]
        $docnode setAttribute boost $BOOST
        $root appendChild $docnode

        foreach {key value} $parameters {
            set node [$doc createElement field]
            $node setAttribute name $key
            $node appendChild [$doc createTextNode $value]
            $docnode appendChild $node
        }

        set myaddString [$root asXML]
        set myurl "$server/solr"

        set params [list commit $COMMIT]
        set querystring [http::formatQuery {*}$params]

        if {[string length $path] < 1} {
            append myurl "/update?$querystring"
        } else {
            append myurl "/$path/update?$querystring"
        }

        set headerl [list Content-Type "text/xml; charset=UTF-8"]
        set res [my send_request $myurl POST $headerl $myaddString]

        return $res
    }

    #
    # xmldata - xml data string want to add
    #
    method addData {xmldata {COMMIT true}} {
        set myurl "$server/solr"

        set params [list commit $COMMIT]
        set querystring [http::formatQuery {*}$params]

        if {[string length $path] < 1} {
            append myurl "/update?$querystring"
        } else {
            append myurl "/$path/update?$querystring"
        }

        set headerl [list Content-Type "text/xml; charset=UTF-8"]
        set res [my send_request $myurl POST $headerl $xmldata]

        return $res
    }

    #
    # The <commit>  operation writes all documents loaded since the last
    # commit to one or more segment files on the disk
    #
    method commit {{WAITSEARCHER true} {EXPUNGEDELETES false}} {
        set mycommitString "<commit waitSearcher=\"$WAITSEARCHER\" expungeDeletes=\"$EXPUNGEDELETES\"/>"
        set myurl "$server/solr"

        if {[string length $path] < 1} {
            append myurl "/update"
        } else {
            append myurl "/$path/update"
        }

        set headerl [list Content-Type "text/xml; charset=UTF-8"]
        set res [my send_request $myurl POST $headerl $mycommitString]

        return $res
    }

    #
    # The <optimize> operation requests Solr to merge internal data structures
    # in order to improve search performance.
    #
    method optimize {{WAITSEARCHER true} {MAXSegments 1}} {
        set myoptimizeString "<optimize waitSearcher=\"$WAITSEARCHER\" maxSegments=\"$MAXSegments\"/>"
        set myurl "$server/solr"

        if {[string length $path] < 1} {
            append myurl "/update"
        } else {
            append myurl "/$path/update"
        }

        set headerl [list Content-Type "text/xml; charset=UTF-8"]
        set res [my send_request $myurl POST $headerl $myoptimizeString]

        return $res
    }

    #
    #  "Delete by ID" deletes the document with the specified ID
    #
    method deleteById {ID {COMMIT true}} {
        set mydeleteString "<delete><id>$ID</id></delete>"
        set myurl "$server/solr"

        set params [list commit $COMMIT]
        set querystring [http::formatQuery {*}$params]

        if {[string length $path] < 1} {
            append myurl "/update?$querystring"
        } else {
            append myurl "/$path/update?$querystring"
        }

        set headerl [list Content-Type "text/xml; charset=UTF-8"]
        set res [my send_request $myurl POST $headerl $mydeleteString]

        return $res
    }

    #
    #  "Delete by Query" deletes all documents matching a specified query
    #
    method deleteByQuery {QUERY {COMMIT true}} {
        set mydeleteString "<delete><query>$QUERY</query></delete>"
        set myurl "$server/solr"

        set params [list commit $COMMIT]
        set querystring [http::formatQuery {*}$params]

        if {[string length $path] < 1} {
            append myurl "/update?$querystring"
        } else {
            append myurl "/$path/update?$querystring"
        }

        set headerl [list Content-Type "text/xml; charset=UTF-8"]
        set res [my send_request $myurl POST $headerl $mydeleteString]

        return $res
    }

    #
    #  Uploading Data by using Apache Tika
    #
    method upload {fileContent {FILENAME ""} {COMMIT true} {ExtractOnly false} {params ""}} {
        set myurl "$server/solr"

        lappend params commit $COMMIT extractOnly $ExtractOnly

        if {[string length $FILENAME] > 1} {
            lappend params "resource.name" $FILENAME
        }

        set querystring [http::formatQuery {*}$params]

        if {[string length $path] < 1} {
            append myurl "/update/extract?$querystring"
        } else {
            append myurl "/$path/update/extract?$querystring"
        }

        set headerl [list Content-Type "text/xml; charset=UTF-8"]
        set res [my send_request $myurl POST $headerl $fileContent]

        return $res
    }
}



Test code:

#!/usr/bin/tclsh

package require solr4tcl

set solrresquest [Solr_Request new "http://localhost:8983"]
$solrresquest setDocumentPath gettingstarted

# support xml, json or csv
$solrresquest setSolrWriter xml

set res [$solrresquest ping]
if {[string compare -nocase $res "ok"]!=0} {
    puts "Apache Solr server returns not OK, close."
    exit
}

set res [$solrresquest search "foundation"]
puts "Search result:"
puts $res

set parameters [list authors "Patrick Eagar" subject "Sports" dd "796.35" \
                     isdn "0002166313" yearpub "1982" publisher "Collins"]
set res [$solrresquest add $parameters true]
puts $res

set res [$solrresquest commit]
puts $res

set res [$solrresquest optimize]
puts $res

set res [$solrresquest deleteById "0002166313"]
puts $res

set res [$solrresquest deleteByQuery "publisher:Collins"]
puts $res

#set size [file size "./solr-word.pdf"] 
#set fd [open "./solr-word.pdf" {RDWR BINARY}]  
#fconfigure $fd -blocking 1 -encoding binary -translation binary 
#set data [read $fd $size]  
#close $fd  
#set res [$solrresquest upload $data "solr-word.pdf"]
#puts $res

set res [$solrresquest search "pdf"]
puts "Search result:"
puts $res



Apache Solr 沒有像 Apache CouchDB 一樣,有將 REST API 做一個整理表,所以我只是對 search 的部份做一個簡單的 search method,然後確定有正確的回應回來(而不是無法連線,或者是傳回來一個網頁不存在的 404 錯誤網頁,如果有執行 Solr 的 Solr Quick Start)。

更新 2015/12/29:
加入上傳檔案和 index 處理的部份

更新 2016/01/02:
更新 method ping 的參數

2015-12-26

PostgreSQL extension: hstore

PostgreSQL: hstore


厲害的地方在於,不只可以放一個 key-value pair,而是可以在一個 column 上放一個以上的 key-value pair。

在 Windows 平台上測試最近安裝的  PostgreSQL 9.4.5 是否有支援 hstore:

package require tdbc::postgres
tdbc::postgres::connection create db -user postgres -password postgres -port 5432

set statement [db prepare {
     CREATE EXTENSION hstore
}]

$statement foreach row {
 puts $row
}

$statement close
db close


得到 extension "hstore" already exists 的答案。

更新:列出目前系統上的 extension name:

package require tdbc::postgres
tdbc::postgres::connection create db -user postgres -password postgres -port 5432

set statement [db prepare {
     select extname from pg_extension
}]

$statement foreach row {
 puts $row
}

$statement close
db close

2015-12-24

SSDB 與 retcl

SSDB

Features

  • An alternative to Redis, 100x Redis
  • LevelDB client-server support, written in C/C++
  • Redis API compatible, Redis clients are supported
  • Designed to store collection data, such as list, hash, zset...
  • Client API supports including C++, PHP, Python, Java, Go
  • Persistent queue service
  • Replication(master-slave), load balance

retcl: Tcl client library for Redis


看完了 SSDB 寫的 features,所以我可以拿一個 Redis 的 client 來連線並且測試囉?閱讀 SSDB 的文件,SSDB 的 port 是 8888。

retcl create r 127.0.0.1 8888

接下來使用 retcl 的範例進行一些測試,真的可以直接使用(除了 Publish / Subscribe and callbacks, Handling errors 我沒試,另外 retcl  Commands pipelining 的範例要先 set i 0,總之要先設定 i 的值),SSDB 有些 command 回來的反應和說明不太一致(例如 info 只傳回 OK),不過大體上 SSDB 確實是 Redis API compatible,也就是如果 Redis client 寫的彈性一點,理論上就可以無痛的拿來 SSDB 這邊使用。

PS. 不要嘗試在 Windows 平台使用 SSDB,雖然在網路上可以找到 binary files,但是跟作者網站上講的一樣,不建議在 Windows 平台上使用。

couchdbtcl: rename oauth-1.0.tm

couchdbtcl


我沒注意到 tcllib 中已經有 package 名稱是 oauth,雖然我測試的時候可以正確執行,不過為了避免套件名稱衝突,所以 oauth-1.0.tm 被我改名為 myoauth-1.0.tm,提供的套件從 oauth 變成 myoauth。

2015-12-23

tclusb v0.3: remove hotplug command

Tclusb


因為 hotplug 已經失效很久了(libusb 的 hotplug 是正常的,但是我不知道怎麼修正 Tcl channel notify 失效的問題),經過思考,我決定保持版本在 v0.3,移掉 hotplug 的部份,然後重新整理一次目前 Tclusb 的網頁。雖然這樣 Tclusb 就只剩下簡單的 list devices 功能。

我會再整理並且 review code,確定我有移乾淨,然後把 LICENSE 改成 2-clause BSD license。

2015-12-23 更新:
整理結束。

2015-12-22

Converting Characters (format command)

資料來源:Tcler's wiki


使用 format command 來做到 chr() function 的效果。
interp alias {} chr {} format %c

測試:
set a admin[chr 48]

很有趣的寫法。

2015-12-15

OpenACS 5.9.0 is released

ANNOUNCE: OpenACS 5.9.0 final released

This release contains many security and performance improvements as well as new functionality. The new release differs from OpenACS 5.8.1 in the following points:
- PostgreSQL enhancements and cleanups to improve performance and maintainability of the basic data model
- Greatly improved HTML validity and protection against XSS attacks.
- Improved theming support to create responsive interfaces.
- Various new functionalities to ease debugging and to improve performance. 



看起來更新幅度還蠻大的版本。

更新:
就閱讀的資料,目前支援 AOLserver 與 NaviServer。但是我沒有時間測試,只是對 OpenACS ChangeLog 的 use tcl8.5 idioms 和一些變動有興趣,我想這應該是一個已經全部使用 Tcl 8.5 的版本。

Redland Tcl support is dead

RDF at Tcler's Wiki

根據我搜尋網路的結果,Redland 對於 Tcl binding 已經不再支援(也已經 drop 掉 C# 和 Java),避免有人跟我一樣傻傻的使用各個關鍵字搜尋半天,所以我已經更新了 Tcler's Wiki 的資料。

目前 Tcl 惟一還可能可以用的 RDF Tool 是 XOTcl 的 xoRDF,不過我對 XOTcl 並不是很熟悉,所以也沒有去嘗試使用。

如果要自己使用 Tcl parse RDF/XML 檔案,可能需要使用 XML parer 去解析才行,但是根據 Problems of the RDF syntax 的說法,簡單的資料或許還可以,但是複雜的資料應該會囧。

我本來想研究一下 RDF 的狀況,如果我接下來的搜尋無法取得更有用的資料,那麼 RDF 的研究工作就到這裡了。

更新:
Google 搜尋會跑出來 RDF is dead 基本上有點…… 下面是另外一個參考文章:
Why Microdata, Not RDF, Will Power the Semantic Web

(接在上面連結以後的更新:RDFa Lite 和 Microdata 是二個直接競爭的標準,然後加上 JSON-LD,就是目前可以讓 web page 具備 structured data 特性的三個主要競爭者,不過三個都距離我目前專注的東西太遠了,跳過)

所以我想除了有一些特別領域的需求,大家可以放生 RDF 了。

2015-12-12

tcljsonnet v0.1

檔案放置網頁


tcljsonnet - Jsonnet wrapper for Tcl

About

Jsonnet is a domain specific configuration language that helps you define JSON data. Jsonnet lets you compute fragments of JSON within the structure, bringing the same benefit to structured data that templating languages bring to plain text.

For additional information on Jsonnet see
http://google.github.io/jsonnet/doc/


This package is a Jsonnet wrapper for Tcl.

一些說明


因為覺得 Jsonnet 是個很有趣的工具,所以今天寫了一個很簡單的 Tcl interface 來使用。