--- /dev/null
+require 'rubygems'
+
+SPEC = Gem::Specification.new do |s|
+ s.name = 'Hdboo'
+ s.version = '0.0.1'
+ s.author = 'Iwauo Tajima'
+ s.email = 'iwauo.tajima@gmail.com'
+ s.homepage = 'http://sourceforge.jp/projects/hdboo/'
+ s.platform = Gem::Platform::RUBY
+ s.summary = 'An HTTP, HTML, relational DataBase, ' +
+ 'and Object-Oriented architecture based web framework.'
+ s.files = Dir.glob('{lib,docs,test}/**/*')
+ s.require_path = 'lib'
+ s.extensions = ['lib/hdboo/extconf.rb']
+ s.test_files = Dir.glob('test/tc_*.rb')
+ s.has_rdoc = false
+end
--- /dev/null
+require 'hdboo/base'
+require 'hdboo/sql'
+require 'hdboo/rest'
--- /dev/null
+
+SHELL = /bin/sh
+
+#### Start of system configuration section. ####
+
+srcdir = .
+topdir = /usr/lib/ruby/1.8/arm-linux-eabi
+hdrdir = $(topdir)
+VPATH = $(srcdir):$(topdir):$(hdrdir)
+exec_prefix = $(prefix)
+prefix = $(DESTDIR)/usr
+sharedstatedir = $(prefix)/com
+mandir = $(prefix)/share/man
+psdir = $(docdir)
+oldincludedir = $(DESTDIR)/usr/include
+localedir = $(datarootdir)/locale
+bindir = $(exec_prefix)/bin
+libexecdir = $(prefix)/lib/ruby1.8
+sitedir = $(DESTDIR)/usr/local/lib/site_ruby
+htmldir = $(docdir)
+vendorarchdir = $(vendorlibdir)/$(sitearch)
+includedir = $(prefix)/include
+infodir = $(prefix)/share/info
+vendorlibdir = $(vendordir)/$(ruby_version)
+sysconfdir = $(DESTDIR)/etc
+libdir = $(exec_prefix)/lib
+sbindir = $(exec_prefix)/sbin
+rubylibdir = $(libdir)/ruby/$(ruby_version)
+docdir = $(datarootdir)/doc/$(PACKAGE)
+dvidir = $(docdir)
+vendordir = $(libdir)/ruby/vendor_ruby
+datarootdir = $(prefix)/share
+pdfdir = $(docdir)
+archdir = $(rubylibdir)/$(arch)
+sitearchdir = $(sitelibdir)/$(sitearch)
+datadir = $(datarootdir)
+localstatedir = $(DESTDIR)/var
+sitelibdir = $(sitedir)/$(ruby_version)
+
+CC = cc
+LIBRUBY = $(LIBRUBY_SO)
+LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a
+LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME)
+LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static
+
+RUBY_EXTCONF_H =
+CFLAGS = -fPIC -fno-strict-aliasing -g -g -O2 -fPIC $(cflags)
+INCFLAGS = -I. -I. -I/usr/lib/ruby/1.8/arm-linux-eabi -I.
+DEFS = -D_FILE_OFFSET_BITS=64
+CPPFLAGS = -DHAVE_MAGIC_H -D_FILE_OFFSET_BITS=64
+CXXFLAGS = $(CFLAGS)
+ldflags = -L. -Wl,-Bsymbolic-functions -rdynamic -Wl,-export-dynamic
+dldflags =
+archflag =
+DLDFLAGS = $(ldflags) $(dldflags) $(archflag)
+LDSHARED = $(CC) -shared
+AR = ar
+EXEEXT =
+
+RUBY_INSTALL_NAME = ruby1.8
+RUBY_SO_NAME = ruby1.8
+arch = arm-linux-eabi
+sitearch = arm-linux-eabi
+ruby_version = 1.8
+ruby = /usr/bin/ruby1.8
+RUBY = $(ruby)
+RM = rm -f
+MAKEDIRS = mkdir -p
+INSTALL = /usr/bin/install -c
+INSTALL_PROG = $(INSTALL) -m 0755
+INSTALL_DATA = $(INSTALL) -m 644
+COPY = cp
+
+#### End of system configuration section. ####
+
+preload =
+
+libpath = . $(libdir)
+LIBPATH = -L. -L$(libdir)
+DEFFILE =
+
+CLEANFILES = mkmf.log
+DISTCLEANFILES =
+
+extout =
+extout_prefix =
+target_prefix = /hdboo
+LOCAL_LIBS =
+LIBS = $(LIBRUBYARG_SHARED) -lmagic -lpthread -ldl -lcrypt -lm -lc
+SRCS = _search.c
+OBJS = _search.o
+TARGET = _search
+DLLIB = $(TARGET).so
+EXTSTATIC =
+STATIC_LIB =
+
+BINDIR = $(bindir)
+RUBYCOMMONDIR = $(sitedir)$(target_prefix)
+RUBYLIBDIR = $(sitelibdir)$(target_prefix)
+RUBYARCHDIR = $(sitearchdir)$(target_prefix)
+
+TARGET_SO = $(DLLIB)
+CLEANLIBS = $(TARGET).so $(TARGET).il? $(TARGET).tds $(TARGET).map
+CLEANOBJS = *.o *.a *.s[ol] *.pdb *.exp *.bak
+
+all: $(DLLIB)
+static: $(STATIC_LIB)
+
+clean:
+ @-$(RM) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES)
+
+distclean: clean
+ @-$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log
+ @-$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES)
+
+realclean: distclean
+install: install-so install-rb
+
+install-so: $(RUBYARCHDIR)
+install-so: $(RUBYARCHDIR)/$(DLLIB)
+$(RUBYARCHDIR)/$(DLLIB): $(DLLIB)
+ $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
+install-rb: pre-install-rb install-rb-default
+install-rb-default: pre-install-rb-default
+pre-install-rb: Makefile
+pre-install-rb-default: Makefile
+$(RUBYARCHDIR):
+ $(MAKEDIRS) $@
+
+site-install: site-install-so site-install-rb
+site-install-so: install-so
+site-install-rb: install-rb
+
+.SUFFIXES: .c .m .cc .cxx .cpp .C .o
+
+.cc.o:
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
+
+.cxx.o:
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
+
+.cpp.o:
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
+
+.C.o:
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
+
+.c.o:
+ $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) -c $<
+
+$(DLLIB): $(OBJS)
+ @-$(RM) $@
+ $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS)
+
+
+
+$(OBJS): ruby.h defines.h
--- /dev/null
+#include <ruby.h>
+#include <rubyio.h>
+#include <magic.h>
+
+static magic_t magic = NULL;
+
+static const char*
+_mime_type(const int file_desc) {
+ magic = magic_open(MAGIC_MIME);
+ magic_load(magic, NULL);
+ return magic_descriptor(magic, dup(file_desc));
+}
+
+static VALUE
+rb_mime_type(VALUE self) {
+ const int file_desc = fileno(RFILE(self)->fptr->f);
+ const char* result = _mime_type(file_desc);
+ if (result == NULL) {
+ rb_raise(rb_eIOError, magic_error(magic));
+ }
+ return rb_str_new2(result);
+}
+
+void
+Init__search() {
+ rb_define_method(rb_cFile, "mime_type", rb_mime_type, 0);
+}
--- /dev/null
+require 'monitor'
+require 'jcode'
+require 'stringio'
+require 'base64'
+require 'erb'
+
+module Hdboo
+ class UserError < StandardError
+ attr_reader :messages
+ def initialize(messages)
+ @messages = messages.is_a?(Hash)\
+ ? messages\
+ : {'*' => [messages.to_s]}
+ end
+
+ def message
+ messages.map {|k,v| "#{k}: #{v.join(', ')}"}.join("\n")
+ end
+
+ def self.exception(messages)
+ new(messages)
+ end
+ end
+ class InvalidArgument < UserError; end
+ class NoDataFound < UserError; end
+ class DataConflict < UserError; end
+
+ class ::ERB
+ attr_accessor :template_src
+ end
+
+ class TemplateContext
+ include ERB::Util
+ @base_url = nil
+
+ def self.base_url=(url)
+ @base_url = url
+ end
+
+ def self.base_url
+ @base_url
+ end
+
+ def base_url
+ TemplateContext.base_url
+ end
+
+ def self.pre_process(src)
+ overwrite_base_url(src)
+ end
+
+ def self.overwrite_base_url(src)
+ @base_url.nil? \
+ ? src \
+ : src.sub(/(\<base\s+href\s*=\s*['"])(.*)(['"])/) do
+ $1 + @base_url + $3
+ end
+ end
+
+ def get_binding
+ binding
+ end
+
+ def base64(data)
+ Base64.encode64(data).gsub(/\n/, '')
+ end
+ alias_method :b, :base64
+
+ def multi_line_markup(str)
+ h(str).gsub(/\n/, '<br/>')
+ end
+ alias_method :m, :multi_line_markup
+ end
+
+ def template(*args, &block)
+ Hdboo.template(*args, &block)
+ end
+
+ def self.template(template_name, &block)
+ template = resource(template_name) do |src|
+ src = TemplateContext.pre_process(src)
+ erb = ERB.new(src)
+ erb.template_src = src
+ erb.filename = template_name
+ erb
+ end
+ context = TemplateContext.new
+ context.instance_eval(&block) if block_given?
+ template.result(context.get_binding)
+ end
+
+ def resource_exist?(*args)
+ Hdboo.resource_exist?(*args)
+ end
+
+ def self.resource_exist?(resource_name)
+ search_in_lib_path(resource_name) do
+ return true
+ end
+ false
+ end
+
+ def resource(*args)
+ Hdboo.resource(*args)
+ end
+
+ def self.resource(resource_name, reload=false, &block)
+ if !reload && @resource_cache.has_key?(resource_name)
+ return @resource_cache[resource_name]
+ end
+ @resource_cache.synchronize do
+ break if !reload && @resource_cache.has_key?(resource_name)
+ result = resource_nocache(resource_name, &block)
+ @resource_cache[resource_name] = result
+ end
+ @resource_cache[resource_name]
+ end
+ @resource_cache = {}
+ @resource_cache.extend(MonitorMixin)
+
+ def resource_nocache(*args)
+ Hdboo.resource_nocache(*args)
+ end
+
+ def self.resource_nocache(resource_name, &block)
+ result = nil
+ found = false
+ search_in_lib_path(resource_name) do |path|
+ result = open(path).read
+ found = true
+ break
+ end
+ unless found
+ raise RuntimeError, "no such resource: #{resource_name}", caller[2..-1]
+ end
+ if block_given?
+ result = block.call(result)
+ end
+ result
+ end
+
+ def self.search_in_lib_path(resource_name, &block)
+ rel_path = File.join(*resource_name.split('/'))
+ $:.each do |lib_path|
+ path = File.join(lib_path, rel_path)
+ if File.exists?(path)
+ block.call(path)
+ end
+ end
+ end
+
+ def escape_html(hash)
+#TODO
+ require 'cgi'
+ result = {}
+ hash.each do |key, val|
+ result[key.to_s] = CGI.escapeHTML(val.to_s)
+ if (val == true || val == 1)
+ result[key.to_s.sub(/^is_/, '') + '_is_true'] = val
+ end
+ end
+ result
+ end
+ module_function :escape_html
+
+ def uncamel(str)
+ words = []
+ str[0] = str[0..0].upcase
+ str.scan(UNCAMEL_REGEX) do |match|
+ word = match[0]
+ word = (word.upcase == word) ? word : word.downcase
+ words << word
+ end
+ words.join('_')
+ end
+ UNCAMEL_REGEX = /([A-Z]{2,}|[A-Z][a-z0-9]+)/
+ module_function :uncamel
+
+ def singleton_eval(clazz, code=nil, &block)
+ clazz.module_eval do
+ singleton_class = (class << self; self; end)
+ if code
+ singleton_class.module_eval(code)
+ else
+ singleton_class.module_eval(&block)
+ end
+ end
+ end
+ module_function :singleton_eval
+
+ def normalize_key(hash)
+ result = {}
+ hash.each do |k, v|
+ result[k.to_sym] = v
+ end
+ result
+ end
+ module_function :normalize_key
+
+ class HashAsObject < Hash
+ def initialize(kargs={})
+ replace(Hdboo.normalize_key(kargs))
+ end
+
+ def method_missing(name)
+ org_arg = name
+ returns_bool = (name.to_s =~ /^(.+?)\?$/)
+ if !has_key?(name) && returns_bool
+ name = has_key?($1) ? $1 : ('is_' + $1)
+ end
+
+ unless has_key?(name)
+ raise NoMethodError, "undefined method: #{org_arg}", caller
+ end
+
+ result = self[name]
+ if returns_bool
+ return (result == 0) ? false : result
+ end
+ return result
+ end
+
+ def [](key)
+ super(key.to_sym)
+ end
+
+ def []=(key, value)
+ super(key.to_sym, value)
+ end
+
+ def has_key?(key)
+ super(key.to_sym)
+ end
+ end
+
+ module Validatable
+ def validators_for(key)
+ raise "#{self.class.name}#validators_for(key)? must be implemented."
+ end
+
+ def invalid?(values=nil)
+ values = values || self.to_hash
+ result = {}
+ values.each do |key, value|
+ key = key.to_sym
+ validators = Validator::ValidatorList.new(validators_for(key))
+ if (errors = validators.invalid?(value, key))
+ result.merge!(errors)
+ end
+ end
+ result.empty? ? nil\
+ : result
+ end
+
+ def validate(values=nil)
+ if errors = invalid?(values)
+ raise InvalidArgument, errors
+ end
+ return self
+ end
+ end
+
+ module Validator
+ def validate(val, key='*')
+ if errors = invalid?(val, key)
+ raise InvalidArgument, errors
+ end
+ end
+
+ def pass?(val)
+ raise "#{self.class.name}#pass? must be implemented."
+ end
+
+ def invalid?(val, key='*')
+ key = key.to_sym
+ pass?(val) ? nil\
+ : {key=>[message(val)]}
+ end
+
+ def message
+ raise "#{self.class.name}#message must be implemented."
+ end
+
+ class ValidatorList < Array
+ include Validator
+ def initialize(validators=[])
+ replace(validators)
+ end
+
+ def invalid?(val, key='*')
+ errors = []
+ key = key.to_sym
+ self.each do |validator|
+ errors << validator.message(val) unless validator.pass?(val)
+ end
+ errors.empty? ? nil \
+ : {key => errors}
+ end
+
+ def pass?
+ self.all?{|all| all.pass?(vall)}
+ end
+
+ def message(val)
+ errors = invalid?(val)
+ errors ? errors.join(', ')\
+ : ''
+ end
+ end
+
+ class NotNil
+ include Validator
+ def pass?(val)
+ !val.nil?
+ end
+
+ def message(val)
+ "must not be nil."
+ end
+ end
+
+ class IsA
+ include Validator
+ def initialize(clazz)
+ @clazz = clazz
+ end
+
+ def pass?(val)
+ val.nil? || val.is_a?(@clazz)
+ end
+
+ def message(val)
+ "must be instance of #{@clazz.name}, but of #{val.class.name}."
+ end
+ end
+
+ class NotBlank
+ include Validator
+ def pass?(val)
+ val.nil? || val.to_s.length > 0
+ end
+
+ def message(val)
+ "must not be blank."
+ end
+ end
+
+ class Bool
+ include Validator
+ def pass?(val)
+ if val.nil? || val.is_a?(TrueClass) || val.is_a?(FalseClass)
+ return true
+ end
+ if (val == 0 || val == 1)
+ return true
+ end
+ false
+ end
+
+ def message(val)
+ "must be '0', '1', true or false, but was #{val}."
+ end
+ end
+
+ class MaxLength
+ include Validator
+ def initialize(max)
+ @max = max
+ end
+
+ def pass?(val)
+ if val.respond_to?(:jlength)
+ return val.jlength <= @max
+ elsif val.respond_to?(:length)
+ return val.length <= @max
+ end
+ true
+ end
+
+ def message(val)
+ length = val.respond_to?(:jlength) ? val.jlength : val.length
+ "must not be longer than #{@max}, but was #{length}."
+ end
+ end
+
+ class MinLength
+ include Validator
+ def initialize(min)
+ @min = min
+ end
+
+ def pass?(val)
+ if val.respond_to?(:jlength)
+ return val.jlength == 0 || val.jlength >= @min
+ elsif val.respond_to?(:length)
+ return val.length == 0 || val.length >= @min
+ end
+ true
+ end
+
+ def message(val)
+ length = val.respond_to?(:jlength) ? val.jlength : val.length
+ "must not be shorter than #{@min} but was #{length}."
+ end
+ end
+
+ class In
+ include Validator
+ def initialize(range)
+ @range = range
+ end
+
+ def pass?(val)
+ val.nil? || @range.include?(val)
+ end
+
+ def message(val)
+ "must be in range #{@range}, but was #{val}."
+ end
+ end
+
+ class Max
+ include Validator
+ def initialize(max)
+ @max = max.to_i
+ end
+
+ def pass?(val)
+ val.nil? || @max >= val.to_i
+ end
+
+ def message(val)
+ "must not be greater than #{@max.to_s}, but was #{val.to_s}."
+ end
+ end
+
+ class Min
+ include Validator
+ def initialize(min)
+ @min = min.to_i
+ end
+
+ def pass?(val)
+ val.nil? || @min <= val.to_i
+ end
+
+ def message(val)
+ "must not be smaller than #{@min.to_s}, but was #{val.to_s}."
+ end
+ end
+
+ class RegexAssertion
+ include Validator
+ def pass?(val)
+ if val.nil? || val.to_s.length == 0
+ return true
+ end
+ return val =~ regex
+ end
+
+ def regex
+ raise 'RegexAssertion#regex must be implemented.'
+ end
+
+ def message(val)
+ "must be #{regex.to_s}, but was #{val}."
+ end
+
+ def self.regex_for_unicode_code_point_range(code_point_ranges)
+ character_class = code_point_ranges.collect do |range|
+ first_glyph = [range.first.to_i(16)].pack('U')
+ last_glyph = [range.last.to_i(16)].pack('U')
+ "#{first_glyph}-#{last_glyph}"
+ end.join
+ /^[#{character_class}]+$/x
+ end
+ end
+
+ class Match < RegexAssertion
+ attr_reader :regex
+ def initialize(regex)
+ @regex = Regexp.compile(regex)
+ end
+ end
+
+ class Printable < RegexAssertion
+ CHARACTER_CLASS = %q/\x21-\x7E/
+ REGEX = /^[#{CHARACTER_CLASS}]+$/x
+ def regex
+ REGEX
+ end
+ end
+
+ class Word < RegexAssertion
+ CHARACTER_CLASS = %q/0-9a-zA-Z_/
+ REGEX = /^[#{CHARACTER_CLASS}]+$/x
+ def regex
+ REGEX
+ end
+ end
+
+ class Hiragana < RegexAssertion
+ REGEX = self.regex_for_unicode_code_point_range(['3040'..'309F'])
+ def regex
+ REGEX
+ end
+ end
+
+ class Katakana < RegexAssertion
+ REGEX = self.regex_for_unicode_code_point_range(
+ ['30A0'..'30FF', '31F0'..'31FF']
+ )
+ def regex
+ REGEX
+ end
+ end
+
+ class Kanji < RegexAssertion
+ REGEX = self.regex_for_unicode_code_point_range([
+ 'F900'..'FAFF', # cjk 互換漢字
+ '4E00'..'9FFF', # cjk 統合漢字
+ '3400'..'4DBF' # cjk 統合漢字拡張A
+ ])
+ def regex
+ REGEX
+ end
+ end
+
+ class Kana < RegexAssertion
+ REGEX = self.regex_for_unicode_code_point_range([
+ '3040'..'309F', #ひらがな
+ '30A0'..'30FF', '31F0'..'31FF' #カタカナ
+ ])
+ def regex
+ REGEX
+ end
+ end
+
+ class KanaKanji < RegexAssertion
+ REGEX = self.regex_for_unicode_code_point_range([
+ '3040'..'309F', #ひらがな
+ '30A0'..'30FF', '31F0'..'31FF', #カタカナ
+ 'F900'..'FAFF', # cjk 互換漢字
+ '4E00'..'9FFF', # cjk 統合漢字
+ '3400'..'4DBF' # cjk 統合漢字拡張A
+ ])
+ def regex
+ REGEX
+ end
+
+ def message(val)
+ "must be japanese kana or kanji, but was #{val}."
+ end
+ end
+
+ class DomainName < RegexAssertion
+ label_lower = %q/[a-z]+(?:[0-9a-z\-]*[a-z0-9]+)*/
+ label_upper = label_lower.gsub(/a-z/, 'A-Z')
+ REGEX_STR = %Q/(?:
+ #{label_lower}(?:\\.#{label_lower})*
+ |
+ #{label_upper}(?:\\.#{label_upper})*
+ )/
+ REGEX = /^#{REGEX_STR}$/x
+
+ def regex
+ REGEX
+ end
+
+ def message(val)
+ "must be valid domain name, but was #{val}."
+ end
+ end
+
+ class MailAddress < RegexAssertion
+ atom = %q/a-zA-Z0-9!#$%&'*+\-\/=?^_`{\|}~/
+ dot_atom = %Q/[#{atom}]+(?:\\.[#{atom}]+)*/
+ qcontent = %q/\x21\x23-\x5B\x5D-\x7E/ # not including '\' and '"'
+ quoted_string = %Q/"[ #{qcontent}]*"/
+ local_part = %Q/(?:
+ #{dot_atom}(?=@)
+ |
+ #{quoted_string}(?=@)
+ )/
+ domain_name = DomainName::REGEX_STR
+ REGEX_STR = "#{local_part}@#{domain_name}"
+ REGEX = /^#{REGEX_STR}$/x
+
+ def regex
+ REGEX
+ end
+
+ def message(val)
+ "must be valid mail address, but was #{val}."
+ end
+ end
+
+ class ZipCode < RegexAssertion
+ def regex
+ /[\d]{7}/
+ end
+
+ def message(val)
+ "must be 7 digits, but was #{val}."
+ end
+ end
+
+ class URL < RegexAssertion
+ domain_name = DomainName::REGEX_STR
+ REGEX = /^https?\:\/\/#{domain_name}/x
+
+ def regex
+ REGEX
+ end
+
+ def message(val)
+ "must be valid url, but was #{val}."
+ end
+ end
+ end
+end
--- /dev/null
+require 'open-uri'
+
+module Hdboo
+ class Crawler
+ public
+ def initialize(kargs={})
+ end
+
+ public
+ def crawl(*path_list, &action)
+ @path_stack = []
+ stack_path(path_list)
+ until @path_stack.empty? do
+ path = @path_stack.pop
+ process(path, &action)
+ end
+ self
+ end
+
+ protected
+ def stack_path(path_list)
+ @path_stack.push(*path_list.compact.uniq)
+ end
+
+ protected
+ def process(path, &action)
+ raise "#{self.class.name}#process_path(path, &action) must be implemented."
+ end
+ end
+
+ class FileSystemCrawler < Crawler
+ protected
+ def stack_path(path_list)
+ path_list = path_list.map do |path|
+ Dir.glob(path)
+ end
+ path_list.flatten!
+ path_list.compact!
+ super(path_list)
+ end
+
+ protected
+ def process(path, &action)
+ if File.file?(path)
+ open(path, &action)
+ end
+ end
+ end
+
+ class WebCrawler < Crawler
+ def process(path, &action)
+ open(path, &action)
+ end
+ end
+
+ class DatabaseCarwler
+ end
+end
--- /dev/null
+require 'time'
+require 'mysql'
+require 'hdboo/base'
+require 'hdboo/sql'
+
+module Hdboo
+class Database
+ ADMINISTRATIVE_DB_NAME = 'mysql'
+ HOST = 'localhost'
+ USER = 'root'
+ PASSWORD = ''
+ PORT = nil
+ SOCKET = '/var/run/mysqld/mysqld.sock'
+ #SOCKET = '/tmp/mysql.sock'
+
+ @instances = Hash.new do |hash, dbname|
+ hash[dbname] = new(dbname)
+ end
+
+ def self.[](dbname)
+ @instances[dbname.to_sym]
+ end
+
+ attr_reader :name
+ private
+ def initialize(name)
+ @name = name
+ ObjectSpace.define_finalizer(self, lambda{self.close})
+ end
+
+ public
+ def connect(dbname=@name)
+ unless @conn
+ @conn = Mysql.connect(
+ HOST,USER, PASSWORD, dbname.to_s, PORT, SOCKET,
+ Mysql::CLIENT_MULTI_RESULTS
+ )
+ @conn.reconnect = true
+ end
+ self
+ end
+
+ def transaction(&block)
+ connect unless @conn
+ @conn.autocommit(false)
+ begin
+ Thread.current[:rdboo_transaction_on_thread] = @conn
+ result = block.call
+ Database.raise_error_if_it_occur
+ @conn.commit
+ return result
+ rescue
+ begin
+ @conn.rollback
+ rescue
+ #nothing todo
+ end
+ raise $!
+ ensure
+ Thread.current[:rdboo_transaction_on_thread] = nil
+ end
+ self
+ end
+
+ def execute(sql_script)
+ ddl_file_name = "#{name}.ddl"
+ File.open(ddl_file_name, 'w+') do |file|
+ file.puts "CREATE DATABASE IF NOT EXISTS #{name} CHARACTER SET utf8;"
+ file.puts "USE #{name};"
+ file.puts sql_script
+ end
+ err = nil
+ open("|mysql -u #{USER} < #{ddl_file_name}") do |pipe|
+ end
+ self
+ end
+
+ def self.call_procedure(procedure_name, args, &block)
+ args = args.collect {|arg| sql_value_of(arg)}
+ sql_script = "CALL #{procedure_name} (#{args.join(',')})"
+ query(sql_script, &block)
+ end
+
+ def self.query(sql_script, &block)
+ unless conn = Thread.current[:rdboo_transaction_on_thread]
+ raise 'No transaction has been started.'
+ end
+#$stderr << sql_script.inspect
+ result_set_list = []
+ conn.query(sql_script) do |rows|
+ raise_error_if_it_occur
+ result_set = SQL::ResultSet.new
+ rows.each do |row|
+ row_hash = SQL::Record.new
+ row.each_with_index do |col_value, col_index|
+ field = rows.fetch_field_direct(col_index)
+ row_hash[field.name.to_sym] = rb_value_of(col_value, field)
+ end
+ result_set << row_hash
+ end
+#$stderr << 'result_set ==> ' + result_set.inspect
+ result_set_list << result_set
+ end
+
+ if block_given?
+ result_set_list.each do |result_set|
+ block.call(result_set)
+ end
+ end
+ result_set_list.last || SQL::ResultSet.new
+ end
+
+ def self.sql_value_of(val)
+ case val
+ when NilClass then "NULL"
+ when TrueClass then "1"
+ when FalseClass then "0"
+ when String then '"' + Mysql.quote(val) + '"'
+ when Array then '"' + val.map{|v| Mysql.quote(v)}.join(',') + '"'
+ when Time then val.strftime('%Y-%m-%d %H:%M:%S').inspect
+ else val.inspect
+ end
+ end
+
+ def self.rb_value_of(val, field)
+ return nil if val.nil?
+ case field.type
+ when Mysql::Field::TYPE_DECIMAL, Mysql::Field::TYPE_TINY,
+ Mysql::Field::TYPE_SHORT, Mysql::Field::TYPE_LONG,
+ Mysql::Field::TYPE_LONGLONG
+ val.to_i
+ when Mysql::Field::TYPE_FLOAT, Mysql::Field::TYPE_DOUBLE
+ val.to_f
+ when 16 #Mysql::Field::TYPE_BIT
+ val == "\x00" ? false : true
+ when Mysql::Field::TYPE_STRING, Mysql::Field::TYPE_VAR_STRING,
+ Mysql::Field::TYPE_CHAR
+ val.to_s
+ when Mysql::Field::TYPE_BLOB
+ val
+ when Mysql::Field::TYPE_DATETIME, Mysql::Field::TYPE_TIMESTAMP
+ Time.xmlschema(val.sub(/\s/, 'T'))
+ else raise TypeError, "Type: #{field.type}, Value: #{val}", caller
+ end
+ end
+
+ def self.raise_error_if_it_occur
+ conn = Thread.current[:rdboo_transaction_on_thread]
+ if conn.errno > 0
+ raise Mysql::Error, conn.errno, conn.error
+ end
+ end
+
+ def close
+ @conn.close if @conn
+ @conn = nil
+ self
+ end
+end
+end
--- /dev/null
+require 'mkmf'
+unless have_header('magic.h') && have_library('magic', 'magic_open')
+ raise 'libmagic-dev must be installed.'
+end
+create_makefile('hdboo/_search')
--- /dev/null
+require 'hdboo/base'
+
+module Hdboo::HTML
+ INDENTATION_STR = ' '
+ def self.indent(lines, depth=1)
+ unless lines.is_a?(Array)
+ lines = lines.to_s.split("\n")
+ end
+ lines.map!{|line| (INDENTATION_STR * depth) + line}
+ end
+end
+
+class String
+ def indent(depth)
+ Hdboo::HTML.indent(self, depth).join("\n")
+ end
+end
+
+class Array
+ def indent(depth)
+ Hdboo::HTML.indent(self, depth)
+ end
+
+ def to_html(profile=nil, depth=0)
+ buff = []
+ buff << '<ol>'.indent(depth)
+ each do |item|
+ buff.concat([
+ '<li>'.indent(depth+1),
+ item.to_html(profile, depth+2),
+ '</li>'.indent(depth+1)
+ ])
+ end
+ buff << '</ol>'.indent(depth)
+ buff.join("\n")
+ end
+end
+
+class Object
+ def to_html(profile=nil, depth=0)
+ respond_to?(:to_hash) ? to_hash.to_html(profile, depth)\
+ : to_s.indent(depth)
+ end
+end
+
+class Hash
+ def to_html(profile=nil, depth=0)
+ buff = []
+ buff << '<dl>'.indent(depth)
+ each do |key, val|
+ buff.concat([
+ '<dt>'.indent(depth+1),
+ key.to_html(profile, depth+2),
+ '</dt>'.indent(depth+1),
+ '<dd>'.indent(depth+1),
+ val.to_html(profile, depth+2),
+ '</dd>'.indent(depth+1)
+ ])
+ end
+ buff << '</dl>'.indent(depth)
+ buff.join("\n")
+ end
+end
--- /dev/null
+have_header: checking for magic.h... -------------------- yes
+
+"cc -E -I. -I/usr/lib/ruby/1.8/arm-linux-eabi -I. -D_FILE_OFFSET_BITS=64 -fno-strict-aliasing -g -g -O2 -fPIC conftest.c -o conftest.i"
+checked program was:
+/* begin */
+1: #include <magic.h>
+/* end */
+
+--------------------
+
+have_library: checking for magic_open() in -lmagic... -------------------- yes
+
+"cc -o conftest -I. -I/usr/lib/ruby/1.8/arm-linux-eabi -I. -D_FILE_OFFSET_BITS=64 -fno-strict-aliasing -g -g -O2 -fPIC conftest.c -L. -L/usr/lib -L. -Wl,-Bsymbolic-functions -rdynamic -Wl,-export-dynamic -lruby1.8-static -lmagic -lpthread -ldl -lcrypt -lm -lc"
+conftest.c: In function ‘t’:
+conftest.c:3: error: ‘magic_open’ undeclared (first use in this function)
+conftest.c:3: error: (Each undeclared identifier is reported only once
+conftest.c:3: error: for each function it appears in.)
+checked program was:
+/* begin */
+1: /*top*/
+2: int main() { return 0; }
+3: int t() { void ((*volatile p)()); p = (void ((*)()))magic_open; return 0; }
+/* end */
+
+"cc -o conftest -I. -I/usr/lib/ruby/1.8/arm-linux-eabi -I. -D_FILE_OFFSET_BITS=64 -fno-strict-aliasing -g -g -O2 -fPIC conftest.c -L. -L/usr/lib -L. -Wl,-Bsymbolic-functions -rdynamic -Wl,-export-dynamic -lruby1.8-static -lmagic -lpthread -ldl -lcrypt -lm -lc"
+checked program was:
+/* begin */
+1: /*top*/
+2: int main() { return 0; }
+3: int t() { magic_open(); return 0; }
+/* end */
+
+--------------------
+
--- /dev/null
+require 'cgi'
+require 'tempfile'
+require 'digest/md5'
+require 'fcgi'
+require 'json'
+require 'hdboo/html'
+
+module Hdboo
+module REST
+ class Server
+ class Request
+ attr_reader :params, :env
+ def initialize(params, env, server)
+ @params = params
+ @env = env
+ @server = server
+ end
+
+ def secure?
+ server_name = @env['SERVER_NAME']
+ if server_name =~ /^(localhost|192\.168\.\d{1,3}.\d{1,3}|127\.0\.0\.1)$/
+ return true
+ end
+ @env['SERVER_PORT'].to_i == 443
+ end
+
+ def secure_url
+ server_name = @env['SERVER_NAME'] || ''
+ script_name = @env['SCRIPT_NAME'] || ''
+ 'https://' + uri
+ end
+
+ def uri
+ server_name = @env['SERVER_NAME'] || ''
+ script_name = @env['SCRIPT_NAME'] || ''
+ server_name + script_name
+ end
+
+ def remote_user
+ result = @env['REMOTE_USER'] || @env['HTTP_AUTHORIZATION_USERNAME']
+ if result.nil? || result.length == 0
+ return nil
+ end
+ return result
+ end
+
+ def authenticated?
+ return false unless remote_user
+ authenticated_as?(remote_user)
+ end
+
+ def authenticated_as?(username)
+ ha1 = @server.password_digest(username)
+ unless ha1 && ha1.length == 32
+ return false #unknown user
+ end
+ realm = @server.base_realm
+ nonce = @env['HTTP_AUTHORIZATION_NONCE'] || ''
+ qop = 'auth' #"auth-int" isn't supported
+ nc = @env['HTTP_AUTHORIZATION_NC'] || ''
+ cnonce = @env['HTTP_AUTHORIZATION_CNONCE'] || ''
+ response= @env['HTTP_AUTHORIZATION_RESPONSE'] || ''
+ method = @env['REQUEST_METHOD'] || ''
+ path = @env['HTTP_AUTHORIZATION_URI'] || ''
+ ha2 = Digest::MD5::hexdigest(method + ':' + path)
+ answer = Digest::MD5::hexdigest([
+ ha1, nonce, nc, cnonce, qop, ha2
+ ].join(':'))
+ response == answer
+ end
+ end
+
+ module Response
+ attr_reader :status, :header
+ def initialize(status, body=nil, header={})
+ @status = status
+ @body = body
+ @header = {'status'=>status.to_s}.merge(header)
+ end
+
+ def body
+ case header['type']
+ when 'application/json' then json
+ when 'text/html' then html
+ else @body
+ end
+ end
+
+ def html
+ buff = []
+ unless has_head_tag?
+ status_text = self.class.name.split('::').last
+ buff.concat([
+ "<head>",
+ " <title>#{status.to_s}: #{status_text}</title>",
+ "</head>"
+ ])
+ end
+ buff.concat(
+ has_body_tag?\
+ ? [@body.to_html]\
+ : ['<body>',
+ @body.to_html(nil, 1),
+ '</body>']
+ )
+ buff.join("\n")
+ end
+
+ def has_head_tag?
+ @body.is_a?(String) && @body =~ /<head(\b|>)/
+ end
+
+ def has_body_tag?
+ @body.is_a?(String) && @body =~ /<body(\b|>)/
+ end
+
+ def json
+ @body.to_json
+ end
+
+ class Success
+ include Response
+ end
+
+ def OK(body, header={})
+ header = header.is_a?(String)\
+ ? {'type' => header}\
+ : header
+ Success.new(200, body, header)
+ end
+
+ def Created(body, header={})
+ Success.new(201, body, header)
+ end
+
+ def Accepted(body, header={})
+ Success.new(202, body, header)
+ end
+
+ class Redirection
+ include Response
+ def initialize(status, uri)
+ super(status, uri, header={'Location' => uri})
+ end
+ end
+
+ def MovedPermanently(uri)
+ Redirection.new(301, uri)
+ end
+
+ def SeeOther(uri)
+ Redirection.new(303, uri)
+ end
+
+ def TemporaryRedirect(uri)
+ Redirection(306, uri)
+ end
+
+ class ClientSideError < UserError
+ include Response
+ def self.exception(messages=nil)
+ self.new(messages)
+ end
+
+ def initialize(messages)
+ @status = status
+ @body = messages
+ @header = {'status'=>status.to_s}
+ end
+
+ def status
+ self.class.status
+ end
+
+ def status_text
+ self.class.status_text
+ end
+ end
+
+ class BadRequest < ClientSideError
+ def self.status
+ 400
+ end
+ end
+
+ class Unauthorized < ClientSideError
+ def self.base_realm=(realm)
+ @base_realm = realm
+ end
+
+ def self.base_realm
+ @base_realm
+ end
+
+ def self.status
+ 401
+ end
+
+ def add_authenticate_header(env)
+ nc = env['HTTP_AUTHORIZATION_NC'].to_i
+ authenticate_header = nil
+ if nc == 1
+ authenticate_header = nil
+ else
+ nonce = Digest::MD5.hexdigest(Time.now.to_f.to_s + 'nonce')
+ opaque = Digest::MD5.hexdigest(Time.now.to_f.to_s + 'opaque')
+ stale = (nc==0) ? 'FALSE' : 'TRUE'
+ authenticate_header = 'Digest ' + [
+ %Q/realm="#{Unauthorized.base_realm}"/,
+ %Q/nonce="#{nonce}"/,
+ %Q/algorithm=MD5/,
+ %Q/qop=auth/,
+ %Q/opaque=#{opaque}/,
+ %Q/stale=#{stale}/
+ ].join(', ')
+ end
+
+ if authenticate_header
+ @header['WWW-Authenticate'] = authenticate_header
+ end
+ self
+ end
+ end
+
+ class Forbidden < ClientSideError
+ def self.status
+ 403
+ end
+ end
+
+ class NotFound < ClientSideError
+ def self.status
+ 404
+ end
+ end
+
+ class MethedNotAllowed < ClientSideError
+ def self.status
+ 405
+ end
+ end
+
+ class Conflict < ClientSideError
+ def self.status
+ 409
+ end
+ end
+
+ class ServerSideError < StandardError
+ include Response
+ def self.exception(message='')
+ self.new(message)
+ end
+
+ def self.status
+ 500
+ end
+
+ def initialize(message)
+ @status = status
+ @body = message + "\n" + backtrace.join("\n")
+ @header = {'status'=>status.to_s}
+ end
+
+ def status
+ self.class.status
+ end
+ end
+
+ class NotImplemented < ServerSideError
+ def self.status
+ 501
+ end
+ end
+
+ class ServiceUnavailable < ServerSideError
+ def self.status
+ 503
+ end
+ end
+ end
+
+ class RouteConflictionError < StandardError; end
+
+ include Hdboo
+ include Response
+
+ def initialize
+ end
+
+ def inspect
+ self.class.name + "\n" + @routes.inspect
+ end
+
+ def base_realm(realm=nil)
+ if realm
+ Unauthorized.base_realm = realm
+ end
+ Unauthorized.base_realm
+ end
+
+ def base_url(url=nil)
+ if url
+ TemplateContext.base_url = url
+ end
+ url
+ end
+
+ def respond_to(uri_template, &block)
+ @routes = URISegment.new unless @routes
+ method, route = URISegment.parse_route(uri_template)
+ @routes.attach_action(route, method, &block)
+ self
+ end
+
+ def before_process(uri_template, &block)
+ @routes = URISegment.new unless @routes
+ method, route = URISegment.parse_route(uri_template)
+ @routes.attach_before_filter(route, method, &block)
+ end
+
+ def after_process(uri_template, &block)
+ @routes = URISegment.new unless @routes
+ method, route = URISegment.parse_route(uri_template)
+ @routes.attach_after_filter(route, method, &block)
+ end
+
+ def clear_routes
+ @routes = URISegment.new
+ self
+ end
+
+ def run
+ FCGI.each_cgi do |cgi|
+ do_cgi(cgi)
+ end
+ end
+
+ def test_run(env_table, params={})
+ cgi = CGIStub.new(env_table, params)
+ pipe = IO.popen('-')
+ if pipe
+ Process.wait
+ pipe.gets(nil)
+ else
+ begin
+ do_cgi(cgi)
+ rescue Exception
+ $stderr << [$!.message, $!.backtrace].join("\n")
+ ensure
+ exit
+ end
+ end
+ end
+
+ private
+ def do_cgi(cgi)
+ method = cgi.request_method
+ route = cgi.script_name
+ action, bound_kargs = @routes.find_action(route, method)
+ unless action
+ raise NotFound, "Not Found : #{route} (#{method})"
+ end
+ params = pack_query(cgi.params).merge!(bound_kargs)
+ env = cgi.env_table
+ request = Request.new(params, env, self)
+ response = action.call(request)
+ unless response.is_a?(Response)
+ response = OK(response)
+ end
+ rescue Unauthorized
+ response = $!
+ response.add_authenticate_header(env)
+ rescue ClientSideError
+ response = $!
+ rescue NoDataFound
+ response = NotFound.new($!.messages)
+ rescue DataConflict
+ response = Conflict.new($!.messages)
+ rescue InvalidArgument
+ response = BadRequest.new($!.messages)
+ rescue ServerSideError
+ response = $!
+ $stderr << response.body
+ rescue
+ error_message = [$!.message, $!.backtrace].join("\n")
+ $stderr << error_message
+ response = ServerSideError.new(error_message)
+ ensure
+ unless response.header.has_key?('type')
+ response.header['type'] = default_content_type(cgi.accept)
+ end
+ cgi.out(response.header) do
+ response.body
+ end
+ end
+
+ def default_content_type(http_accept)
+ if http_accept && http_accept.include?('application/json')
+ 'application/json'
+ else
+ 'text/html'
+ end
+ end
+
+ def pack_query(params)
+ result = HashAsObject.new
+ params.each do |key, value|
+ next unless key && key.to_s.length > 0
+ if value.is_a?(Array)
+ value.collect! {|val| unescape(val)}
+ result[key] = (value.length == 1)\
+ ? value[0]\
+ : value
+ else
+ result[key] = unescape(value)
+ end
+ end
+ result
+ end
+
+ def unescape(value)
+ case value
+ when String then CGI.unescape(value)
+ when StringIO, Tempfile
+ if value.content_type =~ /^image\//
+ value
+ else
+ CGI.unescape(value.read)
+ end
+ else value
+ end
+ end
+
+ class CGIStub < ::CGI
+ attr_accessor :env_table, :params
+ def initialize(env_table, params)
+ @env_table, @params = env_table, params
+ end
+
+ def method_missing(attr)
+ attr = attr.to_s.upcase
+ @env_table[attr] || @env_table['HTTP_' + attr]
+ end
+ end
+
+ class URISegment
+ def self::parse_route(uri_template)
+ method, path= uri_template.scan(/
+ (GET|PUT|POST|DELETE|HEAD|OPTION) #http method
+ \s*
+ (
+ (?:
+ \/[\.\w]+ # segment_name
+ |
+ \/\{[\w]+\} # {variable_name}
+ )+
+ \/? # terminator
+ )
+ /x)[0]
+ unless method && path
+ raise ArgumentError, uri_template
+ end
+ result = path.gsub(/^\/|\/$/, '').split('/')
+ result.collect! {|segment| URISegment.new(segment)}
+ return [method, result]
+ end
+
+ def walk_along(route, segment_name='', depth=0, &block)
+ if route.is_a?(String)
+ route = route.gsub(/^\/|\/$/, '')\
+ .split('/')\
+ .collect! {|seg| URISegment.new(seg)}
+ end
+
+ result = block.call(self, segment_name, route.length != 0, depth)
+
+ next_segment = route.shift
+ return result unless next_segment
+
+ child = self[next_segment.name]
+ return result unless child
+
+ child.walk_along(route,
+ next_segment.name,
+ depth+1,
+ &block)
+ end
+
+ def find_action(route, method)
+ method = method.to_sym
+ before_filter = []
+ after_filter = []
+ bound_kargs = {}
+ action = nil
+
+ walk_along(route) do |segment, segment_name, has_next|
+ before_filter.concat(segment.before_filter[method])
+ after_filter.concat(segment.after_filter[method])
+
+ if segment.variable?
+ bound_kargs[segment.name.to_sym] = CGI.unescape(segment_name)
+ end
+
+ unless has_next
+ unless segment.action.has_key?(method.to_sym)
+ raise Server::MethedNotAllowed,
+ "#{segment.name} : #{method}"
+ end
+ action = lambda do |request|
+ before_filter.each do |filter|
+ filter.call(request)
+ end
+ response = segment.action[method].call(request)
+ after_filter.each do |filter|
+ filter.call(response)
+ end
+ response
+ end
+ end
+ end
+ return [action, bound_kargs]
+ end
+
+ def attach_before_filter(route, method, &block)
+ method = method.to_sym
+ attach_route(route) do |segment|
+ segment.before_filter[method] << block
+ end
+ end
+
+ def attach_after_filter(route, method, &block)
+ method = method.to_sym
+ attach_route(route) do |segment|
+ segment.after_filters[method] << block
+ end
+ end
+
+ def attach_action(route, method, &block)
+ method = method.to_sym
+ attach_route(route) do |segment|
+ segment.action[method] = block
+ end
+ end
+
+ def attach_route(route, &block)
+ segment = route.shift
+
+ unless @children.has_key?(segment.name)
+ @children[segment.name] = segment
+ end
+
+ if route.empty?
+ block.call(@children[segment.name])
+ else
+ @children[segment.name].attach_route(route, &block)
+ end
+ end
+
+ def root?
+ name == ''
+ end
+
+ def leaf?
+ @children.empty?
+ end
+
+ def variable?
+ @variable ? true : false
+ end
+
+ def [](child_name)
+ if @children.has_key? child_name
+ return @children[child_name]
+ end
+ has_variable_child? ? @children.find {|k, v| v.variable?}.last \
+ : nil
+ end
+
+ def has_variable_child?
+ @children.any? {|name, segment| segment.variable?}
+ end
+
+ attr_reader :name, :action, :before_filter, :after_filter
+
+ def initialize(segment='')
+ @variable = segment =~ /^\{(.*)\}$/
+ @name = variable? ? $1 : segment
+ @action = {}
+ @children = {}
+ @before_filter = Hash.new {|hash, method| hash[method] = []}
+ @after_filter = Hash.new {|hash, method| hash[method] = []}
+ end
+
+ def inspect(depth=1)
+ this_node = root? ? '/':
+ variable? ? "{#{name}}"\
+ : name
+ allowed_methods = action.keys.join(', ')
+ result = ["#{this_node} : #{allowed_methods}"]
+ @children.each {|name, child| result << child.inspect(depth+1)}
+ result.join("\n#{' '*depth}> ")
+ end
+ end
+ end
+end
+end
--- /dev/null
+require 'open-uri'
+require 'xapian'
+require 'hdboo/crawler'
+require 'hdboo/_search'
+
+include Hdboo
+
+module Search
+class Document
+ def initialize(io)
+ @io = io.is_a?(IO) \
+ ? io \
+ : StringIO.new(io.to_s)
+ @mime_type = @io.mime_type
+ end
+
+ def text
+ if @text.nil?
+ @text = @io.gets(nil)
+ end
+ @text
+ end
+
+ def term_list
+ if @term_list.nil?
+ @term_list = []
+ end
+ @term_list
+ end
+
+ def mime_type
+ @mime_type
+ end
+
+ class Zone
+ end
+end
+end
+=begin
+class Index
+ public
+ def self.connect(path)
+ @instances[path]
+ end
+
+ @instances = Hash.new do |hash, path|
+ path = File.expand_path
+ hash[path] = new(path)
+ end
+
+ private
+ def initialize(path)
+ Xapian::WritableDatabase.new(path, Xapian::DB_CREATE_OR_OPEN)
+ end
+
+ public
+ def close
+ end
+
+ def query(words, &block)
+ database = Xapian::Database.new('./temp/database')
+ stemmer = Xapian::Stemmer.new('english')
+ query_parser = Xapian::QueryParser.new
+
+ query_parser.database = database
+ query_parser.stemmer = stemmer
+ query_parser.stemming_strategy = Xapian::QueryParser::STEM_SOME
+ end
+end
+
+class Document < ObjectAsHash
+ zones (
+ :body,
+ :title
+ )
+ properties (
+ :id,
+ :mime_type,
+ :name,
+ :path
+ )
+ attr_reader(
+ :term_list
+ )
+
+ public
+ def initialize(file)
+
+ end
+end
+
+class Scanner
+ public
+ def scan(file, &block)
+ mime_type = file.mime_type
+ scrape_text_plain(file, &block)
+ self
+ end
+
+ private
+ def scan_text_html(page)
+
+ end
+
+ private
+ def scan_text_plain(file, &block)
+ Document.new(
+ :body =>
+ :doc_neme =>
+ :doc_path =>
+ :doc_id =>
+ :created_date =>
+ :modified_date =>
+ )
+ end
+end
+
+
+class FileSystemIndexer
+ DEFAULT_INDEX_PATH = './INDEX'
+ def initialize(kargs={})
+ index_path = kargs[:index_path] || DEFAULT_INDEX_PATH
+ @index = Index.connect(index_path)
+ @crawler = FileSystemCrawler.new
+ @scraper = Scraper.new
+ end
+
+ def crawl(*globs)
+ @crawler.crawl(*globs) do |file|
+ @scraper.scrape(file) do ||
+ end
+ end
+ end
+end
+
+class WebIndexer < WebCrawler
+ def crawl_along(uri)
+ open(uri) {|page| scrape(page)}
+ end
+
+ def scrape()
+ file.
+ end
+end
+=end
--- /dev/null
+require 'time'
+require 'hdboo/base'
+require 'hdboo/database'
+require 'hdboo/sqlparser'
+
+module ::Kernel
+ def require_sql(module_name, destructive=false, reload=false)
+ unless module_name =~ /([_\w]+(?:\/[_\w]+)*)@([_a-z]+)/
+ raise NameError, module_name
+ end
+ module_name = $1
+ db_name = $2
+ Hdboo.resource("#{module_name}.sql", reload) do |sql_script|
+ raise Errno::ENOENT, module_name unless sql_script
+ Hdboo::SQL.sql_eval(db_name, sql_script, destructive)
+ if Hdboo.resource_exist?("#{module_name}.rb")
+ reload ? load(module_name+'.rb')\
+ : require(module_name)
+ end
+ sql_script
+ end
+ true
+ end
+
+ def require_sql!(module_name)
+ require_sql(module_name, true, false)
+ end
+
+ def load_sql(module_name)
+ require_sql(module_name, false, true)
+ end
+
+ def load_sql!(module_name)
+ require_sql(module_name, true, true)
+ end
+end
+
+module Hdboo
+module SQL
+
+ def self.connect(database_name)
+ Database[database_name]
+ end
+
+ def self.query(sql_script, &block)
+ Database.query(sql_script, &block)
+ end
+
+ def self.sql_eval!(database_name, sql_script)
+ sql_eval(database_name, sql_script, true)
+ self
+ end
+
+ def self.sql_eval(database_name, sql_script, destructive=false)
+ sql_ast = Parser.new.parse(sql_script)
+ sql_module = nil
+
+ Parser::ASTWalker.new\
+ .visit(:MODULE_ANNOTATION) do |node|
+ sql_module = node.children.find do |child|
+ child.value.is_a?(Module)
+ end.value
+ node.children.each do |child|
+ if child.value.is_a?(Include)
+ sql_module.elements << child.value.sql_element
+ end
+ end
+ end\
+ .visit(:CREATE_TABLE) do |node|
+ sql_module.elements << node.value
+ end\
+ .visit(:CREATE_VIEW) do |view|
+ sql_module.elements << node.value
+ end\
+ .visit(:CREATE_PROCEDURE) do |node|
+ sql_module.elements << node.value
+ end\
+ .walk(sql_ast)
+
+ db = Database[database_name]
+ db.execute(sql_module.ddl) if destructive
+ db.transaction {sql_module.eval(destructive)}
+ self
+ end
+
+ module Annotation
+ #static methods
+ def self.new(type, args)
+ type = type.to_sym
+ clazz = nil
+ annotation_libraries.each do |library|
+ if library.const_defined?(type)
+ clazz = library.const_get(type)
+ break if clazz < Annotation
+ clazz = nil
+ end
+ end
+
+ unless clazz
+ raise 'unknown annotation type: ' + type.to_s
+ end
+ begin
+ clazz.new(*args)
+ rescue
+ raise "annotation #{type.to_s} can not be initialized.: #{$!.message}"
+ end
+ end
+
+ def self.annotation_libraries
+ unless @annotation_libraries
+ @annotation_libraries = [
+ ::Hdboo::SQL,
+ ::Hdboo::Validator,
+ ::Hdboo::SQL::Table,
+ ::Hdboo::SQL::Procedure,
+ ::Hdboo::SQL::Procedure::Intercepter,
+ ::Hdboo::SQL::Procedure::Parameter
+ ]
+ ::Hdboo::Validator.constants.each do |name|
+ validator = ::Hdboo::Validator.const_get(name)
+ if validator < ::Hdboo::Validator
+ validator.send(:include, Annotation)
+ end
+ end
+ end
+ @annotation_libraries
+ end
+
+ #instance methods
+ attr_accessor :annotation_context
+ end
+
+ module Element
+ attr_accessor :sql_module, :source
+ def eval
+ raise "#{self.class.name}#eval() must be implemented."
+ end
+
+ def create
+ raise "#{self.class.name}#create() must be implemented."
+ end
+
+ def drop
+ raise "#{self.class.name}#drop() must be implemented."
+ end
+ end
+
+ class Module
+ include Annotation
+ #static methods
+ @instances = {}
+ def self.[](name)
+ @instances[name.to_sym]
+ end
+
+ def self.register(instance)
+ @instances[instance.fqn] = instance
+ end
+
+ #instance methods
+ attr_reader :fqn, :schema, :namespace, :elements
+ def initialize(fqn, schema='')
+ @fqn = fqn
+ @schema = schema
+ @elements = []
+ end
+
+ def [](symbol)
+ namespace.const_get(symbol.to_sym)
+ end
+
+ def namespace
+ return @namespace if @namespace
+ unless fqn =~ /\w+(.\w+)*/
+ raise SyntaxError, "invalid namespace format: #{fqn}"
+ end
+ script = ''
+ segments = fqn.split(/\./)
+ segments.reverse.each_with_index do |name, depth|
+ root = (depth == segments.length-1) ? '::' : ''
+ script = [ "module #{root}#{name}",
+ script,
+ 'end'
+ ].join("\n")
+ end
+ Kernel.eval(script)
+ @rb_module = Kernel.eval('::' + segments.join('::'))
+ @rb_module
+ end
+
+ def eval(destructive=false)
+ elements.each do |element|
+ element.sql_module = self
+ element.eval(destructive)
+ end
+ end
+
+ def ddl
+ drop + "\n" + create
+ end
+
+ def create
+ statements = elements.collect {|each| each.create}\
+ .join("\n//\n")
+ "DELIMITER // \n#{statements}\n//"
+ end
+
+ def drop
+ statements = elements.collect {|each| each.drop}\
+ .reverse.join("\n//\n")
+ ['DELIMITER //',
+ 'SET FOREIGN_KEY_CHECKS = 0;',
+ statements,
+ 'SET FOREIGN_KEY_CHECKS = 1;'].join("\n//\n") + "\n//"
+ end
+ end
+
+ class Include
+ include Annotation
+
+ attr_reader :package_name
+ def initialize(package_name)
+ @package_name = package_name.to_sym
+ end
+
+ def sql_element
+ unless Include.const_get(package_name)
+ raise "unkown sql package name: #{package_name}"
+ end
+ clazz = Include.const_get(package_name)
+ clazz.new
+ end
+
+ #inner classes
+ class Pivot
+ include Element
+ def eval(destructive)
+ #nothing to do.
+ end
+
+ def create
+ return [
+ 'CREATE TABLE pivot (pos INTEGER) ENGINE = MYISAM',
+ (<<-END_SQL
+ INSERT INTO pivot
+ VALUES
+ (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),
+ (11),(12),(13),(14),(15),(16),(17),(18),(19),(20),
+ (21),(22),(23),(24),(25),(26),(27),(28),(29),(30),
+ (31),(32)
+ ;
+ END_SQL
+ ),
+
+ (<<-END_SQL
+ CREATE FUNCTION CSV_GET(
+ csv VARCHAR(1024),
+ pos INTEGER
+ ) RETURNS VARCHAR (1024) DETERMINISTIC
+ BEGIN
+ RETURN SUBSTRING_INDEX(SUBSTRING_INDEX(csv, ',', pos), ',', -1);
+ END
+ END_SQL
+ ),
+
+ (<<-END_SQL
+ CREATE FUNCTION CSV_LENGTH(
+ csv VARCHAR(1024)
+ ) RETURNS INTEGER DETERMINISTIC
+ BEGIN
+ IF csv IS NULL THEN
+ RETURN 1;
+ END IF;
+ RETURN LENGTH(csv) - LENGTH(REPLACE(csv, ',', '')) + 1;
+ END
+ END_SQL
+ )
+ ]
+ end
+
+ def drop
+ return [
+ 'DROP TABLE IF EXISTS pivot CASCADE',
+ 'DROP FUNCTION IF EXISTS CSV_GET',
+ 'DROP FUNCTION IF EXISTS CSV_LENGTH',
+ ]
+ end
+ end
+ end
+
+ class ResultSet < Array
+ def as(clazz)
+ replace(collect do |row|
+ row.as(clazz)
+ end)
+ end
+ end
+
+ class Table
+ include Element
+
+ #static methods
+ def self.[](name)
+ @instances[name.to_sym]
+ end
+ def self.register(table)
+ @instances[table.name] = table
+ end
+ @instances = {}
+
+ #instance methods
+ attr_reader :name, :elements, :annotations, :clazz
+ def initialize(name, elements, annotations=[])
+ @name = name.to_sym
+ @elements = elements.to_a
+ @annotations = annotations.to_a
+ annotations.each {|each| each.annotation_context = self}
+ end
+
+ def columns
+ unless @columns
+ @columns = elements.select {|each| each.is_a?(Column)}
+ end
+ @columns
+ end
+
+ def constraints
+ unless @constraints
+ @constraints = elements.select {|each| each.is_a?(Constraint)}
+ end
+ @constraints
+ end
+
+ def unique_constraints
+ constraints.select{|any| any.is_a?(Table::Constraint::Unique)}
+ end
+
+ def create
+ [ source ]
+ end
+
+ def drop
+ ["DROP TABLE IF EXISTS #{name} CASCADE"]
+ end
+
+ def eval(destructive)
+ Table.register(self)
+ @clazz = sql_module.namespace.const_defined?(name)\
+ ? augment_class\
+ : define_class
+ if classified_by
+ classified_by.define_subclasses
+ end
+ @clazz
+ end
+
+ def define_class
+ clazz = Class.new(Record)
+ define_accessors(clazz)
+ define_class_accessors(clazz)
+ sql_module.namespace.const_set(name, clazz)
+ clazz
+ end
+
+ def augment_class
+ clazz = sql_module.namespace.const_get(name)
+ define_accessors(clazz)
+ define_class_accessors(clazz)
+ clazz
+ end
+
+ def define_accessors(clazz)
+ metadata = self
+ clazz.module_eval do
+ metadata.columns.each do |column|
+ define_method(column.name) do
+ self[column.name]
+ end
+ setter_name = (column.name.to_s + '=').to_sym
+ define_method(setter_name) do |val|
+ column.validate(val)
+ self[column.name] = val
+ self
+ end
+ if column.name.to_s =~ /^is_(.+)$/
+ define_method($1 + '?') do
+ result = send(column.name)
+ return (result == 0) ? false : result
+ end
+ end
+ end
+ end
+ end
+
+ def define_class_accessors(clazz)
+ metadata = self
+ Hdboo.singleton_eval(clazz) do
+ define_method(:metadata) do
+ metadata
+ end
+ end
+ end
+
+ def classified_by
+ unless @classified_by
+ @classified_by = annotations.find {|each| each.is_a? ClassifiedBy}
+ end
+ @classified_by
+ end
+
+ class Column
+ attr_reader :name, :type, :options, :annotations
+ def initialize(name, type, options=[], annotations=[])
+ @name = name.to_sym
+ @type = type
+ @options = options.to_a
+ @annotations = annotations.to_a
+ annotations.each {|each| each.annotation_context = self}
+ end
+
+ def validators
+ unless @validators
+ validators = annotations.concat(options).push(@type)\
+ .select{|each| each.is_a?(Validator)}
+ @validators = Validator::ValidatorList.new(validators)
+ end
+ @validators
+ end
+
+ def validate(val)
+ validators.validate(val, name)
+ end
+ end
+
+ module Constraint
+ #static methods
+ def self.new(type, args)
+ type = type.to_sym
+ unless self.const_defined?(type)
+ raise "unknown constraint type: #{type.to_s}"
+ end
+ clazz = self.const_get(type)
+ clazz.new(*args)
+ end
+
+ #instance methods
+ attr_reader :name, :columns
+ attr_accessor :annotations
+
+ #inner classes
+ class Unique
+ include Constraint
+ def initialize(name, columns)
+ @name = name.to_sym
+ @columns = columns.to_a
+ @annotations = []
+ annotations.each {|each| each.annotation_context = self}
+ end
+ end
+
+ class PrimaryKey < Unique
+ include Constraint
+ end
+
+ class ReferencesConstraint
+ include Constraint
+ attr_reader :reference_table, :reference_columns
+ def initialize(name, columns, reference_table, reference_columns)
+ @name = name.to_sym
+ @columns = columns.to_a
+ @reference_table = reference_table.to_sym
+ @reference_columns = reference_columns.to_a
+ @annotations = []
+ annotations.each {|each| each.annotation_context = self}
+ end
+ end
+ end
+
+ class Index
+ attr_reader :name, :columns, :annotations
+ def initialize(name, columns)
+ @name = name.to_sym
+ @columns = columns
+ @annotations = []
+ annotations.each {|each| each.annotation_context = self}
+ end
+ end
+
+ class ClassifiedBy
+ include Annotation
+ attr_reader :column_name
+ def initialize(column_name)
+ @column_name = column_name.to_sym
+ end
+
+ def annotation_context=(table)
+ unless table.columns.any? {|any| any.name == column_name}
+ raise "undefined column: #{table.name}.#{@column_name}."
+ end
+ @annotation_context = table
+ end
+
+ def define_subclasses
+ table = annotation_context
+ subtype_names.each do |subtype_name|
+ namespace = table.sql_module.namespace
+ next if namespace.const_defined?(subtype_name)
+ subclass = Class.new(table.clazz)
+ namespace.const_set(subtype_name, subclass)
+ end
+ end
+
+ def subtype_names
+ power_type.clazz.constants.reject{|any| any == 'Enumerable'}
+ end
+
+ def power_type
+ table = annotation_context
+ power_type_name = table.constraints.find do |constraint|
+ constraint.is_a?(Constraint::ReferencesConstraint)\
+ && constraint.columns[0].to_sym == column_name
+ end.reference_table
+ Table[power_type_name]
+ end
+ end
+ end
+
+ class View
+ include Element
+ attr_reader :name, :from_tables, :column_names, :annotations
+ def initialize(name, from_tables=[], column_names=[], annotations=[])
+ @name = name.to_sym
+ @from_tables = from_tables.to_a
+ @column_names = column_names.to_a
+ @annotations = annotations.to_a
+ annotations.each {|each| each.annotation_context = self}
+ end
+
+ def eval
+ #TODO
+ end
+
+ def create
+ [source]
+ end
+
+ def drop
+ ["DROP VIEW IF EXISTS #{name} CASCADE"]
+ end
+ end
+
+ module DataType
+ include Validator
+ attr_reader :name, :length
+
+ def self.new(type, length=nil)
+ clazz = TYPE_TO_CLASS_MAP[type.to_sym]
+ clazz.new(length)
+ end
+
+ def initialize(length=nil)
+ @length = length
+ end
+
+ def value_of(value)
+ raise "#{self.class.name}#value_of() must be implemented."
+ end
+
+ class Integer
+ include DataType
+ def pass?(val)
+ if val.nil? || val.is_a?(TrueClass) || val.is_a?(FalseClass)
+ return true
+ end
+ if val.is_a?(::String) && (val =~ /([+|-]?[1-9][0-9]*|0)/)
+ val = val.to_i
+ end
+ unless val.is_a?(Numeric) && val.integer?
+ return false
+ end
+ length.nil? ? true\
+ : val < 10^length
+ end
+
+ def message(val)
+ length.nil?\
+ ? "must be integer, but was #{val}."\
+ : "must be integer of #{length} digits or less, but was #{val}."
+ end
+
+ def value_of(val)
+ if val.is_a?(Hash)
+ val = val.values.first
+ elsif val.is_a?(Array)
+ val = val.first
+ end
+ val.to_i
+ end
+ end
+
+ class Bool < Integer
+ include DataType
+ def initialize
+ @length = 1
+ end
+
+ def value_of(val)
+ if val.is_a?(Hash)
+ val = val.values.first
+ elsif val.is_a?(Array)
+ val = val.first
+ end
+ val != 0
+ end
+ end
+
+ class String
+ include DataType
+ def pass?(val)
+ val.nil? || val.is_a?(::String)
+ end
+
+ def message(val)
+ "must be String, but was #{val.class.name}."
+ end
+ def value_of(val)
+ if val.is_a?(Hash)
+ val = val.values.first
+ elsif val.is_a?(Array)
+ val = val.first
+ end
+ val.to_s
+ end
+ end
+
+ class DateTime
+ include SQL::DataType
+ def pass?(val)
+ true
+ #TODO
+ end
+
+ def message(val)
+ 'datetime'
+ #TODO
+ end
+ end
+
+ TYPE_TO_CLASS_MAP = {
+ :INTEGER => DataType::Integer,
+ :TINYINT => DataType::Integer,
+ :SMALLINT => DataType::Integer,
+ :BOOL => DataType::Bool,
+ :CHAR => DataType::String,
+ :VARCHAR => DataType::String,
+ :TEXT => DataType::String,
+ :DATETIME => DataType::DateTime,
+ :TIMESTAMP => DataType::DateTime
+ }
+ end
+
+ class Record < HashAsObject
+ include Validatable
+
+ #static methods
+ def self.metadata
+ Table.new('-', [], [])
+ end
+ def metadata
+ self.class.metadata
+ end
+
+ def self.identifier_keys
+ metadata.constraints\
+ .find {|some| some.is_a? Table::Constraint::PrimaryKey}\
+ .columns\
+ .map {|each| each.to_sym}
+ end
+
+ def identifier
+ self.class.identifier_keys.map {|key| self[key]}
+ end
+
+ def ==(other)
+ return super unless other.is_a? Record
+ return other == self if self.instance_of? Record
+ identifier == other.identifier
+ end
+
+ def as(clazz)
+ metadata = clazz.metadata
+ if metadata.classified_by
+ class_name = self[metadata.classified_by.column_name]
+ clazz = metadata.sql_module.namespace.const_get(class_name)
+ end
+ clazz.new.replace(self)
+ end
+
+ def validators_for(key)
+ result = []
+ key = key.to_sym
+ unless self.is_a?(Record)
+ result.concat(super)
+ end
+ column_for_key = metadata.columns.find{|any|any.name==key}
+ result.concat(column_for_key.validators) if column_for_key
+ result
+ end
+ end
+
+ class Procedure
+ include Element
+
+ #static methods
+ @instances = {}
+ def self.[](name)
+ @instances[name.to_sym]
+ end
+
+ def self.register(procedure)
+ @instances[procedure.name.to_sym] = procedure
+ end
+
+ #instance methods
+ attr_reader :name, :owner, :annotations, :parameters
+ def initialize(name, parameters, annotations)
+ @name = name
+ @parameters = parameters
+ parameters.each {|each| each.owner = self}
+ @annotations = annotations.to_a
+ annotations.each {|each| each.annotation_context = self}
+ end
+
+ def create
+ [source]
+ end
+
+ def drop
+ ["DROP PROCEDURE IF EXISTS #{name}"]
+ end
+
+ def eval(destructive)
+ Procedure.register(self)
+ unless name =~ /^([A-Z][A-Za-z0-9]+)_([\w]+)$/
+ return nil
+ end
+ class_name = $1
+ method_name = $2
+ clazz = sql_module.namespace.const_get(class_name)
+ @owner = clazz.metadata
+
+ Hdboo.singleton_eval(clazz, <<-EOS)
+ def #{method_name} (*args, &block)
+ Hdboo::SQL::Procedure[:#{name}].call(*args, &block)
+ end
+ EOS
+
+ clazz.module_eval(<<-EOS)
+ def #{method_name}(kargs={}, &block)
+ kargs = Hdboo.normalize_key(kargs)
+ kargs = self.to_hash.merge(kargs.to_hash)
+ Hdboo::SQL::Procedure[:#{name}].call(kargs, &block)
+ end
+
+ def #{method_name}! (kargs={}, &block)
+ result = #{method_name}(kargs, &block)
+ new_status = result.is_a?(Array)\
+ ? result[0]\
+ : result
+ unless new_status
+ raise NoDataFound, '#{name}', caller
+ end
+ replace(new_status)
+ end
+ EOS
+
+ if master_data_loader && destructive
+ master_data_loader.load
+ end
+
+ if constants_definition
+ constants_definition.define_on(clazz)
+ end
+ self
+ end
+
+ def master_data_loader
+ annotations.find {|any| any.is_a? MasterData}
+ end
+
+ def constants_definition
+ annotations.find {|any| any.is_a? DefineConstants}
+ end
+
+ def call(*args, &block)
+ kargs = nil
+ if args.length == 0
+ kargs = {}
+ elsif args.length == 1 && args.first.respond_to?(:to_hash)
+ kargs = Hdboo.normalize_key(args.first.to_hash)
+ else
+ kargs = {}
+ parameters.each_with_index do |param, i|
+ kargs[param.name.to_sym] = args[i]
+ end
+ end
+
+ intercepters.reverse.each do |intercepter|
+ intercepter.before(kargs, self)
+ end
+
+ invocation = nil
+ intercepters.reverse.each do |intercepter|
+ wrapee = invocation
+ invocation = lambda do |kargs|
+ intercepter.around(wrapee, kargs, self, &block)
+ end
+ end
+
+ begin
+ result = invocation.call(kargs)
+ rescue
+ intercepters.each do |intercepter|
+ intercepter.after_error($!, self)
+ end
+ end
+
+ intercepters.each do |intercepter|
+ result = intercepter.after(result, self)
+ end
+ result
+ end
+
+ def before(kargs, procedure)
+ errors = Hash.new {|hash, key| hash[key] = []}
+ parameters.each do |parameter|
+ value = kargs[parameter.name]
+ if parameter.required? && value.nil?
+ errors[parameter.name] << 'required.'
+ else
+ parameter.validators.each do |validator|
+ unless validator.pass?(value)
+ errors[parameter.name] << validator.message(value)
+ end
+ end
+ end
+ end
+ unless errors.empty?
+ raise InvalidArgument, errors
+ end
+ end
+
+ def around(invocation, kargs, procedure, &block)
+ args = parameters.collect {|parameter| kargs[parameter.name]}
+ Database.call_procedure(name, args, &block)
+ end
+
+ def after(result, procedure)
+ result
+ end
+
+ def after_error(error, procedure)
+ raise error
+ end
+
+ def intercepters
+ unless @intercepters
+ @intercepters = @annotations\
+ .select {|each| each.kind_of?(Intercepter)}\
+ .unshift(self)\
+ .reverse
+ end
+ @intercepters
+ end
+
+ #inner classes
+ class Parameter
+ attr_reader :name, :type, :annotations
+ attr_accessor :owner
+
+ def initialize(name, type, annotations)
+ @name = name.sub(/^_+/, '').to_sym
+ @type = type
+ @annotations = annotations
+ @optional = nil
+ annotations.each {|each| each.annotation_context = self}
+ end
+
+ def annotations= (annotations)
+ annotations.each {|each| each.annotation_context = self}
+ @annotations = annotations
+ end
+
+ def validators
+ return @validators if @validators
+ @validators = annotations.map do |annotation|
+ if annotation.is_a?(ColumnValue)
+ annotation.validators
+ elsif annotation.is_a?(Validator)
+ annotation
+ else
+ nil
+ end
+ end.flatten.compact
+ end
+
+ def optional?
+ if @optional.nil?
+ @optional = annotations.any? {|each| each.is_a?(Optional)}
+ end
+ @optional
+ end
+
+ def required?
+ !optional?
+ end
+
+
+ class Optional
+ include Annotation
+ attr_reader :default_value
+ def intialize(default_value=nil)
+ @default_value = default_value
+ end
+ end
+
+ class ColumnValue
+ include Annotation
+ attr_reader :table_name
+ def initialize(table_name=nil)
+ @table_name = table_name
+ end
+
+ def column_name
+ annotation_context.name
+ end
+
+ def validators
+ procedure = annotation_context.owner
+ namespace = procedure.sql_module.namespace
+ table = table_name.nil?\
+ ? procedure.owner\
+ : namespace.const_get(table_name.to_sym).metadata
+ column = table.columns.find {|each| each.name == column_name}
+ @validators = column.validators
+ end
+ end
+ end
+
+ class Intercepter
+ def before(kargs, procedure)
+ end
+
+ def around(invocation, kargs, procedure, &block)
+ invocation.call(kargs, &block)
+ end
+
+ def after(result, procedure)
+ result
+ end
+
+ def after_error(error, procedure)
+ raise error
+ end
+
+ class OnNoData < Intercepter
+ include Annotation
+ attr_reader :message_key, :message_text
+ def initialize(message_key, message_text)
+ @message_key = message_key
+ @message_text = message_text
+ end
+
+ def after(result, procedure)
+ if result.nil? || (result.is_a?(Array) && result.empty?)
+ raise NoDataFound.new({@message_key => [@message_text]})
+ end
+ result
+ end
+ end
+
+ class OnExists < Intercepter
+ include Annotation
+ attr_reader :message_key, :message_text
+ def initialize(message_key, message_text)
+ @message_key = message_key
+ @message_text = message_text
+ end
+
+ def after(result, procedure)
+ unless result.nil? || (result.is_a?(Array) && result.empty?)
+ raise DataConflict.new({@message_key => [@message_text]})
+ end
+ result
+ end
+ end
+
+ class OnConflict < Intercepter
+ include Annotation
+ attr_reader :constraint_name, :message_key, :message_text
+ def initialize(constraint_name, message_key, message_text)
+ @constraint_name = constraint_name
+ @message_key = message_key
+ @message_text = message_text
+ end
+
+ def after_error(error, procedure)
+ return unless error.sqlstate == '23000'
+ table = procedure.owner
+ if error.message =~ /Duplicate entry '(.*?)' for key '(.+?)'/
+ #seq_num = $1.to_i - 1
+ #violated_constraint = table.unique_constraints[seq_num]
+ #if violated_constraint.name == constraint_name.to_sym
+
+ if constraint_name == $2
+ raise DataConflict, {@message_key => [@message_text]}
+ end
+ elsif error.message =~ /foreign key constraint fails/
+ if error.message.include?(constraint_name.to_s)
+ raise DataConflict, {@message_key => [@message_text]}
+ end
+ end
+ #raise error
+ end
+ end
+
+ class Returns < Intercepter
+ include Annotation
+ attr_reader :return_type
+
+ def initialize(return_type=Record)
+ if return_type.is_a?(DataType)
+ @return_type = return_type
+ elsif return_type.is_a?(String)
+ @return_type = return_type.to_sym == :Bool\
+ ? DataType::Bool.new\
+ : return_type.to_sym
+ else
+ @return_type = return_type
+ end
+ end
+
+ def after(result, procedure)
+ if return_type.is_a?(Symbol)
+ type = procedure.sql_module.namespace.const_get(return_type)
+ raise "unknown type: #{return_type.to_s}" if type.nil?
+ @return_type = type
+ end
+ return nil if result.nil?
+ unless result.is_a?(Array)
+ result = RecordSet.new([result])
+ end
+ result.replace(result.collect do |record|
+ return_type.is_a?(DataType)\
+ ? return_type.value_of(record)\
+ : record.as(return_type)
+ end)
+ result
+ end
+ end
+
+ class ReturnsFirst < Returns
+ def after(result, procedure)
+ super.first
+ end
+ end
+ end
+
+ class MasterData
+ include Annotation
+ def load
+ procedure = annotation_context
+ procedure.call
+ end
+ end
+
+ class DefineConstants
+ include Annotation
+ attr_reader :key_column_name
+ def initialize(key_column_name)
+ @key_column_name = key_column_name.to_sym
+ end
+
+ def define_on(clazz)
+ procedure = annotation_context
+ procedure.call.each do |record|
+ constant_key = record[key_column_name]
+ unless clazz.const_defined?(constant_key)
+ clazz.const_set(constant_key, record)
+ end
+ end
+ end
+ end
+ end
+end
+end
--- /dev/null
+#
+# DO NOT MODIFY!!!!
+# This file is automatically generated by racc 1.4.5
+# from racc grammer file "sqlparser.y".
+#
+
+require 'racc/parser'
+
+
+require 'strscan'
+
+
+module Hdboo
+
+ module SQL
+
+ class Parser < Racc::Parser
+
+module_eval <<'..end sqlparser.y modeval..idadf64e57b3', 'sqlparser.y', 371
+def constraint(type, *args)
+ all_elements = args.pop
+ nd(:CONSTRAINT, SQL::Table::Constraint.new(type, args), all_elements)
+end
+
+def index(name, columns, all_elements)
+ nd(:INDEX, SQL::Table::Index.new(name, columns), all_elements)
+end
+
+def procedure(name, parameters, annotations, all_elements)
+ nd(:CREATE_PROCEDURE,
+ SQL::Procedure.new(
+ name.value,
+ parameters.to_a,
+ annotations.to_a
+ ),
+ all_elements)
+end
+
+def parameter(name, type, annotations, all_elements)
+ nd(:PARAMETER,
+ SQL::Procedure::Parameter.new(
+ name.value,
+ type.value,
+ annotations.to_a
+ ),
+ all_elements)
+end
+
+def table(name, elements, annotations, all_elements)
+ nd(:CREATE_TABLE,
+ SQL::Table.new(name.value, elements.to_a, annotations.to_a),
+ all_elements)
+end
+
+def column(name, type, column_options, annotations, all_elements)
+ nd(:COLUMN_DEFINITION,
+ SQL::Table::Column.new(
+ name.value, type.value, column_options.to_a, annotations.to_a
+ ),
+ all_elements)
+end
+
+def annotation(type, args, all_elements)
+ type = type.value
+ args = args.to_a
+ nd(:ANNOTATION, SQL::Annotation.new(type, args), all_elements)
+end
+
+def datatype(type, length, val)
+ nd(:DATA_TYPE, SQL::DataType.new(type, length), val)
+end
+
+def nd(type, value, all_elements=value)
+ node = Node.new(type, value, @source)
+ update_pos(node, all_elements)
+ if value.respond_to?(:source=)
+ value.source = node.source
+ end
+ node
+end
+
+def update_pos(node, all_elements)
+ all_elements = all_elements.compact
+ node.start = all_elements.collect{|val| val.start}.compact[0]
+ node.last = all_elements.collect{|val| val.last }.compact[-1]
+ node
+end
+
+class Node
+ attr_reader :type, :value
+ attr_accessor :start, :last
+
+ def initialize(type, value, source)
+ @type = type.to_sym
+ @value = value
+ @source = source
+ end
+
+ def push(elem)
+ raise Exception if leaf?
+ @value << elem
+ self
+ end
+
+ def unshift(elem)
+ raise Exception if leaf?
+ @value.unshift(elem)
+ self
+ end
+
+ def [](index)
+ if leaf?
+ return index = 0 ? @value : nil
+ end
+ if index.is_a?(Integer)
+ return @value[index]
+ end
+ @value.detect{|val| val.type == index.to_sym}
+ end
+
+ def leaf?
+ !@value.is_a?(Array)
+ end
+
+ def name
+ self[:IDENTIFIER].value
+ end
+
+ def children
+ leaf? ? [] : @value
+ end
+
+ def find(node_type)
+ node_type = node_type.to_sym
+ return self if type == node_type
+ if leaf?
+ return nil
+ end
+ result = nil
+ children.each do |child_node|
+ found = child_node.find(node_type)
+ if found
+ result = found
+ break
+ end
+ end
+ result
+ end
+
+ def source
+ start_row = @start[0]
+ start_col = @start[1]
+ last_row = @last[0]
+ last_col = @last[1]
+
+ result = @source[start_row..last_row]
+ if result.length == 1
+ result[0] = result[0][start_col..last_col]
+ else
+ result[0] = result[0][start_col..-1]
+ result[-1] = result[-1][0..last_col]
+ end
+ result.join("\n")
+ end
+
+ def to_a
+ if leaf?
+ return [@value]
+ end
+ @value.collect {|child| child.value}
+ end
+
+ def to_s
+ "#{type}: at line #{start[0]+1}, column #{start[1]+1}\nsource> #{source}"
+ end
+
+ def inspect
+ to_s
+ end
+end
+
+class ASTWalker
+ def walk(ast)
+ walk_node(ast)
+ self
+ end
+
+ def initialize
+ @listeners = Hash.new {|hash, key| hash[key] = []}
+ end
+
+ def visit(note_type, &block)
+ node_type = note_type.to_sym
+ @listeners[node_type] << block
+ self
+ end
+
+ private
+ def walk_node(node)
+ if @listeners.has_key?(node.type)
+ @listeners[node.type].each do |listener|
+ listener.call(node)
+ end
+ end
+ node.children.each do |child_node|
+p node if child_node.nil?
+ walk_node(child_node)
+ end
+ end
+end
+
+# token
+PUNCTUATION = /([;\.,()=@*<>\[\]]|\$\$|\/\/|--)/
+ONE_LINE_COMMENT = /--\s[^@]*$/
+KEYWORD_LIST = [
+ #general term
+ 'NOT',
+ 'NULL',
+ 'DEFAULT',
+ 'AS',
+
+ # sql command
+ 'CREATE',
+ 'TABLE',
+ 'VIEW',
+ 'PROCEDURE',
+ 'INSERT',
+ 'INTO',
+ 'VALUES',
+ 'DELIMITER',
+
+ # table option
+ 'ENGINE',
+ 'MYISAM',
+ 'INNODB',
+ 'HEAP',
+
+ # column option
+ 'AUTO_INCREMENT',
+
+ # data type
+ 'INT',
+ 'CHAR',
+ 'VARCHAR',
+ 'TEXT',
+ 'INTEGER',
+ 'TINYINT',
+ 'SMALLINT',
+ 'BIT',
+ 'DATETIME',
+ 'TIMESTAMP',
+
+ # procedure / function
+ 'BEGIN',
+ 'END',
+ 'IN',
+ 'OUT',
+ 'INOUT',
+
+ # constraint
+ 'CONSTRAINT',
+ 'PRIMARY',
+ 'FOREIGN',
+ 'REFERENCES',
+ 'UNIQUE',
+ 'INDEX',
+ 'KEY',
+ 'FULLTEXT',
+ 'USING',
+ 'SECTIONALIZE',
+
+ #query
+ 'SELECT',
+ 'FROM',
+ 'WHERE',
+
+ #dml
+ 'DELETE',
+ 'UPDATE',
+
+ #boolean expression
+ 'TRUE',
+ 'FALSE',
+ 'IS',
+
+ #triggered action
+ 'ON',
+ 'CASCADE'
+]
+KEYWORD = /(#{KEYWORD_LIST.collect{|k| '\b'+k+'\b'}.join('|')})/ix
+IDENTIFIER = /([a-zA-Z][_0-9a-zA-Z]*)/
+
+# NUMBER
+NUMERIC = /([1-9][0-9]*)/
+INTEGER = /([+|-]?[1-9][0-9]*|0)/
+FLOAT = /(
+ (?: #without exponent part
+ [+|-]?(?:[1-9][0-9]*|0)(?:\.[0-9]+)?
+ | #with exponent part
+ [+|-]?[1-9]+(?:\.[0-9]+)?e[+|-]?[1-9][0-9]*
+ )
+ )/x
+
+# STRING
+NOT_PRINTABLE = '\x00-\x1F\x7F' #including space (\x20)
+MUST_BE_ESCAPED = '\x22\x27\x5C' #quot, double-quot, back-slash
+ESCAPE_TABLE = {
+ '\n' => "\n",
+ '\b' => "\b",
+ '\0' => "\x00",
+ '\t' => "\t",
+ "\\'" => "'",
+ '\"' => '"',
+ "\\\\" => "\\"
+}
+ESCAPED = ESCAPE_TABLE.keys.map{|k|k.gsub(/\\/, "\\\\\\\\")}.join('|')
+CHARACTER_STRING = /(
+ #single-quoted string
+ '
+ (?:
+ [^#{MUST_BE_ESCAPED}#{NOT_PRINTABLE}]
+ |
+ #{ESCAPED}
+ |
+ ''
+ |
+ "
+ )*
+ '
+ |
+ #double-quoted string
+ "
+ (?:
+ [^#{MUST_BE_ESCAPED}#{NOT_PRINTABLE}]
+ |
+ #{ESCAPED}
+ |
+ ""
+ |
+ '
+ )*
+ "
+)/x
+
+# PROCEDURE
+PARAMETER_NAME = /([_a-zA-Z][_0-9a-zA-Z]*)/
+
+def parse(str)
+ @source = str.split(/\n/)
+ @lines = @source.dup
+ @total_lines = @lines.length
+ next_line
+ do_parse
+end
+
+def next_line
+ @line = @lines.empty? \
+ ? nil \
+ : StringScanner.new(@lines.shift)
+end
+
+def current_line
+ @total_lines - (@lines.length + 1)
+end
+
+def pos
+ [current_line, @line.pos]
+end
+
+def next_token
+ token = _next_token
+ return nil unless token
+ token[1].start = @start_pos
+ token[1].last = [pos[0], pos[1]-1]
+#puts token
+ return token
+end
+
+def _next_token
+ return nil unless @line
+ @line.skip(/\s*/)
+ if @in_procedure_code
+ if token = @line.skip_until(/(?=\bEND(?!\s+(?:IF|CASE|WHILE|LOOP))\b)/i)
+ @in_procedure_code = false
+ return tkn(:PROCEDURE_CODE)
+ end
+ @line.terminate
+ end
+ if @line.eos?
+ next_line
+ return _next_token
+ end
+ @start_pos = pos
+ if token = @line.scan(ONE_LINE_COMMENT)
+ return _next_token
+ elsif token = @line.scan(PUNCTUATION)
+ return tkn(token)
+ elsif token = @line.scan(KEYWORD)
+ token = token.upcase
+ if token == 'BEGIN'
+ @in_procedure_code = true
+ end
+ return tkn(token)
+ elsif token = @line.scan(IDENTIFIER)
+ return tkn(:IDENTIFIER, token)
+ elsif token = @line.scan(PARAMETER_NAME)
+ return tkn(:PARAMETER_NAME, token)
+ elsif token = @line.scan(CHARACTER_STRING)
+ return tkn(
+ :CHARACTER_STRING,
+ token[1..-2].gsub(/#{ESCAPED}/x) {|s|ESCAPE_TABLE[s]}\
+ .gsub(/""/, '"')\
+ .gsub(/''/, "\'")
+ )
+ elsif token = @line.scan(NUMERIC)
+ return tkn(:NUMERIC, token.to_i)
+ elsif token = @line.scan(INTEGER)
+ return tkn(:INTEGER, token.to_i)
+ elsif token = @line.scan(FLOAT)
+ return tkn(:FLOAT, token.to_f)
+ else
+ return tkn(:UNEXPECTED_TOKEN, @line.scan(/.*/))
+ end
+end
+
+def tkn(type, value=type)
+ [type, Node.new(type, value, @source)]
+end
+
+..end sqlparser.y modeval..idadf64e57b3
+
+##### racc 1.4.5 generates ###
+
+racc_reduce_table = [
+ 0, 0, :racc_error,
+ 2, 76, :_reduce_1,
+ 1, 77, :_reduce_2,
+ 0, 78, :_reduce_3,
+ 1, 78, :_reduce_4,
+ 3, 78, :_reduce_5,
+ 3, 78, :_reduce_6,
+ 3, 78, :_reduce_7,
+ 1, 80, :_reduce_none,
+ 1, 80, :_reduce_none,
+ 1, 80, :_reduce_none,
+ 1, 80, :_reduce_none,
+ 1, 80, :_reduce_none,
+ 1, 86, :_reduce_none,
+ 3, 86, :_reduce_14,
+ 8, 81, :_reduce_15,
+ 6, 82, :_reduce_16,
+ 0, 88, :_reduce_17,
+ 2, 88, :_reduce_18,
+ 3, 90, :_reduce_19,
+ 1, 91, :_reduce_none,
+ 1, 91, :_reduce_none,
+ 1, 91, :_reduce_none,
+ 1, 87, :_reduce_23,
+ 3, 87, :_reduce_24,
+ 1, 92, :_reduce_none,
+ 1, 92, :_reduce_none,
+ 4, 93, :_reduce_27,
+ 0, 96, :_reduce_28,
+ 2, 96, :_reduce_29,
+ 2, 97, :_reduce_30,
+ 1, 97, :_reduce_31,
+ 2, 97, :_reduce_32,
+ 2, 97, :_reduce_33,
+ 1, 97, :_reduce_34,
+ 1, 94, :_reduce_none,
+ 1, 94, :_reduce_none,
+ 1, 94, :_reduce_none,
+ 1, 94, :_reduce_none,
+ 1, 94, :_reduce_none,
+ 6, 99, :_reduce_40,
+ 7, 100, :_reduce_41,
+ 3, 106, :_reduce_42,
+ 6, 106, :_reduce_43,
+ 0, 107, :_reduce_44,
+ 3, 107, :_reduce_45,
+ 5, 101, :_reduce_46,
+ 0, 104, :_reduce_47,
+ 2, 104, :_reduce_48,
+ 5, 102, :_reduce_49,
+ 6, 103, :_reduce_50,
+ 2, 109, :_reduce_51,
+ 0, 108, :_reduce_52,
+ 1, 108, :_reduce_53,
+ 1, 105, :_reduce_54,
+ 3, 105, :_reduce_55,
+ 10, 83, :_reduce_56,
+ 0, 110, :_reduce_57,
+ 1, 110, :_reduce_58,
+ 4, 110, :_reduce_59,
+ 4, 111, :_reduce_60,
+ 1, 113, :_reduce_none,
+ 1, 113, :_reduce_none,
+ 0, 112, :_reduce_none,
+ 1, 112, :_reduce_none,
+ 1, 112, :_reduce_none,
+ 1, 112, :_reduce_none,
+ 0, 79, :_reduce_67,
+ 3, 79, :_reduce_68,
+ 1, 114, :_reduce_69,
+ 2, 114, :_reduce_70,
+ 2, 115, :_reduce_71,
+ 5, 115, :_reduce_72,
+ 0, 116, :_reduce_73,
+ 1, 116, :_reduce_74,
+ 3, 116, :_reduce_75,
+ 1, 117, :_reduce_none,
+ 1, 117, :_reduce_none,
+ 1, 117, :_reduce_none,
+ 4, 84, :_reduce_79,
+ 1, 118, :_reduce_none,
+ 2, 119, :_reduce_81,
+ 1, 120, :_reduce_82,
+ 3, 120, :_reduce_83,
+ 3, 121, :_reduce_84,
+ 0, 122, :_reduce_85,
+ 1, 122, :_reduce_86,
+ 3, 122, :_reduce_87,
+ 1, 123, :_reduce_none,
+ 1, 123, :_reduce_none,
+ 1, 95, :_reduce_90,
+ 1, 95, :_reduce_91,
+ 1, 95, :_reduce_92,
+ 1, 95, :_reduce_93,
+ 4, 95, :_reduce_94,
+ 4, 95, :_reduce_95,
+ 4, 95, :_reduce_96,
+ 4, 95, :_reduce_97,
+ 4, 95, :_reduce_98,
+ 1, 95, :_reduce_99,
+ 1, 95, :_reduce_100,
+ 1, 95, :_reduce_101,
+ 1, 95, :_reduce_102,
+ 1, 98, :_reduce_none,
+ 1, 98, :_reduce_none,
+ 1, 124, :_reduce_none,
+ 1, 124, :_reduce_none,
+ 1, 124, :_reduce_none,
+ 1, 85, :_reduce_none,
+ 3, 89, :_reduce_109,
+ 1, 125, :_reduce_none,
+ 1, 125, :_reduce_none,
+ 1, 127, :_reduce_none,
+ 3, 127, :_reduce_none,
+ 2, 126, :_reduce_114,
+ 2, 128, :_reduce_115,
+ 2, 129, :_reduce_116,
+ 1, 130, :_reduce_none,
+ 1, 131, :_reduce_118,
+ 3, 131, :_reduce_119,
+ 1, 132, :_reduce_120,
+ 3, 132, :_reduce_121,
+ 1, 133, :_reduce_122,
+ 2, 133, :_reduce_123,
+ 1, 134, :_reduce_124,
+ 3, 134, :_reduce_125,
+ 4, 134, :_reduce_126,
+ 1, 136, :_reduce_none,
+ 1, 136, :_reduce_none,
+ 1, 135, :_reduce_none,
+ 1, 137, :_reduce_none,
+ 3, 138, :_reduce_131,
+ 1, 139, :_reduce_none,
+ 1, 139, :_reduce_none,
+ 1, 140, :_reduce_134,
+ 1, 140, :_reduce_135,
+ 2, 140, :_reduce_136,
+ 1, 140, :_reduce_137,
+ 2, 140, :_reduce_138 ]
+
+racc_reduce_n = 139
+
+racc_shift_n = 240
+
+racc_action_table = [
+ 119, 57, 44, 60, 62, 64, 67, 78, 16, 151,
+ 186, 17, 5, 57, 14, 60, 62, 64, 67, 233,
+ 104, 217, 196, 105, 60, 62, 64, 67, 60, 62,
+ 64, 67, 14, 222, 93, 94, 18, 232, 106, 5,
+ 5, 5, 80, 83, 86, 199, 78, 224, 225, 9,
+ 181, 107, 71, 73, 54, 55, 56, 59, 61, 63,
+ 65, 68, 12, 122, 71, 73, 54, 55, 56, 59,
+ 61, 63, 65, 68, 214, 215, 9, 9, 9, 224,
+ 225, 80, 83, 86, -57, 193, 190, 191, -57, 12,
+ 12, 12, 14, 160, 161, 162, 163, 196, 108, 60,
+ 62, 64, 67, 196, 110, 60, 62, 64, 67, 160,
+ 161, 162, 163, 46, 48, 50, 14, 46, 48, 50,
+ 199, 111, 14, 20, 21, 19, 199, 71, 73, 54,
+ 55, 56, 59, 61, 63, 65, 68, 71, 73, 54,
+ 55, 56, 59, 61, 63, 65, 68, 115, 57, 116,
+ 60, 62, 64, 67, 114, 196, 112, 60, 62, 64,
+ 67, 196, 102, 60, 62, 64, 67, 57, 112, 60,
+ 62, 64, 67, 118, 101, 96, 52, 127, 128, 107,
+ 129, 130, 131, 132, 92, 134, 135, 91, 90, 138,
+ 139, 140, 141, 142, 146, 147, 148, 52, 42, 39,
+ 155, 156, 157, 158, 159, 38, 166, 166, 169, 170,
+ 171, 172, 146, 174, 119, 176, 177, 179, 14, 180,
+ 36, 35, 184, 185, 103, 166, 166, 166, 24, 33,
+ 206, 166, 208, 209, 210, 211, 212, 29, 28, 218,
+ 220, 27, 26, 226, 227, 24, 22, 230, 15, 211,
+ 14, 235, 166, 237, 238, 232 ]
+
+racc_action_check = [
+ 90, 42, 37, 42, 42, 42, 42, 111, 5, 123,
+ 168, 5, 1, 106, 123, 106, 106, 106, 106, 230,
+ 59, 202, 177, 66, 177, 177, 177, 177, 162, 162,
+ 162, 162, 37, 212, 49, 49, 5, 230, 70, 19,
+ 20, 21, 111, 111, 111, 177, 43, 222, 222, 1,
+ 162, 72, 42, 42, 42, 42, 42, 42, 42, 42,
+ 42, 42, 1, 90, 106, 106, 106, 106, 106, 106,
+ 106, 106, 106, 106, 202, 202, 19, 20, 21, 212,
+ 212, 43, 43, 43, 124, 174, 174, 174, 38, 19,
+ 20, 21, 43, 165, 165, 165, 165, 211, 73, 211,
+ 211, 211, 211, 218, 80, 218, 218, 218, 218, 136,
+ 136, 136, 136, 124, 124, 124, 124, 38, 38, 38,
+ 211, 82, 109, 6, 6, 6, 218, 109, 109, 109,
+ 109, 109, 109, 109, 109, 109, 109, 95, 95, 95,
+ 95, 95, 95, 95, 95, 95, 95, 84, 52, 84,
+ 52, 52, 52, 52, 84, 199, 83, 199, 199, 199,
+ 199, 216, 55, 216, 216, 216, 216, 128, 86, 128,
+ 128, 128, 128, 88, 54, 51, 96, 97, 98, 99,
+ 101, 102, 103, 104, 47, 107, 108, 45, 44, 113,
+ 114, 115, 116, 117, 118, 119, 120, 39, 35, 33,
+ 129, 130, 131, 132, 135, 29, 138, 139, 140, 141,
+ 142, 143, 145, 146, 147, 148, 150, 151, 153, 160,
+ 27, 24, 166, 167, 56, 169, 170, 172, 23, 22,
+ 179, 184, 187, 188, 189, 194, 198, 18, 17, 205,
+ 209, 16, 15, 214, 215, 14, 9, 220, 3, 229,
+ 2, 232, 233, 235, 236, 238 ]
+
+racc_action_pointer = [
+ nil, -1, 202, 248, nil, -6, 114, nil, nil, 195,
+ nil, nil, nil, nil, 196, 242, 239, 236, 235, 26,
+ 27, 28, 227, 179, 219, nil, nil, 205, nil, 190,
+ nil, nil, nil, 147, nil, 183, nil, -16, 72, 182,
+ nil, nil, -1, 44, 124, 171, nil, 160, nil, 32,
+ nil, 151, 146, nil, 159, 147, 209, nil, nil, 5,
+ nil, nil, nil, nil, nil, nil, 7, nil, nil, nil,
+ 14, nil, 39, 83, nil, nil, nil, nil, nil, nil,
+ 102, nil, 97, 154, 118, nil, 166, nil, 157, nil,
+ -2, nil, nil, nil, nil, 84, 161, 161, 154, 167,
+ nil, 176, 177, 178, 179, nil, 11, 183, 182, 74,
+ nil, 5, nil, 174, 175, 161, 162, 153, 175, 171,
+ 130, nil, nil, -34, 68, nil, nil, nil, 165, 184,
+ 185, 186, 187, nil, nil, 188, 84, nil, 204, 205,
+ 193, 194, 169, 196, nil, 193, 193, 212, 213, nil,
+ 149, 209, nil, 170, nil, nil, nil, nil, nil, nil,
+ 193, nil, 24, nil, nil, 68, 198, 207, -6, 223,
+ 224, nil, 225, nil, 64, nil, nil, 20, nil, 186,
+ nil, nil, nil, nil, 229, nil, nil, 216, 217, 218,
+ nil, nil, nil, nil, 166, nil, nil, nil, 166, 153,
+ nil, nil, 1, nil, nil, 171, nil, nil, nil, 208,
+ nil, 95, 8, nil, 223, 224, 159, nil, 101, nil,
+ 245, nil, -24, nil, nil, nil, nil, nil, nil, 180,
+ 4, nil, 217, 250, nil, 218, 238, nil, 222, nil ]
+
+racc_action_default = [
+ -67, -3, -2, -139, -1, -139, -4, -8, -9, -139,
+ -10, -11, -108, -12, -139, -139, -139, -139, -139, -3,
+ -3, -3, -139, -68, -139, -69, 240, -139, -67, -139,
+ -7, -5, -6, -139, -70, -71, -67, -139, -63, -139,
+ -79, -80, -73, -47, -139, -139, -64, -58, -65, -139,
+ -66, -82, -85, -81, -92, -93, -139, -13, -104, -139,
+ -105, -99, -103, -100, -106, -101, -139, -107, -102, -78,
+ -74, -90, -77, -91, -76, -35, -36, -37, -67, -38,
+ -139, -39, -23, -52, -139, -25, -52, -26, -139, -16,
+ -139, -67, -67, -61, -62, -139, -139, -139, -86, -89,
+ -88, -139, -139, -139, -139, -72, -73, -139, -139, -139,
+ -48, -47, -53, -139, -139, -139, -139, -139, -17, -112,
+ -139, -111, -110, -139, -63, -67, -83, -84, -85, -139,
+ -139, -139, -139, -75, -14, -139, -28, -24, -139, -139,
+ -139, -139, -139, -139, -15, -17, -139, -139, -139, -109,
+ -139, -139, -59, -60, -87, -95, -96, -97, -98, -94,
+ -139, -31, -139, -34, -27, -28, -54, -139, -139, -139,
+ -139, -51, -139, -18, -139, -113, -115, -139, -114, -139,
+ -30, -33, -32, -29, -139, -49, -46, -139, -139, -139,
+ -21, -22, -19, -20, -118, -120, -132, -122, -124, -139,
+ -129, -130, -139, -116, -133, -117, -56, -55, -40, -139,
+ -50, -139, -139, -123, -135, -137, -139, -134, -139, -41,
+ -139, -121, -139, -125, -127, -128, -136, -138, -131, -119,
+ -44, -126, -139, -139, -42, -139, -139, -45, -44, -43 ]
+
+racc_goto_table = [
+ 2, 74, 45, 167, 168, 97, 99, 88, 66, 53,
+ 121, 100, 194, 164, 144, 125, 223, 234, 25, 113,
+ 4, 23, 117, 95, 143, 239, 231, 34, 37, 136,
+ 40, 41, 219, 192, 187, 188, 43, 189, 30, 31,
+ 32, 173, 183, 120, 149, 89, 150, 178, 203, 207,
+ 205, 3, 221, 229, 213, 1, 228, 216, nil, nil,
+ nil, nil, nil, nil, nil, 74, 126, 175, nil, nil,
+ nil, nil, 133, nil, nil, 137, nil, nil, 109, nil,
+ nil, 154, 99, nil, nil, nil, nil, 100, 152, nil,
+ nil, 123, 124, nil, nil, nil, nil, nil, 236, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, 182, nil, nil, nil, 153 ]
+
+racc_goto_check = [
+ 4, 23, 35, 30, 30, 47, 11, 12, 41, 45,
+ 52, 23, 57, 21, 13, 20, 61, 32, 40, 33,
+ 3, 39, 33, 38, 34, 32, 61, 40, 4, 20,
+ 43, 44, 31, 16, 30, 30, 4, 30, 3, 3,
+ 3, 13, 21, 50, 51, 14, 53, 54, 55, 30,
+ 56, 1, 58, 57, 59, 2, 64, 65, nil, nil,
+ nil, nil, nil, nil, nil, 23, 45, 52, nil, nil,
+ nil, nil, 41, nil, nil, 12, nil, nil, 4, nil,
+ nil, 47, 11, nil, nil, nil, nil, 23, 35, nil,
+ nil, 4, 4, nil, nil, nil, nil, nil, 30, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, 23, nil, nil, nil, 4 ]
+
+racc_goto_pointer = [
+ nil, 51, 55, 19, 0, nil, nil, nil, nil, nil,
+ nil, -46, -36, -104, 1, nil, -141, nil, nil, nil,
+ -80, -123, nil, -41, nil, nil, nil, nil, nil, nil,
+ -135, -177, -213, -64, -93, -36, nil, nil, -26, 7,
+ 4, -34, nil, -3, -2, -30, nil, -47, nil, nil,
+ -47, -76, -80, -74, -103, -129, -127, -165, -159, -145,
+ nil, -196, nil, nil, -160, -145 ]
+
+racc_goto_default = [
+ nil, nil, nil, nil, nil, 6, 7, 8, 10, 11,
+ 13, 72, nil, nil, nil, 145, nil, 82, 85, 87,
+ 69, nil, 165, 204, 75, 76, 77, 79, 81, 84,
+ nil, nil, nil, nil, nil, nil, 47, 49, nil, nil,
+ nil, nil, 70, nil, nil, nil, 51, nil, 98, 58,
+ nil, nil, nil, nil, nil, nil, nil, nil, 195, 197,
+ 198, nil, 200, 201, 202, nil ]
+
+racc_token_table = {
+ false => 0,
+ Object.new => 1,
+ :IDENTIFIER => 2,
+ :PARAMETER_NAME => 3,
+ :NUMERIC => 4,
+ :CHARACTER_STRING => 5,
+ :INTEGER => 6,
+ :FLOAT => 7,
+ :PROCEDURE_CODE => 8,
+ ";" => 9,
+ "$$" => 10,
+ "//" => 11,
+ "." => 12,
+ "CREATE" => 13,
+ "TABLE" => 14,
+ "(" => 15,
+ ")" => 16,
+ "VIEW" => 17,
+ "AS" => 18,
+ "ENGINE" => 19,
+ "=" => 20,
+ "MYISAM" => 21,
+ "INNODB" => 22,
+ "HEAP" => 23,
+ "," => 24,
+ "NOT" => 25,
+ "NULL" => 26,
+ "DEFAULT" => 27,
+ "AUTO_INCREMENT" => 28,
+ "PRIMARY" => 29,
+ "KEY" => 30,
+ "FOREIGN" => 31,
+ "REFERENCES" => 32,
+ "ON" => 33,
+ "DELETE" => 34,
+ "CASCADE" => 35,
+ "UNIQUE" => 36,
+ "CONSTRAINT" => 37,
+ "INDEX" => 38,
+ "FULLTEXT" => 39,
+ "USING" => 40,
+ "SECTIONALIZE" => 41,
+ "PROCEDURE" => 42,
+ "BEGIN" => 43,
+ "END" => 44,
+ "IN" => 45,
+ "OUT" => 46,
+ "INOUT" => 47,
+ "--" => 48,
+ "@" => 49,
+ "INSERT" => 50,
+ "INTO" => 51,
+ "VALUES" => 52,
+ "INT" => 53,
+ "INTEGER" => 54,
+ "TINYINT" => 55,
+ "SMALLINT" => 56,
+ "CHAR" => 57,
+ "VARCHAR" => 58,
+ "TEXT" => 59,
+ "BIT" => 60,
+ "DATETIME" => 61,
+ "TIMESTAMP" => 62,
+ "DELIMITER" => 63,
+ "SELECT" => 64,
+ "*" => 65,
+ "FROM" => 66,
+ "WHERE" => 67,
+ "OR" => 68,
+ "AND" => 69,
+ "IS" => 70,
+ "TRUE" => 71,
+ "FALSE" => 72,
+ "<" => 73,
+ ">" => 74 }
+
+racc_use_result_var = true
+
+racc_nt_base = 75
+
+Racc_arg = [
+ racc_action_table,
+ racc_action_check,
+ racc_action_default,
+ racc_action_pointer,
+ racc_goto_table,
+ racc_goto_check,
+ racc_goto_default,
+ racc_goto_pointer,
+ racc_nt_base,
+ racc_reduce_table,
+ racc_token_table,
+ racc_shift_n,
+ racc_reduce_n,
+ racc_use_result_var ]
+
+Racc_token_to_s_table = [
+'$end',
+'error',
+'IDENTIFIER',
+'PARAMETER_NAME',
+'NUMERIC',
+'CHARACTER_STRING',
+'INTEGER',
+'FLOAT',
+'PROCEDURE_CODE',
+'";"',
+'"$$"',
+'"//"',
+'"."',
+'"CREATE"',
+'"TABLE"',
+'"("',
+'")"',
+'"VIEW"',
+'"AS"',
+'"ENGINE"',
+'"="',
+'"MYISAM"',
+'"INNODB"',
+'"HEAP"',
+'","',
+'"NOT"',
+'"NULL"',
+'"DEFAULT"',
+'"AUTO_INCREMENT"',
+'"PRIMARY"',
+'"KEY"',
+'"FOREIGN"',
+'"REFERENCES"',
+'"ON"',
+'"DELETE"',
+'"CASCADE"',
+'"UNIQUE"',
+'"CONSTRAINT"',
+'"INDEX"',
+'"FULLTEXT"',
+'"USING"',
+'"SECTIONALIZE"',
+'"PROCEDURE"',
+'"BEGIN"',
+'"END"',
+'"IN"',
+'"OUT"',
+'"INOUT"',
+'"--"',
+'"@"',
+'"INSERT"',
+'"INTO"',
+'"VALUES"',
+'"INT"',
+'"INTEGER"',
+'"TINYINT"',
+'"SMALLINT"',
+'"CHAR"',
+'"VARCHAR"',
+'"TEXT"',
+'"BIT"',
+'"DATETIME"',
+'"TIMESTAMP"',
+'"DELIMITER"',
+'"SELECT"',
+'"*"',
+'"FROM"',
+'"WHERE"',
+'"OR"',
+'"AND"',
+'"IS"',
+'"TRUE"',
+'"FALSE"',
+'"<"',
+'">"',
+'$start',
+'sql',
+'module_annotation',
+'sql_command_list',
+'annotation_specification',
+'sql_command',
+'table_definition',
+'view_definition',
+'procedure_definition',
+'insert_statement',
+'alter_delimiter',
+'qualified_name',
+'table_element_list',
+'table_option_list',
+'query_specification',
+'table_option',
+'engine_type',
+'table_element',
+'column_definition',
+'constraint_definition',
+'data_type',
+'column_option_list',
+'column_option',
+'literal',
+'primary_key',
+'foreign_key',
+'unique',
+'index',
+'fulltext_index',
+'constraint_name_definition',
+'constrained_column_list',
+'references_specification',
+'triggered_action',
+'index_name_definition',
+'using',
+'parameter_list',
+'parameter',
+'parameter_direction',
+'parameter_name',
+'annotation_list',
+'annotation',
+'annotation_argument_list',
+'annotation_argument',
+'insert_columns_and_source',
+'from_constructor',
+'column_value_expression_list',
+'column_value_expression',
+'value_expression_list',
+'value_expression',
+'number',
+'select_list',
+'table_expression',
+'select_sublist',
+'from_clause',
+'where_clause',
+'search_condition',
+'boolean_value_expression',
+'boolean_term',
+'boolean_facter',
+'boolean_test',
+'boolean_primary',
+'true_value',
+'predicate',
+'comparison_predicate',
+'row_value_expression',
+'comp_op']
+
+Racc_debug_parser = false
+
+##### racc system variables end #####
+
+ # reduce 0 omitted
+
+module_eval <<'.,.,', 'sqlparser.y', 14
+ def _reduce_1( val, _values, result )
+ result = nd(:SQL, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 18
+ def _reduce_2( val, _values, result )
+ result = nd(:MODULE_ANNOTATION, val[0].children, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 22
+ def _reduce_3( val, _values, result )
+ result = nd(:SQL_COMMAND_LIST, [], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 24
+ def _reduce_4( val, _values, result )
+ result = nd(:SQL_COMMAND_LIST, [val[0]], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 27
+ def _reduce_5( val, _values, result )
+ update_pos(val[2], val)
+ result = val[2].unshift(val[0])
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 30
+ def _reduce_6( val, _values, result )
+ update_pos(val[2], val)
+ result = val[2].unshift(val[0])
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 33
+ def _reduce_7( val, _values, result )
+ update_pos(val[2], val)
+ result = val[2].unshift(val[0])
+ result
+ end
+.,.,
+
+ # reduce 8 omitted
+
+ # reduce 9 omitted
+
+ # reduce 10 omitted
+
+ # reduce 11 omitted
+
+ # reduce 12 omitted
+
+ # reduce 13 omitted
+
+module_eval <<'.,.,', 'sqlparser.y', 45
+ def _reduce_14( val, _values, result )
+ result = nd(:IDENTIFIER, val[0].value + val[1].value + val[2].value, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 51
+ def _reduce_15( val, _values, result )
+ result = table(val[2], val[5], val[4], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 56
+ def _reduce_16( val, _values, result )
+ result = nd(:CREATE_VIEW, [val[2], val[5], val[3]], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 60
+ def _reduce_17( val, _values, result )
+ result = nd(:TABLE_OPTION_LIST, [], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 63
+ def _reduce_18( val, _values, result )
+ update_pos(val[1], val)
+ result = val[1].unshift(val[0])
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 67
+ def _reduce_19( val, _values, result )
+ result = nd(:ENGINE, val[2], val)
+ result
+ end
+.,.,
+
+ # reduce 20 omitted
+
+ # reduce 21 omitted
+
+ # reduce 22 omitted
+
+module_eval <<'.,.,', 'sqlparser.y', 76
+ def _reduce_23( val, _values, result )
+ result = nd(:TABLE_ELEMENT_LIST, [val[0]], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 79
+ def _reduce_24( val, _values, result )
+ update_pos(val[2], val)
+ result = val[2].unshift(val[0])
+ result
+ end
+.,.,
+
+ # reduce 25 omitted
+
+ # reduce 26 omitted
+
+module_eval <<'.,.,', 'sqlparser.y', 87
+ def _reduce_27( val, _values, result )
+ result = column(val[0], val[2], val[3], val[1], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 91
+ def _reduce_28( val, _values, result )
+ result = nd(:COLUMN_OPTION_LIST, [], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 94
+ def _reduce_29( val, _values, result )
+ update_pos(val[0], val)
+ result = val[1].unshift(val[0])
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 97
+ def _reduce_30( val, _values, result )
+ result = nd(:NOT_NULL, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 98
+ def _reduce_31( val, _values, result )
+ result = nd(:NULL, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 99
+ def _reduce_32( val, _values, result )
+ result = nd(:DEFAULT, val[1], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 100
+ def _reduce_33( val, _values, result )
+ result = nd(:DEFAULT, nil, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 101
+ def _reduce_34( val, _values, result )
+ result = nd(:AUTO_INCREMENT, val)
+ result
+ end
+.,.,
+
+ # reduce 35 omitted
+
+ # reduce 36 omitted
+
+ # reduce 37 omitted
+
+ # reduce 38 omitted
+
+ # reduce 39 omitted
+
+module_eval <<'.,.,', 'sqlparser.y', 112
+ def _reduce_40( val, _values, result )
+ result = constraint(:PrimaryKey, val[0].value, val[4].to_a, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 120
+ def _reduce_41( val, _values, result )
+ referenced_table, referenced_columns = *(val[6].value)
+ result = constraint(:ReferencesConstraint,
+ val[0].value, val[4].to_a,
+ referenced_table, referenced_columns, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 124
+ def _reduce_42( val, _values, result )
+ result = nd(:REFERENCES_SPECIFICATION, [val[1].value, []], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 126
+ def _reduce_43( val, _values, result )
+ result = nd(:REFERENCES_SPECIFICATION, [val[1].value, val[4].value], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 129
+ def _reduce_44( val, _values, result )
+ result = nd(:TRIGGERED_ACTION, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 130
+ def _reduce_45( val, _values, result )
+ result = nd(:TRIGGERED_ACTION, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 134
+ def _reduce_46( val, _values, result )
+ result = constraint(:Unique, val[0].value, val[3].to_a, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 137
+ def _reduce_47( val, _values, result )
+ result = nd(:CONSTRAINT_NAME, '-', val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 138
+ def _reduce_48( val, _values, result )
+ result = nd(:CONSTRAINT_NAME, val[1].value, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 142
+ def _reduce_49( val, _values, result )
+ result = index(val[1].value, val[3].value, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 146
+ def _reduce_50( val, _values, result )
+ result = index(val[1].value, val[4].value, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 150
+ def _reduce_51( val, _values, result )
+ result = nd(:USING, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 153
+ def _reduce_52( val, _values, result )
+ result = nd(:CONSTRAINT_NAME, '<NO_NAME>', val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 154
+ def _reduce_53( val, _values, result )
+ result = nd(:CONSTRAINT_NAME, val[0].value, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 158
+ def _reduce_54( val, _values, result )
+ result = nd(:CONSTRAINED_COLUMN_LIST, [val[0]], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 161
+ def _reduce_55( val, _values, result )
+ update_pos(val[2], val)
+ result = val[2].unshift(val[0])
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 167
+ def _reduce_56( val, _values, result )
+ result = procedure(val[2], val[4], val[6], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 171
+ def _reduce_57( val, _values, result )
+ result = nd(:PARAMETER_LIST, [], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 173
+ def _reduce_58( val, _values, result )
+ result = nd(:PARAMETER_LIST, [val[0]], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 177
+ def _reduce_59( val, _values, result )
+ val[0].value.annotations = val[2].to_a
+ update_pos(val[3], val)
+ result = val[3].unshift(val[0])
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 181
+ def _reduce_60( val, _values, result )
+ result = parameter(val[1], val[2], val[3], val)
+ result
+ end
+.,.,
+
+ # reduce 61 omitted
+
+ # reduce 62 omitted
+
+ # reduce 63 omitted
+
+ # reduce 64 omitted
+
+ # reduce 65 omitted
+
+ # reduce 66 omitted
+
+module_eval <<'.,.,', 'sqlparser.y', 195
+ def _reduce_67( val, _values, result )
+ result = nd(:ANNTATION_LIST, [], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 199
+ def _reduce_68( val, _values, result )
+ update_pos(val[0], val)
+ val[0].value.concat(val[2].value)
+ result = val[0]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 203
+ def _reduce_69( val, _values, result )
+ result = nd(:ANNTATION_LIST, [val[0]], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 206
+ def _reduce_70( val, _values, result )
+ update_pos(val[0], val)
+ result = val[0].push(val[1])
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 210
+ def _reduce_71( val, _values, result )
+ result = annotation(val[1], [], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 212
+ def _reduce_72( val, _values, result )
+ result = annotation(val[1], val[3], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 216
+ def _reduce_73( val, _values, result )
+ result = nd(:ANNOTATION_ARGUMENT_LIST, [], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 218
+ def _reduce_74( val, _values, result )
+ result = nd(:ANNOTATION_ARGUMENT_LIST, [val[0]], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 220
+ def _reduce_75( val, _values, result )
+ result = update_pos(val[2], val).unshift(val[0])
+ result
+ end
+.,.,
+
+ # reduce 76 omitted
+
+ # reduce 77 omitted
+
+ # reduce 78 omitted
+
+module_eval <<'.,.,', 'sqlparser.y', 229
+ def _reduce_79( val, _values, result )
+ result = nd(:INSERT, val)
+ result
+ end
+.,.,
+
+ # reduce 80 omitted
+
+module_eval <<'.,.,', 'sqlparser.y', 236
+ def _reduce_81( val, _values, result )
+ result = update_pos(val[1], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 240
+ def _reduce_82( val, _values, result )
+ result = nd(:COLUMN_VALUE_EXPRESSION_LIST, [], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 242
+ def _reduce_83( val, _values, result )
+ result = update_pos(val[2], val).unshift(val[0])
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 246
+ def _reduce_84( val, _values, result )
+ result = update_pos(val[1], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 250
+ def _reduce_85( val, _values, result )
+ result = nd(:VALUE_EXPRESSION_LIST, [], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 252
+ def _reduce_86( val, _values, result )
+ result = nd(:VALUE_EXPRESSION_LIST, [val[0]], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 254
+ def _reduce_87( val, _values, result )
+ result = update_pos(val[2], val).unshift(val[0])
+ result
+ end
+.,.,
+
+ # reduce 88 omitted
+
+ # reduce 89 omitted
+
+module_eval <<'.,.,', 'sqlparser.y', 261
+ def _reduce_90( val, _values, result )
+ result = datatype(:INTEGER, nil, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 262
+ def _reduce_91( val, _values, result )
+ result = datatype(:INTEGER, nil, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 263
+ def _reduce_92( val, _values, result )
+ result = datatype(:TINYINT, nil, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 264
+ def _reduce_93( val, _values, result )
+ result = datatype(:SMALLINT, nil, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 265
+ def _reduce_94( val, _values, result )
+ result = datatype(:INTEGER, val[2], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 266
+ def _reduce_95( val, _values, result )
+ result = datatype(:TINYINT, val[2], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 267
+ def _reduce_96( val, _values, result )
+ result = datatype(:SMALLINT, val[2], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 268
+ def _reduce_97( val, _values, result )
+ result = datatype(:CHAR, val[2], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 269
+ def _reduce_98( val, _values, result )
+ result = datatype(:VARCHAR, val[2], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 270
+ def _reduce_99( val, _values, result )
+ result = datatype(:TEXT, nil, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 271
+ def _reduce_100( val, _values, result )
+ result = datatype(:BIT, nil, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 272
+ def _reduce_101( val, _values, result )
+ result = datatype(:DATETIME, nil, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 273
+ def _reduce_102( val, _values, result )
+ result = datatype(:TIMESTAMP, nil, val)
+ result
+ end
+.,.,
+
+ # reduce 103 omitted
+
+ # reduce 104 omitted
+
+ # reduce 105 omitted
+
+ # reduce 106 omitted
+
+ # reduce 107 omitted
+
+ # reduce 108 omitted
+
+module_eval <<'.,.,', 'sqlparser.y', 289
+ def _reduce_109( val, _values, result )
+ result = nd(:QUERY_SPECIFICATION, val)
+ result
+ end
+.,.,
+
+ # reduce 110 omitted
+
+ # reduce 111 omitted
+
+ # reduce 112 omitted
+
+ # reduce 113 omitted
+
+module_eval <<'.,.,', 'sqlparser.y', 301
+ def _reduce_114( val, _values, result )
+ result = nd(:TABLE_EXPRESSION, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 305
+ def _reduce_115( val, _values, result )
+ result = nd(:FROM_CLAUSE, val[1], val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 309
+ def _reduce_116( val, _values, result )
+ result = nd(:WHERE_CLAUSE, val)
+ result
+ end
+.,.,
+
+ # reduce 117 omitted
+
+module_eval <<'.,.,', 'sqlparser.y', 316
+ def _reduce_118( val, _values, result )
+ result = nd(:BOOLEAN_VALUE_EXPRESSION, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 318
+ def _reduce_119( val, _values, result )
+ result = nd(:BOOLEAN_VALUE_EXPRESSION, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 322
+ def _reduce_120( val, _values, result )
+ result = nd(:BOOLEAN_TERM, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 324
+ def _reduce_121( val, _values, result )
+ result = nd(:BOOLEAN_TERM, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 328
+ def _reduce_122( val, _values, result )
+ result = nd(:BOOLEAN_FACTER, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 330
+ def _reduce_123( val, _values, result )
+ result = nd(:BOOLEAN_FACTER, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 334
+ def _reduce_124( val, _values, result )
+ result = nd(:BOOLEAN_TEST, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 336
+ def _reduce_125( val, _values, result )
+ result = nd(:BOOLEAN_TEST, val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 338
+ def _reduce_126( val, _values, result )
+ result = nd(:BOOLEAN_TEST, val)
+ result
+ end
+.,.,
+
+ # reduce 127 omitted
+
+ # reduce 128 omitted
+
+ # reduce 129 omitted
+
+ # reduce 130 omitted
+
+module_eval <<'.,.,', 'sqlparser.y', 352
+ def _reduce_131( val, _values, result )
+ result = nd(:PREDICATE, val)
+ result
+ end
+.,.,
+
+ # reduce 132 omitted
+
+ # reduce 133 omitted
+
+module_eval <<'.,.,', 'sqlparser.y', 359
+ def _reduce_134( val, _values, result )
+ result = nd(:COMP_OP, '>=', val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 360
+ def _reduce_135( val, _values, result )
+ result = nd(:COMP_OP, '>=', val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 361
+ def _reduce_136( val, _values, result )
+ result = nd(:COMP_OP, '>=', val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 362
+ def _reduce_137( val, _values, result )
+ result = nd(:COMP_OP, '>=', val)
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 363
+ def _reduce_138( val, _values, result )
+ result = nd(:COMP_OP, '>=', val)
+ result
+ end
+.,.,
+
+ def _reduce_none( val, _values, result )
+ result
+ end
+
+ end # class Parser
+
+ end # module SQL
+
+end # module Hdboo
--- /dev/null
+#
+# DO NOT MODIFY!!!!
+# This file is automatically generated by racc 1.4.5
+# from racc grammer file "sqlparser.y".
+#
+
+require 'racc/parser'
+
+
+require 'strscan'
+
+
+module Rdboo
+
+ class SQLParser < Racc::Parser
+
+module_eval <<'..end sqlparser.y modeval..id847e574633', 'sqlparser.y', 171
+# token
+PUNCTUATION = /([;,()]|\$\$)/
+KEYWORD_LIST = [
+ 'CREATE', 'TABLE', 'NOT', 'NULL', 'AUTO_INCREMENT', 'DEFAULT',
+ 'INT', 'VARCHAR', 'PROCEDURE', 'IN', 'OUT', 'INOUT', 'KEY',
+ 'PRIMARY', 'UNIQUE', 'INDEX', 'BEGIN', 'END', 'INTEGER', 'TINYINT',
+ 'SMALLINT', 'DELIMITER'
+]
+KEYWORD = /(#{KEYWORD_LIST.collect{|k| '\b'+k+'\b'}.join('|')})/ix
+IDENTIFIER = /([a-zA-Z][_0-9a-zA-Z]*)/
+
+# NUMBER
+NUMERIC = /([1-9][0-9]*)/
+INTEGER = /([+|-]?[1-9][0-9]*)/
+FLOAT = /(
+ (?: #without exponent part
+ [+|-]?(?:[1-9][0-9]*|0)(?:\.[0-9]+)?
+ | #with exponent part
+ [+|-]?[1-9]+(?:\.[0-9]+)?e[+|-]?[1-9][0-9]*
+ )
+ )/x
+
+# STRING
+NOT_PRINTABLE = '\x00-\x1F\x7F' #including space (\x20)
+MUST_BE_ESCAPED = '\x22\x27\x5C' #quot, double-quot, back-slash
+ESCAPE_TABLE = {
+ '\n' => "\n",
+ '\b' => "\b",
+ '\0' => "\x00",
+ '\t' => "\t",
+ "\\'" => "'",
+ '\"' => '"'
+}
+ESCAPED = ESCAPE_TABLE.keys.join('|')
+CHARACTER_STRING = /(
+ #single-quoted string
+ '
+ (?:
+ [^#{MUST_BE_ESCAPED}#{NOT_PRINTABLE}]
+ |
+ #{ESCAPED}
+ |
+ ''
+ |
+ "
+ )*
+ '
+ |
+ #double-quoted string
+ "
+ (?:
+ [^#{MUST_BE_ESCAPED}#{NOT_PRINTABLE}]
+ |
+ #{ESCAPED}
+ |
+ ""
+ |
+ '
+ )*
+ "
+)/x
+
+# PROCEDURE
+PROCEDURE_CODE = /(BEGIN.*END)/
+PARAMETER_NAME = /([_a-zA-Z][_0-9a-zA-Z]*)/
+
+def parse(str)
+ @lines = str.split(/\n+/)
+ next_line
+ do_parse
+end
+
+def next_line
+ @line = @lines.empty? \
+ ? nil \
+ : StringScanner.new(@lines.shift)
+end
+
+def next_token
+ token = _next_token
+ p token
+ token
+end
+
+def _next_token
+ return nil unless @line
+ @line.skip(/\s*/)
+ if @in_procedure_code
+ if @line.skip_until(/\bEND\b/i)
+ @in_procedure_code = false
+ return ['END', 'END']
+ end
+ @line.terminate
+ end
+ if @line.eos?
+ next_line
+ return _next_token
+ end
+ if token = @line.scan(PUNCTUATION)
+ return [token, token]
+ elsif token = @line.scan(KEYWORD)
+ token = token.upcase
+ if token == 'BEGIN' then @in_procedure_code = true end
+ return [token, token]
+ elsif token = @line.scan(IDENTIFIER)
+ return [:IDENTIFIER, token]
+ elsif token = @line.scan(PARAMETER_NAME)
+ return [:PARAMETER_NAME, token]
+ elsif token = @line.scan(NUMERIC)
+ return [:NUMERIC, token.to_i]
+ elsif token = @line.scan(CHARACTER_STRING)
+ return [
+ :CHARACTER_STRING,
+ token[1...-1].gsub(/#{ESCAPED}/) {|s|ESCAPE_TABLE[token]}\
+ .gsub(/""/, '"')\
+ .gsub(/''/, "\'")
+ ]
+ elsif token = @line.scan(INTEGER)
+ return [:INTEGER, token.to_i]
+ elsif token = @line.scan(FLOAT)
+ return [:FLOAT, token.to_f]
+ else
+ return [:UNEXPECTED_TOKEN, @line.scan(/.*/)]
+ end
+end
+
+
+..end sqlparser.y modeval..id847e574633
+
+##### racc 1.4.5 generates ###
+
+racc_reduce_table = [
+ 0, 0, :racc_error,
+ 1, 36, :_reduce_none,
+ 0, 37, :_reduce_2,
+ 1, 37, :_reduce_3,
+ 3, 37, :_reduce_4,
+ 3, 37, :_reduce_5,
+ 1, 38, :_reduce_none,
+ 1, 38, :_reduce_none,
+ 1, 38, :_reduce_none,
+ 6, 39, :_reduce_9,
+ 1, 42, :_reduce_10,
+ 3, 42, :_reduce_11,
+ 1, 43, :_reduce_none,
+ 1, 43, :_reduce_none,
+ 3, 44, :_reduce_14,
+ 0, 47, :_reduce_15,
+ 2, 47, :_reduce_16,
+ 2, 48, :_reduce_17,
+ 1, 48, :_reduce_18,
+ 2, 48, :_reduce_19,
+ 2, 48, :_reduce_20,
+ 1, 48, :_reduce_21,
+ 4, 45, :_reduce_22,
+ 1, 50, :_reduce_none,
+ 1, 50, :_reduce_none,
+ 1, 50, :_reduce_none,
+ 2, 52, :_reduce_26,
+ 1, 53, :_reduce_27,
+ 2, 53, :_reduce_28,
+ 2, 53, :_reduce_29,
+ 1, 54, :_reduce_30,
+ 1, 54, :_reduce_31,
+ 1, 51, :_reduce_32,
+ 3, 51, :_reduce_33,
+ 7, 40, :_reduce_34,
+ 0, 55, :_reduce_35,
+ 1, 55, :_reduce_36,
+ 3, 55, :_reduce_37,
+ 3, 57, :_reduce_38,
+ 3, 57, :_reduce_39,
+ 0, 58, :_reduce_none,
+ 1, 58, :_reduce_none,
+ 1, 58, :_reduce_none,
+ 1, 58, :_reduce_none,
+ 2, 56, :_reduce_none,
+ 1, 46, :_reduce_45,
+ 1, 46, :_reduce_46,
+ 1, 46, :_reduce_47,
+ 1, 46, :_reduce_48,
+ 4, 46, :_reduce_49,
+ 1, 49, :_reduce_none,
+ 1, 49, :_reduce_none,
+ 1, 59, :_reduce_none,
+ 1, 59, :_reduce_none,
+ 1, 41, :_reduce_none ]
+
+racc_reduce_n = 55
+
+racc_shift_n = 85
+
+racc_action_table = [
+ 8, 8, 8, 24, -35, 50, 12, 49, 54, 55,
+ 24, 58, 60, 61, 62, 35, 37, 39, 13, 52,
+ 26, 28, 31, 21, 1, 1, 1, 26, 28, 31,
+ 21, -35, 10, 11, 75, 78, 79, 42, 43, 44,
+ 45, 40, 35, 37, 39, 76, 42, 43, 44, 45,
+ 40, 42, 43, 44, 45, 40, 58, 60, 61, 62,
+ 51, 48, 53, 47, 56, 46, 63, 20, 67, 19,
+ 18, 17, 71, 72, 14, 9, 80, 81, 82, 83,
+ 63 ]
+
+racc_action_check = [
+ 0, 11, 10, 51, 20, 31, 8, 31, 38, 38,
+ 19, 59, 59, 59, 59, 20, 20, 20, 8, 34,
+ 51, 51, 51, 51, 0, 11, 10, 19, 19, 19,
+ 19, 53, 4, 4, 61, 61, 61, 24, 24, 24,
+ 24, 24, 53, 53, 53, 61, 55, 55, 55, 55,
+ 55, 54, 54, 54, 54, 54, 41, 41, 41, 41,
+ 33, 30, 36, 26, 40, 25, 46, 18, 52, 17,
+ 13, 12, 56, 58, 9, 2, 63, 64, 67, 71,
+ 80 ]
+
+racc_action_pointer = [
+ -10, nil, 75, nil, 24, nil, nil, nil, -5, 74,
+ -8, -9, 69, 68, nil, nil, nil, 57, 55, 8,
+ -9, nil, nil, nil, 8, 53, 43, nil, nil, nil,
+ 48, -15, nil, 46, 6, nil, 48, nil, 6, nil,
+ 52, 41, nil, nil, nil, nil, 64, nil, nil, nil,
+ nil, 1, 41, 18, 22, 17, 68, nil, 57, -4,
+ nil, 29, nil, 62, 64, nil, nil, 50, nil, nil,
+ nil, 66, nil, nil, nil, nil, nil, nil, nil, nil,
+ 78, nil, nil, nil, nil ]
+
+racc_action_default = [
+ -2, -54, -55, -1, -3, -6, -7, -8, -55, -55,
+ -2, -2, -55, -55, 85, -4, -5, -55, -55, -55,
+ -40, -30, -12, -13, -55, -55, -55, -23, -31, -24,
+ -55, -27, -25, -10, -55, -41, -36, -42, -55, -43,
+ -55, -15, -45, -46, -47, -48, -55, -26, -9, -29,
+ -28, -55, -55, -40, -55, -55, -55, -14, -55, -15,
+ -18, -55, -21, -32, -55, -11, -34, -55, -37, -39,
+ -38, -55, -17, -16, -51, -50, -20, -19, -52, -53,
+ -55, -22, -44, -49, -33 ]
+
+racc_goto_table = [
+ 64, 30, 34, 57, 3, 77, 41, 2, 66, 74,
+ nil, nil, nil, nil, 15, 16, nil, nil, nil, nil,
+ nil, 73, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, 65, 84, 68, 69, 70 ]
+
+racc_goto_check = [
+ 16, 7, 20, 12, 2, 14, 11, 1, 21, 24,
+ nil, nil, nil, nil, 2, 2, nil, nil, nil, nil,
+ nil, 12, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, 7, 16, 20, 11, 11 ]
+
+racc_goto_pointer = [
+ nil, 7, 4, nil, nil, nil, nil, -18, nil, nil,
+ nil, -18, -38, nil, -56, nil, -46, nil, nil, nil,
+ -18, -44, nil, nil, -52 ]
+
+racc_goto_default = [
+ nil, nil, nil, 4, 5, 6, 7, nil, 33, 22,
+ 23, nil, nil, 59, nil, 25, nil, 27, 29, 32,
+ nil, nil, 36, 38, nil ]
+
+racc_token_table = {
+ false => 0,
+ Object.new => 1,
+ :IDENTIFIER => 2,
+ :PARAMETER_NAME => 3,
+ :NUMERIC => 4,
+ :CHARACTER_STRING => 5,
+ :INTEGER => 6,
+ :FLOAT => 7,
+ ";" => 8,
+ "$$" => 9,
+ "CREATE" => 10,
+ "TABLE" => 11,
+ "(" => 12,
+ ")" => 13,
+ "," => 14,
+ "NOT" => 15,
+ "NULL" => 16,
+ "DEFAULT" => 17,
+ "AUTO_INCREMENT" => 18,
+ "PRIMARY" => 19,
+ "KEY" => 20,
+ "UNIQUE" => 21,
+ "INDEX" => 22,
+ "PROCEDURE" => 23,
+ "IN" => 24,
+ "OUT" => 25,
+ "INOUT" => 26,
+ "BEGIN" => 27,
+ "END" => 28,
+ "INT" => 29,
+ "INTEGER" => 30,
+ "TINYINT" => 31,
+ "SMALLINT" => 32,
+ "VARCHAR" => 33,
+ "DELIMITER" => 34 }
+
+racc_use_result_var = true
+
+racc_nt_base = 35
+
+Racc_arg = [
+ racc_action_table,
+ racc_action_check,
+ racc_action_default,
+ racc_action_pointer,
+ racc_goto_table,
+ racc_goto_check,
+ racc_goto_default,
+ racc_goto_pointer,
+ racc_nt_base,
+ racc_reduce_table,
+ racc_token_table,
+ racc_shift_n,
+ racc_reduce_n,
+ racc_use_result_var ]
+
+Racc_token_to_s_table = [
+'$end',
+'error',
+'IDENTIFIER',
+'PARAMETER_NAME',
+'NUMERIC',
+'CHARACTER_STRING',
+'INTEGER',
+'FLOAT',
+'";"',
+'"$$"',
+'"CREATE"',
+'"TABLE"',
+'"("',
+'")"',
+'","',
+'"NOT"',
+'"NULL"',
+'"DEFAULT"',
+'"AUTO_INCREMENT"',
+'"PRIMARY"',
+'"KEY"',
+'"UNIQUE"',
+'"INDEX"',
+'"PROCEDURE"',
+'"IN"',
+'"OUT"',
+'"INOUT"',
+'"BEGIN"',
+'"END"',
+'"INT"',
+'"INTEGER"',
+'"TINYINT"',
+'"SMALLINT"',
+'"VARCHAR"',
+'"DELIMITER"',
+'$start',
+'sql',
+'sql_command_list',
+'sql_command',
+'base_table_definition',
+'procedure_definition',
+'alter_delimiter',
+'base_table_element_list',
+'base_table_element',
+'column_definition',
+'base_table_constraint_definition',
+'data_type',
+'column_option_list',
+'column_option',
+'constant',
+'constraint_type',
+'constrained_column_list',
+'primary_key',
+'unique',
+'key',
+'parameter_list',
+'procedure_code',
+'parameter',
+'parameter_direction',
+'number']
+
+Racc_debug_parser = false
+
+##### racc system variables end #####
+
+ # reduce 0 omitted
+
+ # reduce 1 omitted
+
+module_eval <<'.,.,', 'sqlparser.y', 15
+ def _reduce_2( val, _values, result )
+ result = [:SQL_COMMAND_LIST]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 17
+ def _reduce_3( val, _values, result )
+ result = [:SQL_COMMAND_LIST, val[0]]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 19
+ def _reduce_4( val, _values, result )
+ result = val[2].insert(1, val[0])
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 21
+ def _reduce_5( val, _values, result )
+ result = val[2].insert(1, val[0])
+ result
+ end
+.,.,
+
+ # reduce 6 omitted
+
+ # reduce 7 omitted
+
+ # reduce 8 omitted
+
+module_eval <<'.,.,', 'sqlparser.y', 30
+ def _reduce_9( val, _values, result )
+ result = [:CREATE_TABLE, val[2], val[4]]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 34
+ def _reduce_10( val, _values, result )
+ result = [:BASE_TABLE_ELEMENT_LIST, val[0]]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 36
+ def _reduce_11( val, _values, result )
+ result = val[2].insert(1, val[0])
+ result
+ end
+.,.,
+
+ # reduce 12 omitted
+
+ # reduce 13 omitted
+
+module_eval <<'.,.,', 'sqlparser.y', 44
+ def _reduce_14( val, _values, result )
+ result = [:COLUMN_DEFINITION, val[0], val[1], val[2]]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 47
+ def _reduce_15( val, _values, result )
+ result = [:COLUMN_OPTION_LIST]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 49
+ def _reduce_16( val, _values, result )
+ result = val[1].insert(1, val[0])
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 52
+ def _reduce_17( val, _values, result )
+ result = [:COLUMN_OPTION, :NOT_NULL]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 53
+ def _reduce_18( val, _values, result )
+ result = [:COLUMN_OPTION, :NULL]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 54
+ def _reduce_19( val, _values, result )
+ result = [:COLUMN_OPTION, :DEFAULT, val[1]]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 55
+ def _reduce_20( val, _values, result )
+ result = [:COLUMN_OPTION, :DEFAULT, :NULL]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 56
+ def _reduce_21( val, _values, result )
+ result = [:COLUMN_OPTION, :AUTO_INCREMENT]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 60
+ def _reduce_22( val, _values, result )
+ result = [:BASE_TABLE_COSTRAINT_DEFINITION, val[0], val[2]]
+ result
+ end
+.,.,
+
+ # reduce 23 omitted
+
+ # reduce 24 omitted
+
+ # reduce 25 omitted
+
+module_eval <<'.,.,', 'sqlparser.y', 68
+ def _reduce_26( val, _values, result )
+ result = :PRIMARY_KEY
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 71
+ def _reduce_27( val, _values, result )
+ result = :UNIQUE
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 72
+ def _reduce_28( val, _values, result )
+ result = :UNIQUE
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 73
+ def _reduce_29( val, _values, result )
+ result = :UNIQUE
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 76
+ def _reduce_30( val, _values, result )
+ result = :KEY
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 77
+ def _reduce_31( val, _values, result )
+ result = :KEY
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 81
+ def _reduce_32( val, _values, result )
+ result = [:CONSTRAINED_COLUMN_LIST, val[0]]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 83
+ def _reduce_33( val, _values, result )
+ result = val[2].insert(1, val[0])
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 87
+ def _reduce_34( val, _values, result )
+ result = [:CREATE_PROCEDURE, val[2], val[4]]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 90
+ def _reduce_35( val, _values, result )
+ result = [:PARAMETER_LIST]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 92
+ def _reduce_36( val, _values, result )
+ result = [:PARAMETER_LIST, val[0]]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 94
+ def _reduce_37( val, _values, result )
+ result = val[2].insert(1, val[0])
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 98
+ def _reduce_38( val, _values, result )
+ result = [:PARAMETER, val[1], val[2]]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 100
+ def _reduce_39( val, _values, result )
+ result = [:PARAMETER, val[1], val[2]]
+ result
+ end
+.,.,
+
+ # reduce 40 omitted
+
+ # reduce 41 omitted
+
+ # reduce 42 omitted
+
+ # reduce 43 omitted
+
+ # reduce 44 omitted
+
+module_eval <<'.,.,', 'sqlparser.y', 111
+ def _reduce_45( val, _values, result )
+ result = [:DATA_TYPE, :INTEGER]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 112
+ def _reduce_46( val, _values, result )
+ result = [:DATA_TYPE, :INTEGER]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 113
+ def _reduce_47( val, _values, result )
+ result = [:DATA_TYPE, :TINYINT]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 114
+ def _reduce_48( val, _values, result )
+ result = [:DATA_TYPE, :SMALLINT]
+ result
+ end
+.,.,
+
+module_eval <<'.,.,', 'sqlparser.y', 115
+ def _reduce_49( val, _values, result )
+ result = [:DATA_TYPE, :VARCHAR, val[2]]
+ result
+ end
+.,.,
+
+ # reduce 50 omitted
+
+ # reduce 51 omitted
+
+ # reduce 52 omitted
+
+ # reduce 53 omitted
+
+ # reduce 54 omitted
+
+ def _reduce_none( val, _values, result )
+ result
+ end
+
+ end # class SQLParser
+
+end # module Rdboo
+
+class Rdboo::SQLParser::ASTWalker
+ def walk(ast)
+ walk_node(ast)
+ self
+ end
+
+ def initialize
+ @listeners = Hash.new {|hash, key| hash[key] = []}
+ end
+
+ def visit(note_type, &block)
+ node_type = note_type.to_sym
+ @listeners[node_type] << block
+ self
+ end
+
+ private
+ def walk_node(node)
+ node_type = node.is_a?(Array) \
+ ? node.shift \
+ : node
+
+ if @listeners.has_key?(node_type)
+ @listeners[node_type].each do |listener|
+ listener.call(node)
+ end
+ end
+
+ if node.is_a?(Array)
+ node.each do |child_node|
+ walk_node(child_node)
+ end
+ end
+ end
+end
+
--- /dev/null
+class Hdboo::SQL::Parser
+
+token
+ IDENTIFIER
+ PARAMETER_NAME
+ NUMERIC
+ CHARACTER_STRING
+ INTEGER
+ FLOAT
+ PROCEDURE_CODE
+
+rule
+ sql
+ : module_annotation sql_command_list
+ { result = nd(:SQL, val)}
+
+ module_annotation
+ : annotation_specification
+ { result = nd(:MODULE_ANNOTATION, val[0].children, val) }
+
+ sql_command_list
+ :
+ { result = nd(:SQL_COMMAND_LIST, [], val) }
+ | sql_command
+ { result = nd(:SQL_COMMAND_LIST, [val[0]], val) }
+ | sql_command ';' sql_command_list
+ { update_pos(val[2], val)
+ result = val[2].unshift(val[0]) }
+ | sql_command '$$' sql_command_list
+ { update_pos(val[2], val)
+ result = val[2].unshift(val[0]) }
+ | sql_command '//' sql_command_list
+ { update_pos(val[2], val)
+ result = val[2].unshift(val[0]) }
+
+ sql_command
+ : table_definition
+ | view_definition
+ | procedure_definition
+ | insert_statement
+ | alter_delimiter
+
+ qualified_name
+ : IDENTIFIER
+ | qualified_name '.' IDENTIFIER
+ { result = nd(:IDENTIFIER, val[0].value + val[1].value + val[2].value, val)}
+
+ table_definition
+ : 'CREATE' 'TABLE' IDENTIFIER '('
+ annotation_specification table_element_list
+ ')' table_option_list
+ { result = table(val[2], val[5], val[4], val) }
+
+ view_definition
+ : 'CREATE' 'VIEW' IDENTIFIER annotation_specification
+ 'AS' query_specification
+ { result = nd(:CREATE_VIEW, [val[2], val[5], val[3]], val) }
+
+ table_option_list
+ :
+ { result = nd(:TABLE_OPTION_LIST, [], val) }
+ | table_option table_option_list
+ { update_pos(val[1], val)
+ result = val[1].unshift(val[0]) }
+
+ table_option
+ : 'ENGINE' '=' engine_type
+ { result = nd(:ENGINE, val[2], val)}
+
+ engine_type
+ : 'MYISAM'
+ | 'INNODB'
+ | 'HEAP'
+
+ table_element_list
+ : table_element
+ { result = nd(:TABLE_ELEMENT_LIST, [val[0]], val) }
+ | table_element ',' table_element_list
+ { update_pos(val[2], val)
+ result = val[2].unshift(val[0]) }
+
+ table_element
+ : column_definition
+ | constraint_definition
+
+ column_definition
+ : IDENTIFIER annotation_specification data_type column_option_list
+ { result = column(val[0], val[2], val[3], val[1], val) }
+
+ column_option_list
+ :
+ { result = nd(:COLUMN_OPTION_LIST, [], val) }
+ | column_option column_option_list
+ { update_pos(val[0], val)
+ result = val[1].unshift(val[0]) }
+
+ column_option
+ : 'NOT' 'NULL' { result = nd(:NOT_NULL, val) }
+ | 'NULL' { result = nd(:NULL, val) }
+ | 'DEFAULT' literal { result = nd(:DEFAULT, val[1], val) }
+ | 'DEFAULT' 'NULL' { result = nd(:DEFAULT, nil, val) }
+ | 'AUTO_INCREMENT' { result = nd(:AUTO_INCREMENT, val) }
+
+ constraint_definition
+ : primary_key
+ | foreign_key
+ | unique
+ | index
+ | fulltext_index
+
+ primary_key
+ : constraint_name_definition 'PRIMARY' 'KEY' '(' constrained_column_list ')'
+ { result = constraint(:PrimaryKey, val[0].value, val[4].to_a, val) }
+
+ foreign_key
+ : constraint_name_definition 'FOREIGN' 'KEY' '(' constrained_column_list ')'
+ references_specification
+ { referenced_table, referenced_columns = *(val[6].value)
+ result = constraint(:ReferencesConstraint,
+ val[0].value, val[4].to_a,
+ referenced_table, referenced_columns, val) }
+
+ references_specification
+ : 'REFERENCES' IDENTIFIER triggered_action
+ { result = nd(:REFERENCES_SPECIFICATION, [val[1].value, []], val) }
+ | 'REFERENCES' IDENTIFIER '(' constrained_column_list ')' triggered_action
+ { result = nd(:REFERENCES_SPECIFICATION, [val[1].value, val[4].value], val) }
+
+ triggered_action
+ : { result = nd(:TRIGGERED_ACTION, val) }
+ | 'ON' 'DELETE' 'CASCADE' { result = nd(:TRIGGERED_ACTION, val) }
+
+ unique
+ : constraint_name_definition 'UNIQUE' '(' constrained_column_list ')'
+ { result = constraint(:Unique, val[0].value, val[3].to_a, val) }
+
+ constraint_name_definition
+ : { result = nd(:CONSTRAINT_NAME, '-', val) }
+ | 'CONSTRAINT' IDENTIFIER { result = nd(:CONSTRAINT_NAME, val[1].value, val) }
+
+ index
+ : 'INDEX' index_name_definition '(' constrained_column_list ')'
+ { result = index(val[1].value, val[3].value, val) }
+
+ fulltext_index
+ : 'FULLTEXT' index_name_definition using '(' constrained_column_list ')'
+ { result = index(val[1].value, val[4].value, val) }
+
+ using
+ : 'USING' 'SECTIONALIZE'
+ { result = nd(:USING, val)}
+
+ index_name_definition
+ : { result = nd(:CONSTRAINT_NAME, '<NO_NAME>', val) }
+ | IDENTIFIER { result = nd(:CONSTRAINT_NAME, val[0].value, val) }
+
+ constrained_column_list
+ : IDENTIFIER
+ { result = nd(:CONSTRAINED_COLUMN_LIST, [val[0]], val) }
+ | IDENTIFIER ',' constrained_column_list
+ { update_pos(val[2], val)
+ result = val[2].unshift(val[0]) }
+
+ procedure_definition
+ : 'CREATE' 'PROCEDURE' IDENTIFIER '(' parameter_list ')'
+ annotation_specification
+ 'BEGIN' PROCEDURE_CODE 'END'
+ { result = procedure(val[2], val[4], val[6], val) }
+
+ parameter_list
+ :
+ { result = nd(:PARAMETER_LIST, [], val)}
+ | parameter
+ { result = nd(:PARAMETER_LIST, [val[0]], val) }
+ | parameter ',' annotation_specification parameter_list
+ { val[0].value.annotations = val[2].to_a
+ update_pos(val[3], val)
+ result = val[3].unshift(val[0]) }
+
+ parameter
+ : parameter_direction parameter_name data_type annotation_specification
+ { result = parameter(val[1], val[2], val[3], val) }
+
+ parameter_name
+ : IDENTIFIER
+ | PARAMETER_NAME
+
+ parameter_direction
+ :
+ | 'IN'
+ | 'OUT'
+ | 'INOUT'
+
+ annotation_specification
+ :
+ { result = nd(:ANNTATION_LIST, [], val) }
+ | annotation_specification '--' annotation_list
+ { update_pos(val[0], val)
+ val[0].value.concat(val[2].value)
+ result = val[0] }
+
+ annotation_list
+ : annotation
+ { result = nd(:ANNTATION_LIST, [val[0]], val) }
+ | annotation_list annotation
+ { update_pos(val[0], val)
+ result = val[0].push(val[1]) }
+
+ annotation
+ : '@' IDENTIFIER
+ { result = annotation(val[1], [], val) }
+ | '@' IDENTIFIER '(' annotation_argument_list ')'
+ { result = annotation(val[1], val[3], val) }
+
+ annotation_argument_list
+ :
+ { result = nd(:ANNOTATION_ARGUMENT_LIST, [], val) }
+ | annotation_argument
+ { result = nd(:ANNOTATION_ARGUMENT_LIST, [val[0]], val) }
+ | annotation_argument ',' annotation_argument_list
+ { result = update_pos(val[2], val).unshift(val[0]) }
+
+ annotation_argument
+ : literal
+ | qualified_name
+ | data_type
+
+ insert_statement
+ : 'INSERT' 'INTO' IDENTIFIER insert_columns_and_source
+ { result = nd(:INSERT, val) }
+
+ insert_columns_and_source
+ : from_constructor
+
+ from_constructor
+ : 'VALUES' column_value_expression_list
+ { result = update_pos(val[1], val) }
+
+ column_value_expression_list
+ : column_value_expression
+ { result = nd(:COLUMN_VALUE_EXPRESSION_LIST, [], val) }
+ | column_value_expression ',' column_value_expression_list
+ { result = update_pos(val[2], val).unshift(val[0]) }
+
+ column_value_expression
+ : '(' value_expression_list ')'
+ { result = update_pos(val[1], val) }
+
+ value_expression_list
+ :
+ { result = nd(:VALUE_EXPRESSION_LIST, [], val) }
+ | value_expression
+ { result = nd(:VALUE_EXPRESSION_LIST, [val[0]], val) }
+ | value_expression ',' value_expression_list
+ { result = update_pos(val[2], val).unshift(val[0]) }
+
+ value_expression
+ : literal
+ | qualified_name
+
+ data_type
+ : 'INT' { result = datatype(:INTEGER, nil, val) }
+ | 'INTEGER' { result = datatype(:INTEGER, nil, val) }
+ | 'TINYINT' { result = datatype(:TINYINT, nil, val) }
+ | 'SMALLINT' { result = datatype(:SMALLINT, nil, val) }
+ | 'INTEGER' '(' NUMERIC ')' { result = datatype(:INTEGER, val[2], val) }
+ | 'TINYINT' '(' NUMERIC ')' { result = datatype(:TINYINT, val[2], val) }
+ | 'SMALLINT' '(' NUMERIC ')' { result = datatype(:SMALLINT, val[2], val) }
+ | 'CHAR' '(' NUMERIC ')' { result = datatype(:CHAR, val[2], val) }
+ | 'VARCHAR' '(' NUMERIC ')' { result = datatype(:VARCHAR, val[2], val) }
+ | 'TEXT' { result = datatype(:TEXT, nil, val) }
+ | 'BIT' { result = datatype(:BIT, nil, val) }
+ | 'DATETIME' { result = datatype(:DATETIME, nil, val) }
+ | 'TIMESTAMP' { result = datatype(:TIMESTAMP, nil, val) }
+
+ literal
+ : CHARACTER_STRING
+ | number
+
+ number
+ : NUMERIC
+ | INTEGER
+ | FLOAT
+
+ alter_delimiter
+ : 'DELIMITER'
+
+ query_specification
+ : 'SELECT' select_list table_expression
+ { result = nd(:QUERY_SPECIFICATION, val) }
+
+ select_list
+ : '*'
+ | select_sublist
+
+ select_sublist
+ : IDENTIFIER
+ | IDENTIFIER ',' select_sublist
+
+ table_expression
+ : from_clause where_clause
+ { result = nd(:TABLE_EXPRESSION, val) }
+
+ from_clause
+ : 'FROM' IDENTIFIER
+ { result = nd(:FROM_CLAUSE, val[1], val)}
+
+ where_clause
+ : 'WHERE' search_condition
+ { result = nd(:WHERE_CLAUSE, val) }
+
+ search_condition
+ : boolean_value_expression
+
+ boolean_value_expression
+ : boolean_term
+ { result = nd(:BOOLEAN_VALUE_EXPRESSION, val) }
+ | boolean_value_expression 'OR' boolean_term
+ { result = nd(:BOOLEAN_VALUE_EXPRESSION, val) }
+
+ boolean_term
+ : boolean_facter
+ { result = nd(:BOOLEAN_TERM, val) }
+ | boolean_term 'AND' boolean_facter
+ { result = nd(:BOOLEAN_TERM, val) }
+
+ boolean_facter
+ : boolean_test
+ { result = nd(:BOOLEAN_FACTER, val) }
+ | 'NOT' boolean_test
+ { result = nd(:BOOLEAN_FACTER, val) }
+
+ boolean_test
+ : boolean_primary
+ { result = nd(:BOOLEAN_TEST, val) }
+ | boolean_primary 'IS' true_value
+ { result = nd(:BOOLEAN_TEST, val) }
+ | boolean_primary 'IS' 'NOT' true_value
+ { result = nd(:BOOLEAN_TEST, val) }
+
+ true_value
+ : 'TRUE'
+ | 'FALSE'
+
+ boolean_primary
+ : predicate
+
+ predicate
+ : comparison_predicate
+
+ comparison_predicate
+ : row_value_expression comp_op row_value_expression
+ { result = nd(:PREDICATE, val) }
+
+ row_value_expression
+ : IDENTIFIER
+ | literal
+
+ comp_op
+ : '=' { result = nd(:COMP_OP, '>=', val) }
+ | '<' { result = nd(:COMP_OP, '>=', val) }
+ | '<' '=' { result = nd(:COMP_OP, '>=', val) }
+ | '>' { result = nd(:COMP_OP, '>=', val) }
+ | '>' '=' { result = nd(:COMP_OP, '>=', val) }
+end
+
+----header
+require 'strscan'
+
+----inner
+def constraint(type, *args)
+ all_elements = args.pop
+ nd(:CONSTRAINT, SQL::Table::Constraint.new(type, args), all_elements)
+end
+
+def index(name, columns, all_elements)
+ nd(:INDEX, SQL::Table::Index.new(name, columns), all_elements)
+end
+
+def procedure(name, parameters, annotations, all_elements)
+ nd(:CREATE_PROCEDURE,
+ SQL::Procedure.new(
+ name.value,
+ parameters.to_a,
+ annotations.to_a
+ ),
+ all_elements)
+end
+
+def parameter(name, type, annotations, all_elements)
+ nd(:PARAMETER,
+ SQL::Procedure::Parameter.new(
+ name.value,
+ type.value,
+ annotations.to_a
+ ),
+ all_elements)
+end
+
+def table(name, elements, annotations, all_elements)
+ nd(:CREATE_TABLE,
+ SQL::Table.new(name.value, elements.to_a, annotations.to_a),
+ all_elements)
+end
+
+def column(name, type, column_options, annotations, all_elements)
+ nd(:COLUMN_DEFINITION,
+ SQL::Table::Column.new(
+ name.value, type.value, column_options.to_a, annotations.to_a
+ ),
+ all_elements)
+end
+
+def annotation(type, args, all_elements)
+ type = type.value
+ args = args.to_a
+ nd(:ANNOTATION, SQL::Annotation.new(type, args), all_elements)
+end
+
+def datatype(type, length, val)
+ nd(:DATA_TYPE, SQL::DataType.new(type, length), val)
+end
+
+def nd(type, value, all_elements=value)
+ node = Node.new(type, value, @source)
+ update_pos(node, all_elements)
+ if value.respond_to?(:source=)
+ value.source = node.source
+ end
+ node
+end
+
+def update_pos(node, all_elements)
+ all_elements = all_elements.compact
+ node.start = all_elements.collect{|val| val.start}.compact[0]
+ node.last = all_elements.collect{|val| val.last }.compact[-1]
+ node
+end
+
+class Node
+ attr_reader :type, :value
+ attr_accessor :start, :last
+
+ def initialize(type, value, source)
+ @type = type.to_sym
+ @value = value
+ @source = source
+ end
+
+ def push(elem)
+ raise Exception if leaf?
+ @value << elem
+ self
+ end
+
+ def unshift(elem)
+ raise Exception if leaf?
+ @value.unshift(elem)
+ self
+ end
+
+ def [](index)
+ if leaf?
+ return index = 0 ? @value : nil
+ end
+ if index.is_a?(Integer)
+ return @value[index]
+ end
+ @value.detect{|val| val.type == index.to_sym}
+ end
+
+ def leaf?
+ !@value.is_a?(Array)
+ end
+
+ def name
+ self[:IDENTIFIER].value
+ end
+
+ def children
+ leaf? ? [] : @value
+ end
+
+ def find(node_type)
+ node_type = node_type.to_sym
+ return self if type == node_type
+ if leaf?
+ return nil
+ end
+ result = nil
+ children.each do |child_node|
+ found = child_node.find(node_type)
+ if found
+ result = found
+ break
+ end
+ end
+ result
+ end
+
+ def source
+ start_row = @start[0]
+ start_col = @start[1]
+ last_row = @last[0]
+ last_col = @last[1]
+
+ result = @source[start_row..last_row]
+ if result.length == 1
+ result[0] = result[0][start_col..last_col]
+ else
+ result[0] = result[0][start_col..-1]
+ result[-1] = result[-1][0..last_col]
+ end
+ result.join("\n")
+ end
+
+ def to_a
+ if leaf?
+ return [@value]
+ end
+ @value.collect {|child| child.value}
+ end
+
+ def to_s
+ "#{type}: at line #{start[0]+1}, column #{start[1]+1}\nsource> #{source}"
+ end
+
+ def inspect
+ to_s
+ end
+end
+
+class ASTWalker
+ def walk(ast)
+ walk_node(ast)
+ self
+ end
+
+ def initialize
+ @listeners = Hash.new {|hash, key| hash[key] = []}
+ end
+
+ def visit(note_type, &block)
+ node_type = note_type.to_sym
+ @listeners[node_type] << block
+ self
+ end
+
+ private
+ def walk_node(node)
+ if @listeners.has_key?(node.type)
+ @listeners[node.type].each do |listener|
+ listener.call(node)
+ end
+ end
+ node.children.each do |child_node|
+p node if child_node.nil?
+ walk_node(child_node)
+ end
+ end
+end
+
+# token
+PUNCTUATION = /([;\.,()=@*<>\[\]]|\$\$|\/\/|--)/
+ONE_LINE_COMMENT = /--\s[^@]*$/
+KEYWORD_LIST = [
+ #general term
+ 'NOT',
+ 'NULL',
+ 'DEFAULT',
+ 'AS',
+
+ # sql command
+ 'CREATE',
+ 'TABLE',
+ 'VIEW',
+ 'PROCEDURE',
+ 'INSERT',
+ 'INTO',
+ 'VALUES',
+ 'DELIMITER',
+
+ # table option
+ 'ENGINE',
+ 'MYISAM',
+ 'INNODB',
+ 'HEAP',
+
+ # column option
+ 'AUTO_INCREMENT',
+
+ # data type
+ 'INT',
+ 'CHAR',
+ 'VARCHAR',
+ 'TEXT',
+ 'INTEGER',
+ 'TINYINT',
+ 'SMALLINT',
+ 'BIT',
+ 'DATETIME',
+ 'TIMESTAMP',
+
+ # procedure / function
+ 'BEGIN',
+ 'END',
+ 'IN',
+ 'OUT',
+ 'INOUT',
+
+ # constraint
+ 'CONSTRAINT',
+ 'PRIMARY',
+ 'FOREIGN',
+ 'REFERENCES',
+ 'UNIQUE',
+ 'INDEX',
+ 'KEY',
+ 'FULLTEXT',
+ 'USING',
+ 'SECTIONALIZE',
+
+ #query
+ 'SELECT',
+ 'FROM',
+ 'WHERE',
+
+ #dml
+ 'DELETE',
+ 'UPDATE',
+
+ #boolean expression
+ 'TRUE',
+ 'FALSE',
+ 'IS',
+
+ #triggered action
+ 'ON',
+ 'CASCADE'
+]
+KEYWORD = /(#{KEYWORD_LIST.collect{|k| '\b'+k+'\b'}.join('|')})/ix
+IDENTIFIER = /([a-zA-Z][_0-9a-zA-Z]*)/
+
+# NUMBER
+NUMERIC = /([1-9][0-9]*)/
+INTEGER = /([+|-]?[1-9][0-9]*|0)/
+FLOAT = /(
+ (?: #without exponent part
+ [+|-]?(?:[1-9][0-9]*|0)(?:\.[0-9]+)?
+ | #with exponent part
+ [+|-]?[1-9]+(?:\.[0-9]+)?e[+|-]?[1-9][0-9]*
+ )
+ )/x
+
+# STRING
+NOT_PRINTABLE = '\x00-\x1F\x7F' #including space (\x20)
+MUST_BE_ESCAPED = '\x22\x27\x5C' #quot, double-quot, back-slash
+ESCAPE_TABLE = {
+ '\n' => "\n",
+ '\b' => "\b",
+ '\0' => "\x00",
+ '\t' => "\t",
+ "\\'" => "'",
+ '\"' => '"',
+ "\\\\" => "\\"
+}
+ESCAPED = ESCAPE_TABLE.keys.map{|k|k.gsub(/\\/, "\\\\\\\\")}.join('|')
+CHARACTER_STRING = /(
+ #single-quoted string
+ '
+ (?:
+ [^#{MUST_BE_ESCAPED}#{NOT_PRINTABLE}]
+ |
+ #{ESCAPED}
+ |
+ ''
+ |
+ "
+ )*
+ '
+ |
+ #double-quoted string
+ "
+ (?:
+ [^#{MUST_BE_ESCAPED}#{NOT_PRINTABLE}]
+ |
+ #{ESCAPED}
+ |
+ ""
+ |
+ '
+ )*
+ "
+)/x
+
+# PROCEDURE
+PARAMETER_NAME = /([_a-zA-Z][_0-9a-zA-Z]*)/
+
+def parse(str)
+ @source = str.split(/\n/)
+ @lines = @source.dup
+ @total_lines = @lines.length
+ next_line
+ do_parse
+end
+
+def next_line
+ @line = @lines.empty? \
+ ? nil \
+ : StringScanner.new(@lines.shift)
+end
+
+def current_line
+ @total_lines - (@lines.length + 1)
+end
+
+def pos
+ [current_line, @line.pos]
+end
+
+def next_token
+ token = _next_token
+ return nil unless token
+ token[1].start = @start_pos
+ token[1].last = [pos[0], pos[1]-1]
+#puts token
+ return token
+end
+
+def _next_token
+ return nil unless @line
+ @line.skip(/\s*/)
+ if @in_procedure_code
+ if token = @line.skip_until(/(?=\bEND(?!\s+(?:IF|CASE|WHILE|LOOP))\b)/i)
+ @in_procedure_code = false
+ return tkn(:PROCEDURE_CODE)
+ end
+ @line.terminate
+ end
+ if @line.eos?
+ next_line
+ return _next_token
+ end
+ @start_pos = pos
+ if token = @line.scan(ONE_LINE_COMMENT)
+ return _next_token
+ elsif token = @line.scan(PUNCTUATION)
+ return tkn(token)
+ elsif token = @line.scan(KEYWORD)
+ token = token.upcase
+ if token == 'BEGIN'
+ @in_procedure_code = true
+ end
+ return tkn(token)
+ elsif token = @line.scan(IDENTIFIER)
+ return tkn(:IDENTIFIER, token)
+ elsif token = @line.scan(PARAMETER_NAME)
+ return tkn(:PARAMETER_NAME, token)
+ elsif token = @line.scan(CHARACTER_STRING)
+ return tkn(
+ :CHARACTER_STRING,
+ token[1..-2].gsub(/#{ESCAPED}/x) {|s|ESCAPE_TABLE[s]}\
+ .gsub(/""/, '"')\
+ .gsub(/''/, "\'")
+ )
+ elsif token = @line.scan(NUMERIC)
+ return tkn(:NUMERIC, token.to_i)
+ elsif token = @line.scan(INTEGER)
+ return tkn(:INTEGER, token.to_i)
+ elsif token = @line.scan(FLOAT)
+ return tkn(:FLOAT, token.to_f)
+ else
+ return tkn(:UNEXPECTED_TOKEN, @line.scan(/.*/))
+ end
+end
+
+def tkn(type, value=type)
+ [type, Node.new(type, value, @source)]
+end
+
--- /dev/null
+#!/usr/bin/ruby -Ku
+# -*- encoding: UTF-8 -*-
+
+require 'net/smtp'
+
+$__sent_mail_box = $__sent_mail_box || []
+class Net::SMTP
+ def self.sent_mail_box
+ $__sent_mail_box
+ end
+
+ def send_message(message, from, to)
+ Net::SMTP.sent_mail_box << message
+ end
+end
--- /dev/null
+#!/usr/bin/ruby -Ku
+# -*- encoding: UTF-8 -*-
+
+require 'tc_base.rb'
+require 'tc_html.rb'
+require 'tc_database.rb'
+require 'tc_sql.rb'
+require 'tc_rest.rb'
+
--- /dev/null
+#!/usr/bin/ruby -Ku
+# -*- encoding: UTF-8 -*-
+
+require 'test/unit'
+require 'base64'
+require 'hdboo/base'
+
+include Hdboo
+
+class TestResource < Test::Unit::TestCase
+ def test_resource
+ 3.times do
+ source_file = resource('hdboo/base.rb')
+ assert_not_nil source_file
+ assert_match /module Hdboo/, source_file
+ end
+ assert ! resource_exist?('no_such/file')
+ assert_raise RuntimeError do
+ resource('no_such/file')
+ end
+ end
+
+ def test_template
+ $:.unshift File.join(File.dirname(__FILE__), '..')
+ 3.times do
+ page = template('testtemplate/sample.txt') do
+ @first_name = 'hogeo'
+ @last_name = 'hogeta'
+ @occupation = 'programmer'
+ @data = 'test_data'
+ end
+
+ assert_not_nil page
+ assert_match /name\: hogeo hogeta/, page
+ assert_match /#{Base64.encode64('test_data')}/x, page
+ end
+ end
+
+ def test_template_base_url_replace
+ Hdboo::TemplateContext.base_url = 'http://www.example.com/'
+ page = template('testtemplate/sample.html') {}
+ assert_match(/<base href='http:\/\/www.example.com\/' \/>/, page)
+ end
+end
+
+class TestHashAsObject < Test::Unit::TestCase
+ def test_hash_as_object
+ obj = HashAsObject.new(
+ 'hoge' => 'hoge_value',
+ 'fuga' => 'fuga_value',
+ 'is_piyo' => 0,
+ 'has_hoge' => 1
+ )
+ assert_equal 'hoge_value', obj.hoge
+ assert_equal 'fuga_value', obj.fuga
+ assert !obj.piyo?
+ assert obj.has_hoge?
+ assert_raise(NoMethodError) do
+ obj.bar\r end
+
+ obj = HashAsObject.new(
+ :hoge => 'hoge_value',
+ :fuga => 'fuga_value',
+ :is_piyo => 0,
+ :has_hoge => 1
+ )
+ assert_equal 'hoge_value', obj.hoge
+ assert_equal 'fuga_value', obj.fuga
+ assert !obj.piyo?
+ assert obj.has_hoge?
+ assert_raise(NoMethodError) do
+ obj.bar
+ end
+ end
+end
+
+class TestValidatable < Test::Unit::TestCase
+ class ValidatableObject
+ include Validatable
+ attr_accessor :name, :data
+ def initialize(name, data)
+ @name, @data = name, data
+ end
+
+ def validators_for(key)
+ case key.to_sym
+ when :name then [
+ Validator::NotNil.new,
+ Validator::NotBlank.new,
+ Validator::KanaKanji.new
+ ]
+ when :data then [
+ Validator::NotNil.new
+ ]
+ end
+ end
+
+ def to_hash
+ {:name => @name, :data => @data}
+ end
+ end
+
+ def test_validate
+ hogeta = ValidatableObject.new(
+ 'ほげ田ほげ夫',
+ 'age:45, gender:male, occupation:Neet'
+ )
+ assert !hogeta.invalid?
+ assert_nothing_raised do
+ hogeta.validate\r end
+
+ hogeta.name = nil
+ exception = nil
+ begin
+ hogeta.validate
+ rescue
+ exception = $!
+ end
+
+ assert hogeta.invalid?
+ assert ! exception.nil?
+ assert exception.is_a?(InvalidArgument)
+ messages = exception.messages
+ assert_equal 1, messages.size
+ assert_equal 'must not be nil.', messages[:name].first
+
+ hogeta.name = ''
+ exception = nil
+ begin
+ hogeta.validate
+ rescue
+ exception = $!
+ end
+
+ assert hogeta.invalid?
+ assert ! exception.nil?
+ assert exception.is_a?(InvalidArgument)
+ messages = exception.messages
+ assert_equal 1, messages.size
+ assert_equal 'must not be blank.', messages[:name].first
+
+ hogeta.name = 'hogeta-hogeo'
+ hogeta.data = nil
+ exception = nil
+ begin
+ hogeta.validate
+ rescue
+ exception = $!
+ end
+
+ assert hogeta.invalid?
+ assert ! exception.nil?
+ assert exception.is_a?(InvalidArgument)
+ messages = exception.messages
+ assert_equal 2, messages.size
+ assert_equal 'must be japanese kana or kanji, but was hogeta-hogeo.',
+ messages[:name].first
+ assert_equal 'must not be nil.', messages[:data].first
+ end
+
+end
+class TestValidator < Test::Unit::TestCase
+ def test_NotNil
+ validator = Validator::NotNil.new
+ assert validator.pass?(1)
+ assert !validator.pass?(nil)
+ end
+
+ def test_NotBlank
+ validator = Validator::NotBlank.new
+ assert validator.pass?(nil)
+ assert !validator.pass?('')
+ assert validator.pass?('test')
+ end
+
+ def test_In
+ validator = Validator::In.new(5..6)
+ assert validator.pass?(nil)
+ assert validator.pass?(6)
+ assert !validator.pass?(1)
+ end
+
+ def test_MaxLength
+ validator = Validator::MaxLength.new(10)
+ assert validator.pass?(nil)
+ assert validator.pass?('')
+ assert validator.pass?((['a']*10).join)
+ assert !validator.pass?((['a']*11).join)
+ assert validator.pass?((['あ']*10).join)
+ assert !validator.pass?((['あ']*11).join)
+ assert validator.pass?((['あa']*5).join)
+ assert !validator.pass?((['あa']*5).join + 'a')
+ end
+
+ def test_MinLength
+ validator = Validator::MinLength.new(10)
+ assert validator.pass?(nil)
+ assert validator.pass?('')
+ assert validator.pass?((['a']*10).join)
+ assert !validator.pass?((['a']*9).join)
+ assert validator.pass?((['あ']*10).join)
+ assert !validator.pass?((['あ']*9).join)
+ assert validator.pass?((['あa']*5).join)
+ assert !validator.pass?((['あa']*4).join + 'a')
+ end
+
+ def test_Printable
+ validator = Validator::Printable.new
+ assert validator.pass?(nil)
+ assert validator.pass?('')
+ assert validator.pass?('aa')
+ assert validator.pass?('dradt|ed_e@_edwr')
+ assert !validator.pass?('drayte drwed')
+ end
+
+ def test_Hiragana
+ validator = Validator::Hiragana.new
+ assert validator.pass?(nil)
+ assert validator.pass?('')
+ assert validator.pass?('いろはにほへと')
+ assert !validator.pass?('いろはにホへと')
+ assert !validator.pass?('いろはに歩へと')
+ assert !validator.pass?('いろはにHoへと')
+ end
+
+ def test_Katakana
+ validator = Validator::Katakana.new
+ assert validator.pass?(nil)
+ assert validator.pass?('')
+ assert validator.pass?('イロハニホヘト')
+ assert !validator.pass?('イロハニほヘト')
+ assert !validator.pass?('イロハニ歩ヘト')
+ assert !validator.pass?('イロハニHoヘト')
+ end
+
+ def test_Kanji
+ validator = Validator::Kanji.new
+ assert validator.pass?(nil)
+ assert validator.pass?('')
+ assert validator.pass?('意炉葉二歩屁戸')
+ assert !validator.pass?('意炉葉二ほ屁戸')
+ assert !validator.pass?('意炉葉二ホ屁戸')
+ assert !validator.pass?('意炉葉二Ho屁戸')
+ end
+
+ def test_Kana
+ validator = Validator::Kana.new
+ assert validator.pass?(nil)
+ assert validator.pass?('')
+ assert validator.pass?('イロハニホヘト')
+ assert validator.pass?('イロハニほヘト')
+ assert !validator.pass?('イロハニ歩ヘト')
+ assert !validator.pass?('イロハニHoヘト')
+ assert !validator.pass?('意炉葉二歩屁戸')
+ assert !validator.pass?('意炉葉二ほ屁戸')
+ end
+
+ def test_KanaKanji
+ validator = Validator::KanaKanji.new
+ assert validator.pass?(nil)
+ assert validator.pass?('')
+ assert validator.pass?('イロハニホヘト')
+ assert validator.pass?('イロハニほヘト')
+ assert validator.pass?('イロハニ歩ヘト')
+ assert !validator.pass?('イロハニHoヘト')
+ assert validator.pass?('意炉葉二歩屁戸')
+ assert validator.pass?('意炉葉二ほ屁戸')
+ end
+
+ def test_MailAddress
+ validator = Validator::MailAddress.new
+ assert validator.pass?(nil)
+ assert validator.pass?('')
+ assert validator.pass?('syok_hanako@syok.jp')
+ assert validator.pass?('syok.hanako@syok.jp')
+ assert !validator.pass?('syok_hanako.@syok.jp')
+ assert !validator.pass?('syok_hanako..@syok.jp')
+ assert validator.pass?('"syok_hanako."@syok.jp')
+ assert !validator.pass?('syok..hanako@syok.jp')
+ assert validator.pass?('"syok..hanako"@syok.jp')
+ assert !validator.pass?('syok hanako@syok.jp')
+ assert validator.pass?('"syok hanako"@syok.jp')
+ assert validator.pass?('syok_hanako@SYOK.JP')
+ assert !validator.pass?('syok_hanako@syok.JP')
+ assert !validator.pass?('syok_hanako@.syok.jp')
+ assert validator.pass?('syok_hanako@sy-ok.jp')
+ assert !validator.pass?('syok_hanako@syok.-jp')
+ assert validator.pass?('syok_hanako@syok.jp0')
+ assert !validator.pass?('syok_hanako@syok.0jp')
+ assert validator.pass?('syok%hanako@syok.jp')
+ assert validator.pass?('syok-hanako@syok.jp')
+ assert validator.pass?('syok/hanako@syok.jp')
+ end
+end
--- /dev/null
+#!/usr/bin/ruby -Ku
+# -*- encoding: UTF-8 -*-
+$:.unshift("#{File.dirname(__FILE__)}/../lib/")
+
+require 'fileutils'
+require 'test/unit'
+
+require 'hdboo/crawler'
+
+include Hdboo
+include FileUtils
+
+class TestFileSystemCrawler < Test::Unit::TestCase
+
+ public
+ def test_crawls_on_all_files_whose_path_matches_the_grob_given_as_a_arg
+ given_documents_are_in_TEST_DOCS_dir
+ when_it_crawls_with_a_grob_matches_all_files_under_TEST_DOCS_dir
+ then_ensure_it_processes_the_all_documents_under_TEST_DOCS_dir
+ ensure
+ teardown_TEST_dir
+ end
+
+ TEST_DIR_PATH = './TEST/'
+ TEST_DOCS_DIR_PATH = './TEST/DOCS/'
+
+ private
+ def given_documents_are_in_TEST_DOCS_dir
+ dir1 = TEST_DOCS_DIR_PATH + 'dir1/'
+ dir2 = TEST_DOCS_DIR_PATH + 'dir2/'
+ mkdir_p(dir1)
+ mkdir_p(dir2)
+ option = File::CREAT|File::TRUNC|File::RDWR
+ cd(dir1) do
+ File.open('file1.txt', option) do |file|
+ file << 'Hello world!'
+ end
+ File.open('file2.txt', option) do |file|
+ file << 'What a wornderful world!'
+ end
+ end
+ cd(dir2) do
+ File.open('file1.txt', option) do |file|
+ file << 'Hello world!'
+ end
+ end
+ end
+
+ private
+ def when_it_crawls_with_a_grob_matches_all_files_under_TEST_DOCS_dir
+ @processed_path_list = []
+ crawler = FileSystemCrawler.new
+ crawler.crawl(TEST_DOCS_DIR_PATH + '**/*') do |file|
+ @processed_path_list << file.path
+ end
+ end
+
+ private
+ def then_ensure_it_processes_the_all_documents_under_TEST_DOCS_dir
+ processed_pathes = @processed_path_list.join(', ')
+ assert_equal 3, @processed_path_list.size, "processed_pathes: #{processed_pathes}"
+ assert_match /#{TEST_DOCS_DIR_PATH}dir1\/file1.txt/, processed_pathes.to_s
+ assert_match /#{TEST_DOCS_DIR_PATH}dir1\/file2.txt/, processed_pathes.to_s
+ assert_match /#{TEST_DOCS_DIR_PATH}dir2\/file1.txt/, processed_pathes.to_s
+ end
+
+ private
+ def teardown_TEST_dir
+ rm_rf(TEST_DIR_PATH)
+ end
+
+ public
+ def test_processes_each_grob_when_multiple_grobs_are_given_as_vargs
+ given_documents_are_in_TEST_DOCS_dir
+ when_it_crawls_with_grobs_one_of_which_matches_dir1_and_another_matches_dir2
+ then_ensure_it_processes_the_all_documents_under_TEST_DOCS_dir1_and_dir2
+ ensure
+ teardown_TEST_dir
+ end
+
+ private
+ def when_it_crawls_with_grobs_one_of_which_matches_dir1_and_another_matches_dir2
+ @processed_path_list = []
+ crawler = FileSystemCrawler.new
+ crawler.crawl(
+ TEST_DOCS_DIR_PATH + 'dir1/**/*',
+ TEST_DOCS_DIR_PATH + 'dir2/**/*'
+ ) do |file|
+ @processed_path_list << file.path
+ end
+ end
+
+ private
+ def then_ensure_it_processes_the_all_documents_under_TEST_DOCS_dir1_and_dir2
+ then_ensure_it_processes_the_all_documents_under_TEST_DOCS_dir
+ end
+
+ public
+ def test_processes_a_file_only_once_though_its_path_matches_two_or_more_grobs
+ given_documents_are_in_TEST_DOCS_dir
+ when_it_crawls_with_grobs_one_of_which_matches_parent_dir_and_another_matches_sub_dir
+ then_ensure_it_processes_the_all_documents_under_TEST_DOCS_dir_only_once
+ ensure
+ teardown_TEST_dir
+ end
+
+ private
+ def when_it_crawls_with_grobs_one_of_which_matches_parent_dir_and_another_matches_sub_dir
+ @processed_path_list = []
+ crawler = FileSystemCrawler.new
+ crawler.crawl(
+ TEST_DOCS_DIR_PATH + '**/*',
+ TEST_DOCS_DIR_PATH + 'dir1/**/*'
+ ) do |file|
+ @processed_path_list << file.path
+ end
+ end
+
+ private
+ def then_ensure_it_processes_the_all_documents_under_TEST_DOCS_dir_only_once
+ then_ensure_it_processes_the_all_documents_under_TEST_DOCS_dir
+ end
+end
+
+
+class TestWebCrawler #< Test::Unit::TestCase
+ def test_initialize
+ crawler = setup_crawler
+ assert_equal 3, crawler.path_stack.size
+ end
+
+ def test_crawl
+ crawler = setup_crawler
+ url_list = []
+ crawler.crawl {|page| url_list << page.base_uri}
+ assert_equal 3, url_list.size
+ end
+
+ def setup_crawler
+ WebCrawler.new(
+ 'http://www.example.com',
+ 'http://www.example.org',
+ 'http://www.example.net'
+ )
+ end
+end
--- /dev/null
+#!/usr/bin/ruby -Ku
+# -*- encoding: UTF-8 -*-
+
+require 'test/unit'
+require 'hdboo/database'
+
+include Hdboo
+
+class TestDatabase < Test::Unit::TestCase
+
+ def test_create_view_and_subtype_mapping
+ end
+
+end
--- /dev/null
+#!/usr/bin/ruby -Ku
+# -*- encoding: UTF-8 -*-
+
+require 'test/unit'
+require 'hdboo/html'
+
+class TestToHTML < Test::Unit::TestCase
+
+ def test_String_to_html
+ assert 'hoge'.respond_to?(:to_html)
+ assert_equal 'hoge', 'hoge'.to_html
+ assert_equal "hoge\nfuga", "hoge\nfuga".to_html
+ end
+
+ def test_String_to_html_with_indentation
+ assert_equal ' hoge', 'hoge'.to_html(nil, 1)
+ assert_equal " hoge\n fuga", "hoge\nfuga".to_html(nil, 1)
+ end
+
+ def test_Nil_to_html
+ assert nil.respond_to?(:to_html)
+ assert_equal '', nil.to_html
+ end
+
+ def test_Hash_to_html
+ hash = {:hoge => 'hoge_value', :fuga => 'fuga_value'}
+ assert hash.respond_to?(:to_html)
+
+ html = hash.to_html
+ assert_match /^<dl>/, html
+ assert(html.include?([
+ ' <dt>',
+ ' hoge',
+ ' </dt>',
+ ' <dd>',
+ ' hoge_value',
+ ' </dd>',
+ ].join("\n")))
+ assert(html.include?([
+ ' <dt>',
+ ' fuga',
+ ' </dt>',
+ ' <dd>',
+ ' fuga_value',
+ ' </dd>'
+ ].join("\n")))
+ assert_match /<\/dl>$/, html
+ end
+
+ def test_Array_to_html
+ array = ['hoge', 'fuga', 'piyo']
+ assert array.respond_to?(:to_html)
+ assert_equal([
+ '<ol>',
+ ' <li>',
+ ' hoge',
+ ' </li>',
+ ' <li>',
+ ' fuga',
+ ' </li>',
+ ' <li>',
+ ' piyo',
+ ' </li>',
+ '</ol>'
+ ].join("\n"), array.to_html)
+ end
+
+ class TestClass
+ attr_reader :fuga, :piyo
+ def initialize(fuga, piyo)
+ @fuga = fuga
+ @piyo = piyo
+ end
+ end
+
+ def test_Object_to_html
+ obj = TestClass.new('fuga_value', 'piyo_value')
+ assert obj.respond_to?(:to_html)
+ assert_equal obj.to_html, obj.to_s
+
+ def obj.to_hash
+ {:fuga => @fuga, :piyo => @piyo}
+ end
+ html = obj.to_html
+ assert_match /^<dl>/, html
+ assert(html.include?([
+ ' <dt>',
+ ' piyo',
+ ' </dt>',
+ ' <dd>',
+ ' piyo_value',
+ ' </dd>',
+ ].join("\n")))
+ assert(html.include?([
+ ' <dt>',
+ ' fuga',
+ ' </dt>',
+ ' <dd>',
+ ' fuga_value',
+ ' </dd>',
+ ].join("\n")))
+ assert_match /<\/dl>/, html
+ end
+
+ def test_compound_object_to_html
+ hash = {
+ :str => 'str_value',
+ :nested_hash => {:hoge => 'hoge_value', :fuga => 'fuga_value'},
+ :nested_array => ['item1', 'item2']
+ }
+ html = hash.to_html
+ assert_match /^<dl>/, html
+ assert(html.include?([
+ ' <dt>',
+ ' nested_array',
+ ' </dt>',
+ ' <dd>',
+ ' <ol>',
+ ' <li>',
+ ' item1',
+ ' </li>',
+ ' <li>',
+ ' item2',
+ ' </li>',
+ ' </ol>',
+ ' </dd>',
+ ].join("\n")))
+ assert(html.include?([
+ ' <dt>',
+ ' str',
+ ' </dt>',
+ ' <dd>',
+ ' str_value',
+ ' </dd>',
+ ].join("\n")))
+ assert(html.include?([
+ ' <dt>',
+ ' nested_hash',
+ ' </dt>',
+ ' <dd>',
+ ' <dl>',
+ ].join("\n")))
+ assert(html.include?([
+ ' <dt>',
+ ' hoge',
+ ' </dt>',
+ ' <dd>',
+ ' hoge_value',
+ ' </dd>',
+ ].join("\n")))
+ assert(html.include?([
+ ' <dt>',
+ ' fuga',
+ ' </dt>',
+ ' <dd>',
+ ' fuga_value',
+ ' </dd>',
+ ].join("\n")))
+ assert(html.include?([
+ ' </dl>',
+ ' </dd>',
+ ].join("\n")))
+ assert_match /<\/dl>/, html
+ end
+end
--- /dev/null
+#!/usr/bin/ruby -Ku
+# -*- encoding:UTF-8 -*-
+$:.unshift("#{File.dirname(__FILE__)}/../lib/")
+
+require 'test/unit'
+require 'fileutils'
+require 'hdboo/mime'
+
+include FileUtils
+
+class RbFileTest < Test::Unit::TestCase
+ public
+ def test_identifies_its_mime
+ given_a_plain_text_file
+ then_its_mime_type_is_text_plain
+ ensure
+ clean_up
+ end
+
+ TEST_DIR_PATH = './test_temp/'
+
+ private
+ def given_a_plain_text_file
+ mkdir_p TEST_DIR_PATH
+ cd(TEST_DIR_PATH) do
+ open('ascii_plain.txt', File::CREAT|File::RDWR) do |file|
+ file << 'hello world!'
+ end
+ end
+ end
+
+ private
+ def then_its_mime_type_is_text_plain
+ file_path = "#{TEST_DIR_PATH}ascii_plain.txt"
+ assert_equal 'text/plain charset=us-ascii', \
+ File.new(file_path).mime_type
+ assert_equal 'text/plain charset=us-ascii', \
+ File.mime_type(file_path)
+ end
+
+ private
+ def clean_up
+ rm_rf TEST_DIR_PATH
+ end
+end
+
--- /dev/null
+#!/usr/bin/ruby -Ku
+# -*- encoding: UTF-8 -*-
+
+require 'digest/md5'
+require 'test/unit'
+require 'rubygems'
+require 'json'
+require 'hdboo/rest'
+
+include Hdboo
+class TestREST < Test::Unit::TestCase
+ class TestServer < REST::Server
+ @@users = {
+ 'hogeta_hogeo' => {
+ :username => 'hogeta_hogeo',
+ :mail => 'hogeta.hogeo@example.com',
+ :password => 'hogehoge'
+ }
+ }
+
+ def password_digest(username)
+ user = @@users.find{|k, any| any[:username] == username}.last
+ return nil unless user
+ Digest::MD5::hexdigest(
+ "#{user[:username]}:#{base_realm}:#{user[:password]}"
+ )
+ end
+
+ def initialize
+ base_url 'localhost'
+ base_realm 'test_realm'
+
+ respond_to 'POST /user/' do |req|
+ username = req.params[:username]
+ password = req.params[:password]
+ mail = req.params[:mail]
+ @@users[username] = {
+ :username => username,
+ :mail => mail
+ }
+ Created @@users[username]
+ end
+
+ respond_to 'GET /user/LIST/' do |req|
+ OK @@users.values
+ end
+
+ respond_to 'GET /user/{username}/' do |req|
+ username = req.params.username
+ unless @@users.has_key?(username)
+ raise NotFound, {:username, 'does not be registered in this system.'}
+ end
+ OK @@users[username]
+ end
+
+ respond_to 'DELETE /user/{username}/' do |req|
+ username = req.params.username
+ raise Unauthorized unless req.authenticated_as?(username)
+ @@users.delete(req.params[:username])
+ OK username
+ end
+ end
+ end
+
+ def setup
+ @testServer = TestServer.new
+ end
+
+ def test_post_method
+ response = @testServer.test_run({
+ 'REQUEST_METHOD' => 'POST',
+ 'SCRIPT_NAME' => '/user/',
+ 'HTTP_ACCEPT' => 'application/json'
+ }, {
+ :username => 'fugata_fugao',
+ :mail => 'fugata.fugao@example.com',
+ :password => 'fugafuga'
+ })
+ expect = {
+ :username => 'fugata_fugao',
+ :mail => 'fugata.fugao@example.com'
+ }.to_json
+
+ assert_equal [
+ 'Status: 201',
+ 'Content-Type: application/json',
+ "Content-Length: #{expect.length}",
+ '',
+ expect
+ ].join(CGI::EOL), response
+ end
+
+ def test_get_method_json
+ response = @testServer.test_run(
+ 'REQUEST_METHOD' => 'GET',
+ 'SCRIPT_NAME' => '/user/hogeta_hogeo/',
+ 'HTTP_ACCEPT' => 'application/json, text/plain'
+ )
+ expect = {
+ :username => 'hogeta_hogeo',
+ :mail => 'hogeta.hogeo@example.com',
+ :password => 'hogehoge'
+ }.to_json
+
+ assert_equal [
+ 'Status: 200',
+ 'Content-Type: application/json',
+ "Content-Length: #{expect.length}",
+ '',
+ expect
+ ].join(CGI::EOL), response
+
+ response = @testServer.test_run(
+ 'REQUEST_METHOD' => 'GET',
+ 'SCRIPT_NAME' => '/user/nobody/'
+ )
+ assert_match /^Status\: 404/, response
+
+ response = @testServer.test_run(
+ 'REQUEST_METHOD' => 'GET',
+ 'SCRIPT_NAME' => '/user/LIST/'
+ )
+ assert_match /^Status\: 200/, response
+ end
+
+ def test_get_method_html
+ response = @testServer.test_run(
+ 'REQUEST_METHOD' => 'GET',
+ 'SCRIPT_NAME' => '/user/hogeta_hogeo/'
+ )
+ assert(response.include?([
+ 'Status: 200',
+ 'Content-Type: text/html',
+ 'Content-Length: '
+ ].join(CGI::EOL)))
+ assert(response.include?([
+ '<head>',
+ ' <title>200: Success</title>',
+ '</head>',
+ '<body>',
+ ' <dl>',
+ ].join("\n")))
+ assert(response.include?([
+ ' <dt>',
+ ' username',
+ ' </dt>',
+ ' <dd>',
+ ' hogeta_hogeo',
+ ' </dd>',
+ ].join("\n")))
+ assert(response.include?([
+ ' <dt>',
+ ' mail',
+ ' </dt>',
+ ' <dd>',
+ ' hogeta.hogeo@example.com',
+ ' </dd>',
+ ].join("\n")))
+ assert(response.include?([
+ ' <dt>',
+ ' password',
+ ' </dt>',
+ ' <dd>',
+ ' hogehoge',
+ ' </dd>',
+ ].join("\n")))
+ assert(response.include?([
+ ' </dl>',
+ '</body>'
+ ].join("\n")))
+
+ response = @testServer.test_run(
+ 'REQUEST_METHOD' => 'GET',
+ 'SCRIPT_NAME' => '/user/nobody/'
+ )
+ assert_match /^Status\: 404/, response
+ assert(response.include?([
+ 'Status: 404',
+ 'Content-Type: text/html',
+ 'Content-Length: '
+ ].join(CGI::EOL)))
+ assert(response.include?([
+ '<head>',
+ ' <title>404: NotFound</title>',
+ '</head>',
+ '<body>',
+ ' <dl>',
+ ' <dt>',
+ ' username',
+ ' </dt>',
+ ' <dd>',
+ ' does not be registered in this system.',
+ ' </dd>',
+ ' </dl>',
+ '</body>',
+ ].join("\n")))
+
+ response = @testServer.test_run(
+ 'REQUEST_METHOD' => 'GET',
+ 'SCRIPT_NAME' => '/user/LIST/'
+ )
+ assert(response.include?([
+ 'Status: 200',
+ 'Content-Type: text/html',
+ 'Content-Length: '
+ ].join(CGI::EOL)))
+ assert(response.include?([
+ '<head>',
+ ' <title>200: Success</title>',
+ '</head>',
+ '<body>',
+ ' <ol>',
+ ' <li>',
+ ' <dl>'
+ ].join("\n")))
+ assert(response.include?([
+ ' <dt>',
+ ' username',
+ ' </dt>',
+ ' <dd>',
+ ' hogeta_hogeo',
+ ' </dd>',
+ ].join("\n")))
+ assert(response.include?([
+ ' <dt>',
+ ' mail',
+ ' </dt>',
+ ' <dd>',
+ ' hogeta.hogeo@example.com',
+ ' </dd>',
+ ].join("\n")))
+ assert(response.include?([
+ ' <dt>',
+ ' password',
+ ' </dt>',
+ ' <dd>',
+ ' hogehoge',
+ ' </dd>',
+ ].join("\n")))
+ assert(response.include?([
+ ' </dl>',
+ ' </li>',
+ ' </ol>',
+ '</body>'
+ ].join("\n")))
+ end
+
+ def test_authorization_error
+ response = @testServer.test_run({
+ 'REQUEST_METHOD' => 'DELETE',
+ 'SCRIPT_NAME' => '/user/hogeta_hogeo/',
+ })
+ assert_match /^Status\: 401/, response
+ assert_match /WWW-Authenticate\: Digest/, response
+ assert_match /realm="test_realm"/, response
+ assert /nonce="(.{30,})"/ =~ response
+ nonce = $1
+ assert_match /algorithm=MD5/, response
+ assert_match /qop=auth/, response
+ assert_match /<title>401/, response
+
+ method = 'DELETE'
+ username = 'hogeta_hogeo'
+ password = 'hogehoge'
+ realm = 'test_realm'
+ uri = '/user/hogeta_hogeo/'
+ qop = 'auth'
+ nc = '00000001'
+ cnonce =Digest::MD5::hexdigest(nonce+uri)
+ opaque =Digest::MD5::hexdigest(Time.now.to_f.to_s)
+
+ password_digest = Digest::MD5::hexdigest([
+ username, realm, password
+ ].join(':'))
+
+ ha2 = Digest::MD5.hexdigest(method + ':' + uri)
+
+ response = Digest::MD5::hexdigest([
+ password_digest,
+ nonce,
+ nc,
+ cnonce,
+ qop,
+ ha2
+ ].join(':'))
+
+ response = @testServer.test_run({
+ 'REQUEST_METHOD' => 'DELETE',
+ 'SCRIPT_NAME' => '/user/hogeta_hogeo/',
+ 'HTTP_AUTHORIZATION_USERNAME' => username,
+ 'HTTP_AUTHORIZATION_REALM' => realm,
+ 'HTTP_AUTHORIZATION_NONCE' => nonce,
+ 'HTTP_AUTHORIZATION_URI' => uri,
+ 'HTTP_AUTHORIZATION_QOP' => qop,
+ 'HTTP_AUTHORIZATION_NC' => nc,
+ 'HTTP_AUTHORIZATION_CNONCE' => cnonce,
+ 'HTTP_AUTHORIZATION_OPAQUE' => opaque,
+ 'HTTP_AUTHORIZATION_RESPONSE' => response
+ })
+ assert_match /^Status\: 200/, response
+ end
+
+
+ def test_not_found_error
+ response = @testServer.test_run({
+ 'REQUEST_METHOD' => 'GET',
+ 'SCRIPT_NAME' => '/user/unknown_user/',
+ 'HTTP_ACCEPT' => 'application/json'
+ })
+ assert_match /Status: 404/, response
+ assert_match /"username":"does not be registered in this system\."/,
+ response
+
+ response = @testServer.test_run({
+ 'REQUEST_METHOD' => 'GET',
+ 'SCRIPT_NAME' => '/user/unknown_user/',
+ 'HTTP_ACCEPT' => 'text/html'
+ })
+ assert_match /Status: 404/, response
+ assert_match /<title>404/, response
+ assert(response.include?([
+ '<head>',
+ ' <title>404: NotFound</title>',
+ '</head>',
+ '<body>',
+ ' <dl>',
+ ' <dt>',
+ ' username',
+ ' </dt>',
+ ' <dd>',
+ ' does not be registered in this system.',
+ ' </dd>',
+ ' </dl>',
+ '</body>'
+ ].join("\n")))
+ end
+end
--- /dev/null
+#!/usr/bin/ruby -Ku
+# -*- encoding: UTF-8 -*-
+$:.unshift "#{File.dirname(__FILE__)}/../lib/"
+
+require 'test/unit'
+require 'fileutils'
+
+require 'hdboo/search'
+
+include Hdboo
+include FileUtils
+
+class DocumentTest < Test::Unit::TestCase
+ public
+ def test_is_created_from_a_file
+ given_a_text_file_is_in_the_test_dir
+ when_a_document_object_is_created_from_the_file
+ ensure_the_object_is_not_nil_and_instance_of_document
+ ensure
+ clean_up
+ end
+
+ private
+ def given_a_text_file_is_in_the_test_dir
+ rm_rf 'test'
+ mkdir 'test'
+ cd('test') do
+ open('file.txt', File::CREAT|File::RDWR) do |file|
+ file << 'Hello World!!\n'
+ end
+ @file = File.new('file.txt')
+ end
+ end
+
+ private
+ def clean_up
+ @file.close
+ @document = nil
+ rm_rf('./test')
+ end
+
+ private
+ def when_a_document_object_is_created_from_the_file
+ @document = Search::Document.new(@file)
+ end
+
+ private
+ def ensure_the_object_is_not_nil_and_instance_of_document
+ assert ! @document.nil?
+ assert @document.is_a?(Search::Document)
+ end
+
+ public
+ def test_has_text_and_term_list_of_it
+ given_a_text_file_is_in_the_test_dir
+ when_a_document_object_is_created_from_the_file
+ ensure_the_text_equals_to_the_files_content
+ ensure_the_term_list_is_correct
+ ensure
+ clean_up
+ end
+
+ private
+ def ensure_the_text_equals_to_the_files_content
+ assert ! @document.text.nil?
+ assert_equal 'Hello World!!\n', @document.text
+ end
+
+ private
+ def ensure_the_term_list_is_correct
+ assert @document.term_list.is_a?(Array)
+ assert_equal ['hellow', 'world'].sort.join(', '), \
+ @document.term_list.sort.join(', ')
+ end
+
+ public
+ def test_has_mime_type
+ given_a_text_file_is_in_the_test_dir
+ when_a_document_object_is_created_from_the_file
+ ensure_its_mime_type_field_is_text_plain
+ ensure
+ clean_up
+ end
+
+ private
+ def ensure_its_mime_type_field_is_text_plain
+ assert_match /text\/plain/, @document.mime_type
+ end
+
+ public
+ def _test_has_fields_which_represent_documents_property
+ given_a_text_file_is_in_the_test_dir
+ when_a_document_object_is_created_from_the_file
+ ensure_its_doc_path_field_is_its_absolute_path
+ ensure_its_doc_name_field_is_its_file_name
+ ensure_its_doc_id_field_is_nil
+
+ end
+end
+
+class HtmlScannerTest #< Test::Unit::TestCase
+ public
+ def test_scanns_html_and_stores_text_of_body_and_title__tag_in_default_zone
+ given_a_html_file_is_in_the_test_dir
+ when_a_document_object_is_created_from_the_file
+ ensure_its_default_zone_holds_inner_text_of_body_and_title_tag
+ end
+
+ public
+ def test_stores_innertext_of_particular_tags_in_separate_zones
+ given_a_html_file_is_in_the_test_dir
+ when_a_document_object_is_created_from_the_file
+ ensure_innertext_of_title_tag_is_stored_in_title_zone
+ ensure_innertext_of_all_heading_tags_is_stored_in_table_of_contents_zone
+ end
+
+ public
+ def test_scanns_particular_tags_in_header_and_stores_its_values_to_fields
+ given_a_html_file_is_in_the_test_dir
+ when_a_document_object_is_created_from_the_file
+ ensure_its_language_field_equals_to_the_value_assigned_in_metatag
+ end
+end
+
+class IndexTest # < Test::Unit::TestCase
+ public
+ def test_can_be_defined_with_sql_create_table_statement
+ end
+
+ public
+ def test_can_be_queried_with_sql_select_statement
+ end
+end
+
+
+
+class FileSystemIndexerTest #< Test::Unit::TestCase
+ public
+ def test_indexes_all_files_whose_path_matches_with_a_grob_given_as_arg
+ given_documents_are_in_TEST_DOCS_dir
+ when_it_crawls_with_a_grob_matches_all_files_under_TEST_DOCS_dir
+ then_ensure_the_all_documents_under_TEST_DOCS_dir_are_indexed_properly_in_the_index
+ ensure
+ teardown_TEST_dir
+ end
+
+ TEST_DIR_PATH = './TEST/'
+ TEST_DOCS_DIR_PATH = './TEST/DOCS/'
+ INDEX_PATH = './TEST/INDEX'
+
+ private
+ def given_documents_are_in_TEST_DOCS_dir
+ dir1 = TEST_DOCS_DIR_PATH + 'dir1/'
+ dir2 = TEST_DOCS_DIR_PATH + 'dir2/'
+ mkdir_p(dir1)
+ mkdir_p(dir2)
+ cd(dir1) do
+ File.open('file1.txt', File::CREAT|File::TRUNC|File::RDWR) do |file|
+ file << 'Hello world!'
+ end
+ File.open('file2.txt', File::CREAT|File::TRUNC|File::RDWR) do |file|
+ file << 'What a wornderful world!'
+ end
+ end
+ cd(dir2) do
+ File.open('file1.txt', File::CREAT|File::TRUNC|File::RDWR) do |file|
+ file << 'Hello world!'
+ end
+ end
+ end
+
+ private
+ def when_it_crawls_with_a_grob_matches_all_files_under_TEST_DOCS_dir
+ indexer = FileSystemIndexer.new(
+ :index_path => INDEX_PATH
+ )
+ grob = TEST_DOCS_DIR_PATH + '**/*'
+ indexer.crawl(grob)
+ end
+
+ private
+ def then_ensure_the_all_documents_under_TEST_DOCS_dir_are_indexed_properly_in_the_index
+ index = Index.connect(INDEX_PATH)
+ results = index.query('world')
+ assert 3, results.length
+ results.each do |result|
+ assert_match /world/, result.data
+ end
+
+ results = index.query('hello world')
+ assert 2, results.length
+ results.each do |result|
+ assert_match /hello/, result.data
+ assert_match /world/, result.data
+ end
+
+ results = index.query('wonderful')
+ assert 1, results.length
+ results.each do |result|
+ assert_match /wonderful/, result.data
+ end
+ end
+
+ private
+ def teardown_TEST_dir
+ rm_rf(TEST_DIR_PATH)
+ end
+end
+
+
+class TestXapianStorageEngine #< Test::Unit::TestCase
+
+
+ def test_provides_ability_for_sql_to_query_on_xapian_index
+
+ end
+
+ def set_up_table_uses_xapian_storage_engine
+ sql_script = <<-END_SQL
+ CREATE TABLE Document (
+ ) ENGINE = Xapian
+ //
+
+ CREATE PROCEDURE Document_fulltext_search(
+ TEXT _query
+ )
+ BEGIN
+ SELECT DBS.name, Document.kwin
+ FROM Document, DBS
+ WHERE Document.query = query
+ AND Document.dbs_code = DBS.code
+ ;
+ END
+ //
+
+ CREATE TABLE DBS (
+ code
+ CHAR(24) NOT NULL,
+ CONSTRAINT PK_DBS PRIMARY KEY (code),
+
+ name
+ VARCHAR(512) NOT NULL
+ ) ENGINE = InnoDB
+ //
+ END_SQL
+ end
+
+ def test_query_via_xapian_table
+
+ end
+end
--- /dev/null
+#!/usr/bin/ruby -Ku
+# -*- encoding: UTF-8 -*-
+
+require 'test/unit'
+require 'hdboo/sql'
+
+include Hdboo
+
+module TestSQL
+ USER_SQL = <<-END_SQL
+ -- user.sql
+ -- @Module(Model)
+ -- @Include(Pivot)
+
+ DELIMITER //
+
+ CREATE TABLE UserType (
+ user_type_code
+ CHAR(24) NOT NULL,
+ CONSTRAINT PK_UserType
+ PRIMARY KEY (user_type_code),
+
+ name
+ VARCHAR(256) NOT NULL
+ ) ENGINE = INNODB
+ //
+
+ CREATE PROCEDURE UserType_load_master_data()
+ -- @MasterData
+ BEGIN
+ INSERT INTO UserType
+ VALUES
+ ('User', '通常ユーザー'),
+ ('PrivilegedUser', '特権ユーザー');
+ END
+ //
+
+ CREATE PROCEDURE UserType_list_all_types()
+ -- @DefineConstants(user_type_code)
+ BEGIN
+ SELECT *
+ FROM UserType;
+ END
+ //
+
+ CREATE TABLE User (
+ -- @ClassifiedBy(user_type_code)
+
+ user_id
+ INTEGER AUTO_INCREMENT,
+ CONSTRAINT
+ PK_User PRIMARY KEY(user_id),
+
+ user_type_code
+ CHAR(24) NOT NULL DEFAULT 'User',
+ CONSTRAINT FK_User_user_type_code
+ FOREIGN KEY(user_type_code) REFERENCES UserType(user_type_code),
+
+ is_active
+ -- @Bool
+ INTEGER NOT NULL DEFAULT 0,
+
+ name
+ -- @KanaKanji @NotBlank
+ CHAR(32) NOT NULL,
+
+ nickname
+ -- @Word @NotBlank
+ CHAR(32) NOT NULL,
+ INDEX IND_User_nickname (nickname),
+
+ mail_address
+ -- @MailAddress @NotBlank
+ CHAR(128) NOT NULL,
+ CONSTRAINT UQ_User_mail_address
+ UNIQUE(mail_address),
+
+ comment
+ TEXT NOT NULL
+
+ ) ENGINE=INNODB
+ //
+
+ CREATE PROCEDURE User_count_active_users()
+ -- @ReturnsFirst(INTEGER)
+ BEGIN
+ SELECT COUNT(*)
+ FROM User
+ WHERE is_active;
+ END
+ //
+
+ CREATE PROCEDURE User_list_active_users()
+ -- @Returns(User)
+ BEGIN
+ SELECT *
+ FROM User
+ WHERE is_active;
+ END
+ //
+
+ CREATE PROCEDURE User_find_by_id(
+ _user_id INTEGER -- @ColumnValue
+ )
+ -- @ReturnsFirst(User)
+ BEGIN
+ SELECT *
+ FROM User
+ WHERE user_id = _user_id;
+ END
+ //
+
+ CREATE PROCEDURE User_activate(
+ _mail_address CHAR(128) -- @ColumnValue
+ )
+ -- @ReturnsFirst(User)
+ -- @OnNoData(mail_address, 'メールアドレスが誤っているか、まだ登録されていません。')
+ BEGIN
+ UPDATE User
+ SET is_active = 1
+ WHERE mail_address = _mail_address
+ AND NOT is_active;
+
+ SELECT *
+ FROM User
+ WHERE mail_address = _mail_address
+ AND is_active;
+ END
+ //
+
+ CREATE PROCEDURE User_register(
+ _name TEXT, -- @ColumnValue
+ _nickname TEXT, -- @ColumnValue
+ _mail_address TEXT, -- @ColumnValue
+ _comment TEXT -- @ColumnValue @Optional
+ )
+ -- @ReturnsFirst(User)
+ -- @OnConflict(UQ_User_mail_address, mail_address, 'このメールアドレスは既に登録されています。')
+ BEGIN
+ SET @comment := COALESCE(_comment, '');
+
+ INSERT INTO User
+ VALUES (
+ DEFAULT,
+ DEFAULT,
+ DEFAULT,
+ _name,
+ _nickname,
+ _mail_address,
+ @comment
+ );
+
+ SELECT *
+ FROM User
+ WHERE user_id = LAST_INSERT_ID();
+ END
+ //
+
+ CREATE PROCEDURE User_edit_comment(
+ _user_id INTEGER, -- @ColumnValue
+ _comment TEXT -- @ColumnValue
+ )
+ -- @ReturnsFirst(User)
+ -- @OnNoData(user_id, 'このユーザは登録されていないか、一時的に使用できなくなっています。')
+ BEGIN
+ UPDATE User
+ SET comment = _comment
+ WHERE user_id = _user_id
+ AND is_active;
+
+ SELECT *
+ FROM User
+ WHERE user_id = _user_id
+ AND is_active;
+ END
+ //
+
+ CREATE TABLE PrivilegedUser (
+ user_id
+ INTEGER NOT NULL,
+ CONSTRAINT PK_PrivilegedUser
+ PRIMARY KEY(user_id),
+ CONSTRAINT FK_PrivilegedUser_user_id
+ FOREIGN KEY(user_id) REFERENCES User(user_id)
+ ON DELETE CASCADE,
+
+ access_level
+ -- @Min(1) @Max(5)
+ INTEGER NOT NULL DEFAULT 1
+
+ ) ENGINE = INNODB
+ //
+
+ CREATE PROCEDURE User_make_privileged(
+ _user_id INTEGER -- @ColumnValue
+ )
+ -- @ReturnsFirst(User)
+ -- @OnNoData(user_id, 'このユーザはすでに特権ユーザとなっているか、一時的に使用できなくなっています。')
+ BEGIN
+ UPDATE User
+ SET user_type_code = 'PrivilegedUser'
+ WHERE user_id = _user_id
+ AND is_active
+ AND user_type_code = 'User';
+
+ INSERT INTO PrivilegedUser
+ SELECT user_id, 1
+ FROM User
+ WHERE user_id = _user_id
+ AND is_active
+ AND user_type_code = 'PrivilegedUser';
+
+ SELECT *
+ FROM User JOIN PrivilegedUser USING (user_id)
+ WHERE user_id = _user_id
+ AND is_active;
+ END
+ //
+
+ CREATE PROCEDURE PrivilegedUser_find_by_id(
+ _user_id INTEGER
+ )
+ -- @ReturnsFirst(User)
+ BEGIN
+ SELECT *
+ FROM User JOIN PrivilegedUser USING (user_id)
+ WHERE user_id = _user_id
+ AND is_active
+ AND user_type_code = 'PrivilegedUser';
+ END
+ //
+
+ CREATE PROCEDURE PrivilegedUser_acquire_top_secret(
+ _user_id INTEGER -- @ColumnValue
+ )
+ -- @ReturnsFirst(User)
+ -- @OnNoData('*', 'このユーザには権限がありません。')
+ BEGIN
+ SELECT *, '王様の耳は、ろばタイプ' AS top_secret
+ FROM User JOIN PrivilegedUser USING(user_id)
+ WHERE user_id = _user_id
+ AND is_active;
+ END
+ //
+ END_SQL
+end
+
+class TestSQLLoad < Test::Unit::TestCase
+ include Hdboo
+
+ def test_query
+ SQL.sql_eval!('test_hdboo_temp_db', TestSQL::USER_SQL)
+ db = SQL.connect('test_hdboo_temp_db')
+ sql = <<-SQL_END
+ DELETE
+ FROM UserType
+ WHERE user_type_code = 'User';
+ SQL_END
+ db.transaction do
+ assert_equal 2, Model::UserType.list_all_types.size
+ SQL.query(sql)
+ assert_equal 1, Model::UserType.list_all_types.size\r end
+ end
+
+ def test_equality_of_record_object
+ SQL.sql_eval!('test_hdboo_temp_db', TestSQL::USER_SQL)
+ hogeo = Model::User.new(
+ :user_id => 1,
+ :email => 'hogeta.hogeo@example.com'
+ )
+
+ nise_hogeo = Model::User.new(
+ :user_id => 1,
+ :email => 'nise.hogeo@example.com'
+ )
+
+ assert_equal [1], hogeo.identifier
+ assert_equal [:user_id], hogeo.class.identifier_keys
+ assert hogeo == nise_hogeo
+ assert !hogeo.eql?(nise_hogeo)
+ assert hogeo == {:user_id => 1, :email => 'hogeta.hogeo@example.com'}
+ assert hogeo != {:user_id => 1, :email => ''}
+ assert hogeo.eql?({:user_id => 1, :email => 'hogeta.hogeo@example.com'})
+ assert ({:user_id => 1, :email => 'hogeta.hogeo@example.com'}) == hogeo
+ assert ({:user_id => 1, :email => 'hogeta.hogeo@example.com'}).eql?(hogeo)
+ assert ({:user_id => 1, :email => ''}) != hogeo
+ end
+
+ def test_require_sql!
+ $:.unshift File.join(File.dirname(__FILE__))
+ require_sql 'testmodel/user@test_hdboo_temp_db'
+ assert ::Fuga::Model::User.is_a?(Class)
+ end
+
+ def test_sql_eval!
+ SQL.sql_eval!('test_hdboo_temp_db', TestSQL::USER_SQL)
+ db = SQL.connect('test_hdboo_temp_db')
+
+ #procedure call
+ assert Model::User.respond_to?(:list_active_users)
+ active_users = db.transaction {Model::User.list_active_users}
+ assert active_users.empty?
+
+ assert Model::User.respond_to?(:find_by_id)
+ nobody = db.transaction {Model::User.find_by_id(:user_id => 9999)}
+ assert nobody.nil?
+
+ nobody = db.transaction {Model::User.find_by_id(9999)}
+ assert nobody.nil?
+
+ hogeo = Model::User.new(
+ :name => 'ホゲ田ほげ夫',
+ :nickname => 'hogeo',
+ :mail_address => 'invalid..address@example.com'
+ )
+ assert_raise(InvalidArgument) {hogeo.validate}
+
+ hogeo.mail_address = 'hogeta.hogeo@example.com'
+ assert_nothing_raised {hogeo.validate}
+
+ assert hogeo.respond_to?(:register)
+ assert hogeo.respond_to?(:register!)
+
+ db.transaction { hogeo.register! }
+ assert_equal 1, hogeo.user_id
+ assert ! hogeo.active?
+
+ assert_equal 0, db.transaction { Model::User.count_active_users }
+ db.transaction { hogeo.activate! }
+ assert hogeo.active?
+ assert_equal 1, db.transaction { Model::User.count_active_users }
+
+ all_users = db.transaction { Model::User.list_active_users }
+ assert_equal 1, all_users.size
+ assert all_users[0].is_a?(Model::User)
+
+ assert_equal '', hogeo.comment
+ db.transaction { hogeo.edit_comment!(:comment => "multi\nline\ndata") }
+ assert_equal "multi\nline\ndata", hogeo.comment
+
+
+ #unique constraint violation
+ nise_hogeo = Model::User.new(
+ :name => 'にせホゲ田ほげ夫',
+ :nickname => 'nise_hogeo',
+ :mail_address => 'hogeta.hogeo@example.com' #must be unique
+ )
+
+ assert_raise(DataConflict) do
+ db.transaction { nise_hogeo.register! }\r end
+
+ assert_raise(NoDataFound) do
+ db.transaction do
+ Model::User.activate(:mail_address => 'nobody@example.com')
+ end\r end
+
+ #initialize master data & load as class constants
+ assert Model::UserType.is_a?(Class)
+ assert Model::UserType.const_defined?('User')
+ assert Model::UserType.const_defined?('PrivilegedUser')
+
+ assert_equal 'User', Model::UserType::User.user_type_code
+ assert_equal '通常ユーザー', Model::UserType::User.name
+
+ assert_equal 'PrivilegedUser',
+ Model::UserType::PrivilegedUser.user_type_code
+ assert_equal '特権ユーザー',
+ Model::UserType::PrivilegedUser.name
+
+ #inheritance
+ assert Model::PrivilegedUser.is_a?(Class)
+ assert Model::PrivilegedUser < Model::User
+
+ super_hogeo = Model::PrivilegedUser.new(
+ :user_id => 1,
+ :name => '超ホゲ田ほげ夫',
+ :nickname => 'hogeo',
+ :mail_address => 'hogeta.hogeo@example.com'
+ )
+
+ assert super_hogeo.is_a?(Model::PrivilegedUser)
+ assert ! hogeo.respond_to?(:acquire_top_secret)
+ assert super_hogeo.respond_to?(:acquire_top_secret)
+
+ assert_raise(NoDataFound) do
+ db.transaction {super_hogeo.acquire_top_secret!}\r end
+
+ #overridden method
+ assert_not_nil db.transaction {Model::User.find_by_id(:user_id => 1)}
+ assert_not_nil db.transaction {Model::User.find_by_id(1)}
+ assert_nil db.transaction {Model::PrivilegedUser.find_by_id(:user_id => 1)}
+ assert_nil db.transaction {Model::PrivilegedUser.find_by_id(1)}
+
+ db.transaction { super_hogeo = hogeo.make_privileged }
+ assert super_hogeo.is_a?(Model::PrivilegedUser)
+
+ assert_not_nil db.transaction {Model::PrivilegedUser.find_by_id(1)}
+ assert super_hogeo.respond_to?(:access_level)
+ assert_equal 1, super_hogeo.access_level
+
+ db.transaction { super_hogeo.acquire_top_secret! }
+ assert_equal '王様の耳は、ろばタイプ', super_hogeo.top_secret
+ end
+
+ def test_load_sql
+ SQL.sql_eval('test_hdboo_temp_db', TestSQL::USER_SQL)
+
+ assert Model::User.is_a?(Class)
+
+ hogeo = Model::User.new(
+ :name => 'ホゲ田ほげ夫',
+ :nickname => 'hogeo',
+ :mail_address => 'hogeta.hogeo@example.com',
+ :is_active => 0
+ )
+
+ #accessors
+ assert hogeo.respond_to?(:mail_address)
+ assert hogeo.respond_to?(:mail_address=)
+ assert_equal 'hogeta.hogeo@example.com', hogeo.mail_address
+ assert hogeo.respond_to?(:nickname)
+ assert hogeo.respond_to?(:nickname=)
+ assert_equal 'hogeo', hogeo.nickname
+
+ #accessors (boolean)
+ assert hogeo.respond_to?(:is_active)
+ assert hogeo.respond_to?(:is_active=)
+ assert hogeo.respond_to?(:active?)
+ assert_equal 0, hogeo.is_active
+ assert_equal false, hogeo.active?
+
+ #instance method
+ assert hogeo.respond_to?(:count_active_users)
+ assert hogeo.respond_to?(:list_active_users)
+
+ #class method
+ assert hogeo.class.respond_to?(:count_active_users)
+ assert hogeo.class.respond_to?(:list_active_users)
+
+ #validation
+ assert hogeo.respond_to?(:invalid?)
+ assert hogeo.respond_to?(:validate)
+ assert !hogeo.invalid?
+ assert_nothing_raised do
+ hogeo.validate
+ end
+
+ assert hogeo.validate.is_a?(Model::User)
+
+
+ raised_error = nil
+ begin
+ hogeo.is_active = 'treu'
+ rescue
+ raised_error = $!
+ end
+ assert ! raised_error.nil?
+ assert raised_error.is_a?(InvalidArgument)
+ assert_equal 1, raised_error.messages.size
+ assert_match /treu/, raised_error.messages[:is_active].to_s
+
+ hogeo.is_active = true
+ assert hogeo.active?
+ end
+
+ def test_csv_util
+ SQL.sql_eval!('test_hdboo_temp_db', <<-END_DDL)
+ -- @Module(CSVTest)
+ -- @Include(Pivot)
+
+ DELIMITER //
+
+ CREATE TABLE Dummy (
+ id INTEGER NOT NULL AUTO_INCREMENT,
+ PRIMARY KEY (id)
+ ) ENGINE=MYISAM
+ //
+
+ CREATE PROCEDURE Dummy_parse(csv VARCHAR(1024))
+ BEGIN
+ SELECT csv_get(csv, pos) AS val
+ FROM pivot
+ WHERE pos <= csv_length(csv)
+ ;
+ END
+ //
+ END_DDL
+
+ db = SQL.connect('test_hdboo_temp_db')
+
+ result = db.transaction do
+ CSVTest::Dummy.parse(:csv => 'hoge,fuga,piyo')
+ end
+ assert_equal 3, result.length
+ assert_equal 'hoge', result[0]['val']
+ assert_equal 'fuga', result[1]['val']
+ assert_equal 'piyo', result[2]['val']
+ end
+end
+
+class TestSQLParser < Test::Unit::TestCase
+ def test_parse
+ ast = SQL::Parser.new.parse(TestSQL::USER_SQL)
+
+ create_table_statements = []
+ create_procedure_statements = []
+
+ SQL::Parser::ASTWalker.new\
+ .visit(:CREATE_TABLE) do |statement|
+ create_table_statements << statement
+ end\
+ .visit(:CREATE_PROCEDURE) do |statement|
+ create_procedure_statements << statement
+ end\
+ .walk(ast)
+
+ assert_equal 3, create_table_statements.length
+ assert_equal 11, create_procedure_statements.length
+
+ user_table_source = create_table_statements[0].source.collect do |line|
+ line.strip
+ end.join("\n")
+ end
+end
+
+class TestTable < Test::Unit::TestCase
+ include Hdboo::SQL
+ def test_new
+ sql_module = SQL::Module.new('TestModule.Model')
+ sql_module.elements << Table.new(:User, [
+ Table::Column.new(:id, DataType.new(:INTEGER)),
+ Table::Constraint::PrimaryKey.new(:User_id_PK, [:id]),
+ Table::Column.new(:mail_address, DataType.new(:CHAR, 128)),
+ Table::Column.new(:password, DataType.new(:CHAR, 32))
+ ])
+ sql_module.eval
+
+ assert TestModule::Model::User.is_a?(Class)
+ end
+end